mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-26 23:54:15 +01:00
[SC64][SW] Added access to the SD card via USB interface (#90)
This commit is contained in:
parent
3146cc8c99
commit
74e20cb8cc
@ -18,6 +18,7 @@
|
|||||||
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
|
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
|
||||||
- Enhanced [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
|
- Enhanced [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
|
||||||
- PC app for communicating with flashcart (game/save data upload/download, feature enable control and debug terminal)
|
- PC app for communicating with flashcart (game/save data upload/download, feature enable control and debug terminal)
|
||||||
|
- Access to the SD card via USB interface with the use of the PC app
|
||||||
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
|
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
|
||||||
- Initial programming via UART header or dedicated JTAG/SWD interfaces
|
- Initial programming via UART header or dedicated JTAG/SWD interfaces
|
||||||
- Software and firmware updatable via USB interface
|
- Software and firmware updatable via USB interface
|
||||||
|
@ -1,50 +1,38 @@
|
|||||||
- [First time setup on PC](#first-time-setup-on-pc)
|
- [First time setup](#first-time-setup)
|
||||||
- [Firmware backup/update](#firmware-backupupdate)
|
- [Standalone mode (Running menu and games on the N64)](#standalone-mode-running-menu-and-games-on-the-n64)
|
||||||
- [Running menu in standalone mode](#running-menu-in-standalone-mode)
|
- [Developer mode (Uploading ROMs from the PC, and more)](#developer-mode-uploading-roms-from-the-pc-and-more)
|
||||||
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
||||||
- [Downloading save to PC](#downloading-save-to-pc)
|
- [Downloading save to PC](#downloading-save-to-pc)
|
||||||
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
||||||
- [Direct boot option](#direct-boot-option)
|
- [Direct boot option](#direct-boot-option)
|
||||||
- [Debug terminal on PC](#debug-terminal-on-pc)
|
- [Debug terminal on PC](#debug-terminal-on-pc)
|
||||||
|
- [Firmware backup/update](#firmware-backupupdate)
|
||||||
- [LED blink patters](#led-blink-patters)
|
- [LED blink patters](#led-blink-patters)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## First time setup on PC
|
# First time setup
|
||||||
|
|
||||||
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
## Standalone mode (Running menu and games on the N64)
|
||||||
|
|
||||||
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) and firmware (`sc64-firmware-{version}.bin`) from GitHub releases page
|
|
||||||
2. Extract deployer tool package contents to a folder and place firmware file inside it
|
|
||||||
3. Connect SC64 device to your computer with USB type C cable
|
|
||||||
4. Run `./sc64deployer list` to check if device is detected in the system
|
|
||||||
5. Update SC64 firmware to the latest version with `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
|
||||||
6. Run `./sc64deployer info` to check if update process finished successfully and SC64 is detected correctly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Firmware backup/update
|
|
||||||
|
|
||||||
Keeping SC64 firmware up to date is highly recommended.
|
|
||||||
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
|
||||||
|
|
||||||
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
|
||||||
|
|
||||||
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
|
||||||
|
|
||||||
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running menu in standalone mode
|
|
||||||
|
|
||||||
Menu, as known from 64drive or EverDrive-64, is developed in another repository: [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu).
|
Menu, as known from 64drive or EverDrive-64, is developed in another repository: [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu).
|
||||||
Download latest version from [here](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases) and put `sc64menu.n64` file in the root directory of the SD card.
|
Download latest version from [here](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases) and put `sc64menu.n64` file in the root directory of the SD card.
|
||||||
|
Additionally, follow the instructions in the N64FlashcartMenu repository for more information about thr SD card setup and extra functionality.
|
||||||
When N64 is powered on menu is automatically loaded from the SD card. Supported file system formats are FAT32 and exFAT.
|
When N64 is powered on menu is automatically loaded from the SD card. Supported file system formats are FAT32 and exFAT.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Uploading game and/or save from PC
|
## Developer mode (Uploading ROMs from the PC, and more)
|
||||||
|
|
||||||
|
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
||||||
|
|
||||||
|
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) from the GitHub releases page
|
||||||
|
2. Extract deployer tool package contents to a folder
|
||||||
|
3. Connect SC64 device to your computer with USB type C cable
|
||||||
|
4. Run `./sc64deployer list` to check if device is detected in the system
|
||||||
|
5. Follow instructions below for specific use cases
|
||||||
|
|
||||||
|
### Uploading game and/or save from PC
|
||||||
|
|
||||||
`./sc64deployer upload path_to_rom.n64 --save-type eeprom4k --save path_to_save.sav`
|
`./sc64deployer upload path_to_rom.n64 --save-type eeprom4k --save path_to_save.sav`
|
||||||
|
|
||||||
@ -53,18 +41,14 @@ Application will try to autodetect used save type so explicitly setting save typ
|
|||||||
Check included help in the application to list available save types.
|
Check included help in the application to list available save types.
|
||||||
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start with fresh save file.
|
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start with fresh save file.
|
||||||
|
|
||||||
---
|
### Downloading save to PC
|
||||||
|
|
||||||
## Downloading save to PC
|
|
||||||
|
|
||||||
`./sc64deployer download save path_to_save.sav`
|
`./sc64deployer download save path_to_save.sav`
|
||||||
|
|
||||||
Replace `path_to_save.sav` with appropriate value.
|
Replace `path_to_save.sav` with appropriate value.
|
||||||
Command will raise error when no save type is currently enabled in the SC64 device.
|
Command will raise error when no save type is currently enabled in the SC64 device.
|
||||||
|
|
||||||
---
|
### Running 64DD games from PC
|
||||||
|
|
||||||
## Running 64DD games from PC
|
|
||||||
|
|
||||||
64DD games require DDIPL ROM and disk images.
|
64DD games require DDIPL ROM and disk images.
|
||||||
To run disk game type `./sc64deployer 64dd path_to_ddipl.n64 path_to_disk_1.ndd path_to_disk_2.ndd`.
|
To run disk game type `./sc64deployer 64dd path_to_ddipl.n64 path_to_disk_1.ndd path_to_disk_2.ndd`.
|
||||||
@ -79,31 +63,38 @@ Make sure retail and development disks formats aren't mixed together.
|
|||||||
If disk game supports running in conjunction with cartridge game then `--rom path_to_rom.n64` argument can be added to command above.
|
If disk game supports running in conjunction with cartridge game then `--rom path_to_rom.n64` argument can be added to command above.
|
||||||
N64 will boot cartridge game instead of 64DD IPL.
|
N64 will boot cartridge game instead of 64DD IPL.
|
||||||
|
|
||||||
---
|
### Direct boot option
|
||||||
|
|
||||||
## Direct boot option
|
|
||||||
|
|
||||||
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
|
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
|
||||||
Pass `--direct` option in `upload` or `64dd` command to disable bootloader during boot and console reset.
|
Pass `--direct` option in `upload` or `64dd` command to disable bootloader during boot and console reset.
|
||||||
This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
|
This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
|
||||||
TV type cannot be forced when direct boot mode is enabled.
|
TV type cannot be forced when direct boot mode is enabled.
|
||||||
|
|
||||||
---
|
### Debug terminal on PC
|
||||||
|
|
||||||
## Debug terminal on PC
|
|
||||||
|
|
||||||
`sc64deployer` application supports UNFLoader protocol and has same functionality implemented as aforementioned program.
|
`sc64deployer` application supports UNFLoader protocol and has same functionality implemented as aforementioned program.
|
||||||
Type `./sc64deployer debug` to activate it.
|
Type `./sc64deployer debug` to activate it.
|
||||||
|
|
||||||
|
### Firmware backup/update
|
||||||
|
|
||||||
|
Keeping SC64 firmware up to date is strongly recommended.
|
||||||
|
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||||
|
|
||||||
|
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
||||||
|
|
||||||
|
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
||||||
|
|
||||||
|
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## LED blink patters
|
# 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 patterns:
|
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 patterns:
|
||||||
|
|
||||||
| Pattern | Meaning |
|
| Pattern | Meaning |
|
||||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||||
| Nx [Short ON - Short OFF] | SD card access is in progress (initialization or data read/write) or save writeback is in progress |
|
| Irregular | SD card access is in progress (initialization or data read/write) or save writeback was finished |
|
||||||
| Nx [Medium ON - Long OFF] | CIC region did not match, please power off console and power on again |
|
| Nx [Medium ON - Long OFF] | CIC region did not 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 |
|
| 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 |
|
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
||||||
@ -112,4 +103,4 @@ LED on SC64 board can blink in certain situations. Most of them during normal us
|
|||||||
Nx means that blink count is varied.
|
Nx means that blink count is varied.
|
||||||
|
|
||||||
LED blinking on SD card access can be disabled through `sc64deployer` application.
|
LED blinking on SD card access can be disabled through `sc64deployer` application.
|
||||||
Please refer to included help for option to change the LED behavior.
|
Please refer to the included help for option to change the LED behavior.
|
||||||
|
@ -25,9 +25,9 @@ This mapping is used internally by FPGA/μC and when accessing flashcart from US
|
|||||||
| Flash [1] | `0x0400_0000` | 16 MiB | RW/R | Flash |
|
| Flash [1] | `0x0400_0000` | 16 MiB | RW/R | Flash |
|
||||||
| Data buffer | `0x0500_0000` | 8 kiB | RW | BlockRAM |
|
| Data buffer | `0x0500_0000` | 8 kiB | RW | BlockRAM |
|
||||||
| EEPROM | `0x0500_2000` | 2 kiB | RW | BlockRAM |
|
| EEPROM | `0x0500_2000` | 2 kiB | RW | BlockRAM |
|
||||||
| 64DD buffer | `0x0500_2800` | 256 bytes | RW | BlockRAM |
|
| 64DD/MCU buffer | `0x0500_2800` | 1 kiB | RW | BlockRAM |
|
||||||
| FlashRAM buffer [2] | `0x0500_2900` | 128 bytes | R | BlockRAM |
|
| FlashRAM buffer [2] | `0x0500_2C00` | 128 bytes | R | BlockRAM |
|
||||||
| N/A [3] | `0x0500_2980` | to `0x07FF_FFFF` | R | N/A |
|
| N/A [3] | `0x0500_2C80` | to `0x07FF_FFFF` | R | N/A |
|
||||||
|
|
||||||
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
|
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
|
||||||
- Note [2]: Due to BlockRAM usage optimization this section is read only.
|
- Note [2]: Due to BlockRAM usage optimization this section is read only.
|
||||||
@ -53,8 +53,8 @@ This mapping is used when accessing flashcart from N64 side.
|
|||||||
| ROM shadow [7] | `0x1FFC_0000` | 128 kiB | R | `0x04FE_0000` | Flash | mem bus | SC64 register access is enabled |
|
| ROM shadow [7] | `0x1FFC_0000` | 128 kiB | R | `0x04FE_0000` | Flash | mem bus | SC64 register access is enabled |
|
||||||
| Data buffer | `0x1FFE_0000` | 8 kiB | RW | `0x0500_0000` | Block RAM | mem bus | SC64 register access is enabled |
|
| Data buffer | `0x1FFE_0000` | 8 kiB | RW | `0x0500_0000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
|
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| 64DD buffer [8] | `0x1FFE_2800` | 256 bytes | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
| 64DD/MCU buffer [8] | `0x1FFE_2800` | 1 kiB | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| FlashRAM buffer [8] | `0x1FFE_2900` | 128 bytes | R | `0x0500_2900` | Block RAM | mem bus | SC64 register access is enabled |
|
| FlashRAM buffer [8] | `0x1FFE_2C00` | 128 bytes | R | `0x0500_2C00` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
|
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
|
||||||
|
|
||||||
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
|
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
## N64 commands
|
## N64 commands
|
||||||
|
|
||||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||||
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ---------------------------------------------------------- |
|
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ------------------------------------------------------------ |
|
||||||
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||||
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
||||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||||
@ -18,10 +18,10 @@
|
|||||||
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
| `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 |
|
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
||||||
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
||||||
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on SD card |
|
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on the SD card |
|
||||||
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
||||||
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from SD card to flashcart |
|
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from the SD card to flashcart memory space |
|
||||||
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from flashcart to SD card |
|
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from the flashcart memory space to the SD card |
|
||||||
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
||||||
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
||||||
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
||||||
|
@ -45,6 +45,22 @@
|
|||||||
- [`data` (data)](#data-data-1)
|
- [`data` (data)](#data-data-1)
|
||||||
- [`X`: **AUX\_WRITE**](#x-aux_write)
|
- [`X`: **AUX\_WRITE**](#x-aux_write)
|
||||||
- [`arg0` (data)](#arg0-data)
|
- [`arg0` (data)](#arg0-data)
|
||||||
|
- [`i`: **SD\_CARD\_OP**](#i-sd_card_op)
|
||||||
|
- [`arg0` (address)](#arg0-address-2)
|
||||||
|
- [`arg1` (operation)](#arg1-operation)
|
||||||
|
- [`response` (result/status)](#response-resultstatus)
|
||||||
|
- [Available SD card operations](#available-sd-card-operations)
|
||||||
|
- [SD card status](#sd-card-status)
|
||||||
|
- [`s`: **SD\_READ**](#s-sd_read)
|
||||||
|
- [`arg0` (address)](#arg0-address-3)
|
||||||
|
- [`arg1` (sector\_count)](#arg1-sector_count)
|
||||||
|
- [`data` (sector)](#data-sector)
|
||||||
|
- [`response` (result)](#response-result)
|
||||||
|
- [`S`: **SD\_WRITE**](#s-sd_write)
|
||||||
|
- [`arg0` (address)](#arg0-address-4)
|
||||||
|
- [`arg1` (sector\_count)](#arg1-sector_count-1)
|
||||||
|
- [`data` (sector)](#data-sector-1)
|
||||||
|
- [`response` (result)](#response-result-1)
|
||||||
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
|
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
|
||||||
- [`arg0` (error)](#arg0-error)
|
- [`arg0` (error)](#arg0-error)
|
||||||
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
|
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
|
||||||
@ -153,7 +169,7 @@ Available packet IDs are listed in the [asynchronous packets](#asynchronous-pack
|
|||||||
## Supported commands
|
## Supported commands
|
||||||
|
|
||||||
| id | name | arg0 | arg1 | data | response | description |
|
| id | name | arg0 | arg1 | data | response | description |
|
||||||
| --- | ----------------------------------------------- | ------------ | ------------- | ---- | ---------------- | -------------------------------------------------------------- |
|
| --- | ----------------------------------------------- | ------------ | ------------- | ------ | ---------------- | -------------------------------------------------------------- |
|
||||||
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||||
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
||||||
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||||
@ -168,6 +184,9 @@ Available packet IDs are listed in the [asynchronous packets](#asynchronous-pack
|
|||||||
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
||||||
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||||
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
||||||
|
| `i` | [**SD_CARD_OP**](#i-sd_card_op) | address | operation | --- | result/status | Perform special operation on the SD card |
|
||||||
|
| `s` | [**SD_READ**](#s-sd_read) | address | sector_count | sector | result | Read sectors from the SD card to flashcart memory space |
|
||||||
|
| `S` | [**SD_WRITE**](#s-sd_write) | address | sector_count | sector | result | Write sectors from the flashcart memory space to the SD card |
|
||||||
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||||
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
||||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||||
@ -223,7 +242,7 @@ _This command does not require arguments or data._
|
|||||||
_This command does not send response data._
|
_This command does not send response data._
|
||||||
|
|
||||||
This command is used to reset most of the config options to default state (same as on power-up).
|
This command is used to reset most of the config options to default state (same as on power-up).
|
||||||
Additionally, CIC emulation is enabled and 6102/7101 seed/checksum values are set.
|
Additionally, CIC emulation is enabled, 6102/7101 seed/checksum values are set and SD card lock is released.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -467,6 +486,110 @@ This command puts 32 bits of data to the AUX register accessible from the N64 si
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `i`: **SD_CARD_OP**
|
||||||
|
|
||||||
|
**Perform special operation on the SD card**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (operation)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Operation |
|
||||||
|
|
||||||
|
#### `response` (result/status)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
| `4` | uint32_t | SD card status (always returned and valid regardless of the SD card operation result) |
|
||||||
|
|
||||||
|
This command performs special operation on the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
PC and N64 cannot use the SD card interface at the same time. Lock mechanism is implemented to prevent data corruption.
|
||||||
|
SD card access is locked when PC or N64 requests SD card initialization, and unlocked when the card is uninitialized.
|
||||||
|
Lock is also released when certain conditions occur - when N64 side lock is active then it is released when the console is powered off
|
||||||
|
and save writeback operation is not pending. PC side lock is released when USB interface is in reset state, or the USB cable is unplugged.
|
||||||
|
|
||||||
|
#### Available SD card operations
|
||||||
|
| operation | description |
|
||||||
|
| --------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | Init SD card |
|
||||||
|
| `1` | Deinit SD card |
|
||||||
|
| `2` | Get SD card status |
|
||||||
|
| `3` | Get SD card info (loads CSD and CID registers to a specified address, data length is 32 bytes) |
|
||||||
|
| `4` | Turn on byte swap |
|
||||||
|
| `5` | Turn off byte swap |
|
||||||
|
|
||||||
|
#### SD card status
|
||||||
|
| bits | description |
|
||||||
|
| -------- | -------------------------------------------------------------------------- |
|
||||||
|
| `[31:5]` | _Unused_ |
|
||||||
|
| `[4]` | `0` - Byte swap disabled, `1` - Byte swap enabled (valid when initialized) |
|
||||||
|
| `[3]` | `0` - 25 MHz clock, `1` - 50 MHz clock (valid when initialized) |
|
||||||
|
| `[2]` | `0` - Byte addressed, `1` - Sector addressed (valid when initialized) |
|
||||||
|
| `[1]` | `0` - SD card not initialized, `1` - SD card initialized |
|
||||||
|
| `[0]` | `0` - SD card not inserted, `1` - SD card inserted |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `s`: **SD_READ**
|
||||||
|
|
||||||
|
**Read sectors from the SD card to flashcart memory space**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (sector_count)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ------------ |
|
||||||
|
| `[31:0]` | Sector count |
|
||||||
|
|
||||||
|
#### `data` (sector)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | --------------- |
|
||||||
|
| `0` | uint32_t | Starting sector |
|
||||||
|
|
||||||
|
#### `response` (result)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
|
||||||
|
This command reads sectors from the SD card to a specified memory address. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `S`: **SD_WRITE**
|
||||||
|
|
||||||
|
**Write sectors from the flashcart memory space to the SD card**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (sector_count)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ------------ |
|
||||||
|
| `[31:0]` | Sector count |
|
||||||
|
|
||||||
|
#### `data` (sector)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | --------------- |
|
||||||
|
| `0` | uint32_t | Starting sector |
|
||||||
|
|
||||||
|
#### `response` (result)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
|
||||||
|
This command writes sectors from a specified memory address to the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `D`: **DD_SET_BLOCK_READY**
|
### `D`: **DD_SET_BLOCK_READY**
|
||||||
|
|
||||||
**Notify flashcart about 64DD block readiness**
|
**Notify flashcart about 64DD block readiness**
|
||||||
|
@ -36,11 +36,11 @@ module memory_bram (
|
|||||||
eeprom_selected = 1'b0;
|
eeprom_selected = 1'b0;
|
||||||
dd_selected = 1'b0;
|
dd_selected = 1'b0;
|
||||||
flashram_selected = 1'b0;
|
flashram_selected = 1'b0;
|
||||||
if (mem_bus.address[25:24] == 2'b01 && mem_bus.address[23:14] == 10'd0) begin
|
if (mem_bus.address[26:24] == 3'h5) begin
|
||||||
buffer_selected = mem_bus.address[13] == 1'b0;
|
buffer_selected = (mem_bus.address[23:0] >= 24'h00_0000 && mem_bus.address[23:0] < 24'h00_2000);
|
||||||
eeprom_selected = mem_bus.address[13:11] == 3'b100;
|
eeprom_selected = (mem_bus.address[23:0] >= 24'h00_2000 && mem_bus.address[23:0] < 24'h00_2800);
|
||||||
dd_selected = mem_bus.address[13:8] == 6'b101000;
|
dd_selected = (mem_bus.address[23:0] >= 24'h00_2800 && mem_bus.address[23:0] < 24'h00_2C00);
|
||||||
flashram_selected = mem_bus.address[13:7] == 7'b1010010;
|
flashram_selected = (mem_bus.address[23:0] >= 24'h00_2C00 && mem_bus.address[23:0] < 24'h00_2C80);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -112,26 +112,26 @@ module memory_bram (
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
// DD memory
|
// 64DD/MCU buffer memory
|
||||||
|
|
||||||
logic [15:0] dd_bram [0:127];
|
logic [15:0] dd_bram [0:511];
|
||||||
logic [15:0] dd_bram_rdata;
|
logic [15:0] dd_bram_rdata;
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
if (write && dd_selected) begin
|
if (write && dd_selected) begin
|
||||||
dd_bram[mem_bus.address[7:1]] <= mem_bus.wdata;
|
dd_bram[mem_bus.address[9:1]] <= mem_bus.wdata;
|
||||||
end
|
end
|
||||||
if (n64_scb.dd_write) begin
|
if (n64_scb.dd_write) begin
|
||||||
dd_bram[n64_scb.dd_address] <= n64_scb.dd_wdata;
|
dd_bram[{2'b00, n64_scb.dd_address}] <= n64_scb.dd_wdata;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
dd_bram_rdata <= dd_bram[mem_bus.address[7:1]];
|
dd_bram_rdata <= dd_bram[mem_bus.address[9:1]];
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
n64_scb.dd_rdata <= dd_bram[n64_scb.dd_address];
|
n64_scb.dd_rdata <= dd_bram[{2'b00, n64_scb.dd_address}];
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
6
sw/bootloader/src/fatfs/ffconf.h
vendored
6
sw/bootloader/src/fatfs/ffconf.h
vendored
@ -30,7 +30,7 @@
|
|||||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||||
|
|
||||||
|
|
||||||
#define FF_USE_MKFS 0
|
#define FF_USE_MKFS 1
|
||||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
@ -133,7 +133,7 @@
|
|||||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_LFN_UNICODE 0
|
#define FF_LFN_UNICODE 2
|
||||||
/* This option switches the character encoding on the API when LFN is enabled.
|
/* This option switches the character encoding on the API when LFN is enabled.
|
||||||
/
|
/
|
||||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||||
@ -145,7 +145,7 @@
|
|||||||
/ When LFN is not enabled, this option has no effect. */
|
/ When LFN is not enabled, this option has no effect. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_LFN_BUF 255
|
#define FF_LFN_BUF 1023
|
||||||
#define FF_SFN_BUF 12
|
#define FF_SFN_BUF 12
|
||||||
/* This set of options defines size of file name members in the FILINFO structure
|
/* This set of options defines size of file name members in the FILINFO structure
|
||||||
/ which is used to read out directory items. These values should be suffcient for
|
/ which is used to read out directory items. These values should be suffcient for
|
||||||
|
@ -194,6 +194,7 @@ const char *sc64_error_description (sc64_error_t error) {
|
|||||||
case SD_ERROR_ACMD41_IO: return "ACMD41 I/O";
|
case SD_ERROR_ACMD41_IO: return "ACMD41 I/O";
|
||||||
case SD_ERROR_ACMD41_OCR: return "ACMD41 OCR";
|
case SD_ERROR_ACMD41_OCR: return "ACMD41 OCR";
|
||||||
case SD_ERROR_ACMD41_TIMEOUT: return "ACMD41 timeout";
|
case SD_ERROR_ACMD41_TIMEOUT: return "ACMD41 timeout";
|
||||||
|
case SD_ERROR_LOCKED: return "SD card is locked by the PC side";
|
||||||
default: return "Unknown error (SD)";
|
default: return "Unknown error (SD)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ typedef enum {
|
|||||||
SD_ERROR_ACMD41_IO = 27,
|
SD_ERROR_ACMD41_IO = 27,
|
||||||
SD_ERROR_ACMD41_OCR = 28,
|
SD_ERROR_ACMD41_OCR = 28,
|
||||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||||
|
SD_ERROR_LOCKED = 30,
|
||||||
} sc64_sd_error_t;
|
} sc64_sd_error_t;
|
||||||
|
|
||||||
typedef uint32_t sc64_error_t;
|
typedef uint32_t sc64_error_t;
|
||||||
|
@ -141,6 +141,10 @@ static struct process p;
|
|||||||
|
|
||||||
|
|
||||||
static bool cfg_cmd_check (void) {
|
static bool cfg_cmd_check (void) {
|
||||||
|
if (!writeback_pending() && !hw_gpio_get(GPIO_ID_N64_RESET)) {
|
||||||
|
sd_release_lock(SD_LOCK_N64);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
|
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
|
||||||
|
|
||||||
if (reg & CFG_CMD_AUX_PENDING) {
|
if (reg & CFG_CMD_AUX_PENDING) {
|
||||||
@ -634,21 +638,25 @@ void cfg_process (void) {
|
|||||||
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
|
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_ID_SD_CARD_OP:
|
case CMD_ID_SD_CARD_OP: {
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
switch (p.data[1]) {
|
switch (p.data[1]) {
|
||||||
case SD_CARD_OP_DEINIT:
|
case SD_CARD_OP_DEINIT:
|
||||||
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
sd_card_deinit();
|
sd_card_deinit();
|
||||||
|
sd_release_lock(SD_LOCK_N64);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SD_CARD_OP_INIT: {
|
case SD_CARD_OP_INIT:
|
||||||
|
error = sd_try_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
led_activity_on();
|
led_activity_on();
|
||||||
sd_error_t error = sd_card_init();
|
error = sd_card_init();
|
||||||
led_activity_off();
|
led_activity_off();
|
||||||
if (error != SD_OK) {
|
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SD_CARD_OP_GET_STATUS:
|
case SD_CARD_OP_GET_STATUS:
|
||||||
p.data[1] = sd_card_get_status();
|
p.data[1] = sd_card_get_status();
|
||||||
@ -658,36 +666,44 @@ void cfg_process (void) {
|
|||||||
if (cfg_translate_address(&p.data[0], SD_CARD_INFO_SIZE, (SDRAM | BRAM))) {
|
if (cfg_translate_address(&p.data[0], SD_CARD_INFO_SIZE, (SDRAM | BRAM))) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
sd_error_t error = sd_card_get_info(p.data[0]);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_card_get_info(p.data[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SD_CARD_OP_BYTE_SWAP_ON: {
|
case SD_CARD_OP_BYTE_SWAP_ON:
|
||||||
sd_error_t error = sd_set_byte_swap(true);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_set_byte_swap(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SD_CARD_OP_BYTE_SWAP_OFF: {
|
case SD_CARD_OP_BYTE_SWAP_OFF:
|
||||||
sd_error_t error = sd_set_byte_swap(false);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_set_byte_swap(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_OPERATION);
|
error = SD_ERROR_INVALID_OPERATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (error != SD_OK) {
|
||||||
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CMD_ID_SD_SECTOR_SET:
|
case CMD_ID_SD_SECTOR_SET: {
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error != SD_OK) {
|
||||||
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
|
}
|
||||||
p.sd_card_sector = p.data[0];
|
p.sd_card_sector = p.data[0];
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CMD_ID_SD_READ: {
|
case CMD_ID_SD_READ: {
|
||||||
if (p.data[1] >= 0x800000) {
|
if (p.data[1] >= 0x800000) {
|
||||||
@ -696,9 +712,12 @@ void cfg_process (void) {
|
|||||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
led_activity_on();
|
led_activity_on();
|
||||||
sd_error_t error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||||
led_activity_off();
|
led_activity_off();
|
||||||
|
}
|
||||||
if (error != SD_OK) {
|
if (error != SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
@ -713,9 +732,12 @@ void cfg_process (void) {
|
|||||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
led_activity_on();
|
led_activity_on();
|
||||||
sd_error_t error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||||
led_activity_off();
|
led_activity_off();
|
||||||
|
}
|
||||||
if (error != SD_OK) {
|
if (error != SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
|
@ -134,11 +134,14 @@ static bool dd_block_read_request (void) {
|
|||||||
uint16_t index = dd_track_head_block();
|
uint16_t index = dd_track_head_block();
|
||||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||||
if (p.sd_mode) {
|
if (p.sd_mode) {
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
||||||
led_activity_on();
|
led_activity_on();
|
||||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
||||||
led_activity_off();
|
led_activity_off();
|
||||||
|
}
|
||||||
dd_set_block_ready(error == SD_OK);
|
dd_set_block_ready(error == SD_OK);
|
||||||
} else {
|
} else {
|
||||||
usb_tx_info_t packet_info;
|
usb_tx_info_t packet_info;
|
||||||
@ -158,11 +161,14 @@ static bool dd_block_write_request (void) {
|
|||||||
uint32_t index = dd_track_head_block();
|
uint32_t index = dd_track_head_block();
|
||||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||||
if (p.sd_mode) {
|
if (p.sd_mode) {
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
||||||
led_activity_on();
|
led_activity_on();
|
||||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
||||||
led_activity_off();
|
led_activity_off();
|
||||||
|
}
|
||||||
dd_set_block_ready(error == SD_OK);
|
dd_set_block_ready(error == SD_OK);
|
||||||
} else {
|
} else {
|
||||||
usb_tx_info_t packet_info;
|
usb_tx_info_t packet_info;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
||||||
#define FLASHRAM_PAGE_SIZE (128)
|
#define FLASHRAM_PAGE_SIZE (128)
|
||||||
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
||||||
#define FLASHRAM_BUFFER_ADDRESS (0x05002900UL)
|
#define FLASHRAM_BUFFER_ADDRESS (0x05002C00UL)
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
|
||||||
|
|
||||||
#define SD_INIT_BUFFER_ADDRESS (0x05002800UL)
|
#define SD_INIT_BUFFER_ADDRESS (0x05002BB8UL)
|
||||||
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
|
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
|
||||||
|
|
||||||
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
||||||
@ -86,6 +86,7 @@ struct process {
|
|||||||
uint8_t csd[16];
|
uint8_t csd[16];
|
||||||
uint8_t cid[16];
|
uint8_t cid[16];
|
||||||
bool byte_swap;
|
bool byte_swap;
|
||||||
|
sd_lock_t lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -428,6 +429,7 @@ sd_error_t sd_card_init (void) {
|
|||||||
void sd_card_deinit (void) {
|
void sd_card_deinit (void) {
|
||||||
if (p.card_initialized) {
|
if (p.card_initialized) {
|
||||||
p.card_initialized = false;
|
p.card_initialized = false;
|
||||||
|
p.card_type_block = false;
|
||||||
p.byte_swap = false;
|
p.byte_swap = false;
|
||||||
sd_set_clock(CLOCK_400KHZ);
|
sd_set_clock(CLOCK_400KHZ);
|
||||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||||
@ -575,10 +577,32 @@ sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32
|
|||||||
return SD_OK;
|
return SD_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sd_error_t sd_get_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == lock) {
|
||||||
|
return SD_OK;
|
||||||
|
}
|
||||||
|
return SD_ERROR_LOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
sd_error_t sd_try_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == SD_LOCK_NONE) {
|
||||||
|
p.lock = lock;
|
||||||
|
return SD_OK;
|
||||||
|
}
|
||||||
|
return sd_get_lock(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sd_release_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == lock) {
|
||||||
|
p.lock = SD_LOCK_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void sd_init (void) {
|
void sd_init (void) {
|
||||||
p.card_initialized = false;
|
p.card_initialized = false;
|
||||||
p.byte_swap = false;
|
p.byte_swap = false;
|
||||||
|
p.lock = SD_LOCK_NONE;
|
||||||
sd_set_clock(CLOCK_STOP);
|
sd_set_clock(CLOCK_STOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +41,17 @@ typedef enum {
|
|||||||
SD_ERROR_ACMD41_IO = 27,
|
SD_ERROR_ACMD41_IO = 27,
|
||||||
SD_ERROR_ACMD41_OCR = 28,
|
SD_ERROR_ACMD41_OCR = 28,
|
||||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||||
|
SD_ERROR_LOCKED = 30,
|
||||||
} sd_error_t;
|
} sd_error_t;
|
||||||
|
|
||||||
typedef sd_error_t sd_process_sectors_t (uint32_t address, uint32_t sector, uint32_t count);
|
typedef sd_error_t sd_process_sectors_t (uint32_t address, uint32_t sector, uint32_t count);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SD_LOCK_NONE,
|
||||||
|
SD_LOCK_N64,
|
||||||
|
SD_LOCK_USB,
|
||||||
|
} sd_lock_t;
|
||||||
|
|
||||||
|
|
||||||
sd_error_t sd_card_init (void);
|
sd_error_t sd_card_init (void);
|
||||||
void sd_card_deinit (void);
|
void sd_card_deinit (void);
|
||||||
@ -58,6 +65,10 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count);
|
|||||||
|
|
||||||
sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors);
|
sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors);
|
||||||
|
|
||||||
|
sd_error_t sd_get_lock (sd_lock_t lock);
|
||||||
|
sd_error_t sd_try_lock (sd_lock_t lock);
|
||||||
|
void sd_release_lock (sd_lock_t lock);
|
||||||
|
|
||||||
void sd_init (void);
|
void sd_init (void);
|
||||||
|
|
||||||
void sd_process (void);
|
void sd_process (void);
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
#include "fpga.h"
|
#include "fpga.h"
|
||||||
#include "hw.h"
|
#include "hw.h"
|
||||||
|
#include "led.h"
|
||||||
#include "rtc.h"
|
#include "rtc.h"
|
||||||
|
#include "sd.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include "update.h"
|
#include "update.h"
|
||||||
#include "usb.h"
|
#include "usb.h"
|
||||||
@ -15,7 +17,7 @@
|
|||||||
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
||||||
#define BOOTLOADER_LENGTH (1920 * 1024)
|
#define BOOTLOADER_LENGTH (1920 * 1024)
|
||||||
|
|
||||||
#define MEMORY_LENGTH (0x05002980UL)
|
#define MEMORY_LENGTH (0x05002C80UL)
|
||||||
|
|
||||||
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
||||||
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
||||||
@ -268,6 +270,7 @@ static void usb_rx_process (void) {
|
|||||||
case 'R':
|
case 'R':
|
||||||
cfg_reset_state();
|
cfg_reset_state();
|
||||||
cic_reset_parameters();
|
cic_reset_parameters();
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
break;
|
break;
|
||||||
@ -384,6 +387,119 @@ static void usb_rx_process (void) {
|
|||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'i': {
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
switch (p.rx_args[1]) {
|
||||||
|
case 0:
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
sd_card_deinit();
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
error = sd_try_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_card_init();
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (usb_validate_address_length(p.rx_args[0], SD_CARD_INFO_SIZE, true)) {
|
||||||
|
error = SD_ERROR_INVALID_ADDRESS;
|
||||||
|
} else {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
error = sd_card_get_info(p.rx_args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
error = sd_set_byte_swap(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
error = sd_set_byte_swap(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = SD_ERROR_INVALID_OPERATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 8;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
p.response_info.data[1] = sd_card_get_status();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's': {
|
||||||
|
uint32_t sector = 0;
|
||||||
|
if (!usb_rx_word(§or)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
if (p.rx_args[1] >= 0x800000) {
|
||||||
|
error = SD_ERROR_INVALID_ARGUMENT;
|
||||||
|
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||||
|
error = SD_ERROR_INVALID_ADDRESS;
|
||||||
|
} else {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_read_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 4;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'S': {
|
||||||
|
uint32_t sector = 0;
|
||||||
|
if (!usb_rx_word(§or)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
if (p.rx_args[1] >= 0x800000) {
|
||||||
|
error = SD_ERROR_INVALID_ARGUMENT;
|
||||||
|
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||||
|
error = SD_ERROR_INVALID_ADDRESS;
|
||||||
|
} else {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_write_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 4;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'D':
|
case 'D':
|
||||||
dd_set_block_ready(p.rx_args[0] == 0);
|
dd_set_block_ready(p.rx_args[0] == 0);
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
@ -635,5 +751,6 @@ void usb_process (void) {
|
|||||||
usb_tx_process();
|
usb_tx_process();
|
||||||
} else {
|
} else {
|
||||||
usb_flush_packet();
|
usb_flush_packet();
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,19 +72,20 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeback_save_to_sd (void) {
|
static void writeback_save_to_sd (void) {
|
||||||
save_type_t save;
|
|
||||||
uint32_t address;
|
uint32_t address;
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
|
|
||||||
save = writeback_get_address_length(&address, &length);
|
if (writeback_get_address_length(&address, &length) == SAVE_TYPE_NONE) {
|
||||||
if (save == SAVE_TYPE_NONE) {
|
|
||||||
writeback_disable();
|
writeback_disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sd_error_t error = sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors);
|
if (sd_get_lock(SD_LOCK_N64) != SD_OK) {
|
||||||
|
writeback_disable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (error != SD_OK) {
|
if (sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors) != SD_OK) {
|
||||||
writeback_disable();
|
writeback_disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
124
sw/deployer/Cargo.lock
generated
124
sw/deployer/Cargo.lock
generated
@ -124,7 +124,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -162,6 +162,26 @@ dependencies = [
|
|||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"log",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex",
|
||||||
|
"syn 2.0.77",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@ -218,13 +238,22 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.103"
|
version = "1.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
|
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -263,6 +292,17 @@ dependencies = [
|
|||||||
"windows-targets 0.52.5",
|
"windows-targets 0.52.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.8"
|
version = "4.5.8"
|
||||||
@ -303,7 +343,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -482,6 +522,12 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@ -633,7 +679,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -755,6 +801,16 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libudev"
|
name = "libudev"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -922,7 +978,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1007,6 +1063,16 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn 2.0.77",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.20+deprecated"
|
version = "0.5.20+deprecated"
|
||||||
@ -1038,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1219,10 +1285,18 @@ dependencies = [
|
|||||||
"ordered-multimap",
|
"ordered-multimap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sc64deployer"
|
name = "sc64deployer"
|
||||||
version = "2.19.0"
|
version = "2.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap-num",
|
"clap-num",
|
||||||
@ -1266,14 +1340,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.6"
|
version = "0.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -1308,6 +1382,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -1357,9 +1437,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.68"
|
version = "2.0.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1402,7 +1482,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1430,18 +1510,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.6"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.14"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1527,7 +1607,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1549,7 +1629,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -1738,9 +1818,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.13"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -6,6 +6,10 @@ authors = ["Polprzewodnikowy"]
|
|||||||
description = "SummerCart64 loader and control software"
|
description = "SummerCart64 loader and control software"
|
||||||
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
bindgen = "0.70.1"
|
||||||
|
cc = "1.1.18"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.8", features = ["derive"] }
|
clap = { version = "4.5.8", features = ["derive"] }
|
||||||
|
19
sw/deployer/build.rs
Normal file
19
sw/deployer/build.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.file("../bootloader/src/fatfs/ff.c")
|
||||||
|
.file("../bootloader/src/fatfs/ffsystem.c")
|
||||||
|
.file("../bootloader/src/fatfs/ffunicode.c")
|
||||||
|
.compile("fatfs");
|
||||||
|
|
||||||
|
bindgen::Builder::default()
|
||||||
|
.header("../bootloader/src/fatfs/ff.h")
|
||||||
|
.blocklist_function("get_fattime")
|
||||||
|
.generate()
|
||||||
|
.expect("Unable to generate FatFs bindings")
|
||||||
|
.write_to_file(out_dir.join("fatfs_bindings.rs"))
|
||||||
|
.expect("Unable to write FatFs bindings");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -11,12 +11,13 @@ use panic_message::panic_message;
|
|||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{stdin, stdout, Read, Write},
|
io::{stdin, stdout, Read, Write},
|
||||||
|
panic,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
process,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
{panic, process},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -57,6 +58,12 @@ enum Commands {
|
|||||||
/// Dump data from arbitrary location in SC64 memory space
|
/// Dump data from arbitrary location in SC64 memory space
|
||||||
Dump(DumpArgs),
|
Dump(DumpArgs),
|
||||||
|
|
||||||
|
/// Perform operations on the SD card
|
||||||
|
SD {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: SDCommands,
|
||||||
|
},
|
||||||
|
|
||||||
/// Print information about connected SC64 device
|
/// Print information about connected SC64 device
|
||||||
Info,
|
Info,
|
||||||
|
|
||||||
@ -202,6 +209,71 @@ struct DumpArgs {
|
|||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum SDCommands {
|
||||||
|
/// List a directory on the SD card
|
||||||
|
#[command(name = "ls")]
|
||||||
|
List {
|
||||||
|
/// Path to the directory
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Display a file or directory status
|
||||||
|
#[command(name = "stat")]
|
||||||
|
Stat {
|
||||||
|
/// Path to the file or directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Move or rename a file or directory
|
||||||
|
#[command(name = "mv")]
|
||||||
|
Move {
|
||||||
|
/// Path to the current file or directory
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the new file or directory
|
||||||
|
dst: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove a file or empty directory
|
||||||
|
#[command(name = "rm")]
|
||||||
|
Delete {
|
||||||
|
/// Path to the file or directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Create a new directory
|
||||||
|
#[command(name = "mkdir")]
|
||||||
|
CreateDirectory {
|
||||||
|
/// Path to the directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Download a file to the PC
|
||||||
|
#[command(name = "download")]
|
||||||
|
Download {
|
||||||
|
/// Path to the file on the SD card
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the file on the PC
|
||||||
|
dst: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Upload a file to the SD card
|
||||||
|
#[command(name = "upload")]
|
||||||
|
Upload {
|
||||||
|
/// Path to the file on the PC
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the file on the SD card
|
||||||
|
dst: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Format the SD card
|
||||||
|
#[command(name = "mkfs")]
|
||||||
|
Format,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum SetCommands {
|
enum SetCommands {
|
||||||
/// Synchronize real time clock (RTC) on the SC64 with local system time
|
/// Synchronize real time clock (RTC) on the SC64 with local system time
|
||||||
@ -318,6 +390,9 @@ fn main() {
|
|||||||
panic::set_hook(Box::new(|_| {}));
|
panic::set_hook(Box::new(|_| {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
colored::control::set_virtual_terminal(true).ok();
|
||||||
|
|
||||||
match panic::catch_unwind(|| handle_command(&cli.command, cli.port, cli.remote)) {
|
match panic::catch_unwind(|| handle_command(&cli.command, cli.port, cli.remote)) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(payload) => {
|
Err(payload) => {
|
||||||
@ -340,6 +415,7 @@ fn handle_command(command: &Commands, port: Option<String>, remote: Option<Strin
|
|||||||
Commands::_64DD(args) => handle_64dd_command(connection, args),
|
Commands::_64DD(args) => handle_64dd_command(connection, args),
|
||||||
Commands::Debug(args) => handle_debug_command(connection, args),
|
Commands::Debug(args) => handle_debug_command(connection, args),
|
||||||
Commands::Dump(args) => handle_dump_command(connection, args),
|
Commands::Dump(args) => handle_dump_command(connection, args),
|
||||||
|
Commands::SD { command } => handle_sd_command(connection, command),
|
||||||
Commands::Info => handle_info_command(connection),
|
Commands::Info => handle_info_command(connection),
|
||||||
Commands::Reset => handle_reset_command(connection),
|
Commands::Reset => handle_reset_command(connection),
|
||||||
Commands::Set { command } => handle_set_command(connection, command),
|
Commands::Set { command } => handle_set_command(connection, command),
|
||||||
@ -767,16 +843,167 @@ fn handle_dump_command(connection: Connection, args: &DumpArgs) -> Result<(), sc
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_sd_command(connection: Connection, command: &SDCommands) -> Result<(), sc64::Error> {
|
||||||
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
|
match sc64.init_sd_card()? {
|
||||||
|
sc64::SdCardResult::OK => {}
|
||||||
|
error => {
|
||||||
|
return Err(sc64::Error::new(
|
||||||
|
format!("Couldn't init the SD card: {error}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc64.is_console_powered_on()? {
|
||||||
|
println!(
|
||||||
|
"{}\n{}\n{}",
|
||||||
|
"========== [WARNING] ==========".bold().bright_yellow(),
|
||||||
|
"The console is powered on. To avoid potential data corruption it's strongly"
|
||||||
|
.bright_yellow(),
|
||||||
|
"recommended to access the SD card only when the console is powered off."
|
||||||
|
.bright_yellow()
|
||||||
|
);
|
||||||
|
let answer = prompt(format!("{}", "Continue anyways? [y/N] ".bold()));
|
||||||
|
if answer.to_ascii_lowercase() != "y" {
|
||||||
|
sc64.deinit_sd_card()?;
|
||||||
|
println!("{}", "SD card access aborted".red());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc64.reset_state()?;
|
||||||
|
|
||||||
|
let mut ff = sc64::ff::FatFs::new(sc64)?;
|
||||||
|
|
||||||
|
match command {
|
||||||
|
SDCommands::List { path } => {
|
||||||
|
for item in ff.list(path.clone().unwrap_or(PathBuf::from("/")))? {
|
||||||
|
let sc64::ff::Entry {
|
||||||
|
info,
|
||||||
|
datetime,
|
||||||
|
name,
|
||||||
|
} = item;
|
||||||
|
let name = match info {
|
||||||
|
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||||
|
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||||
|
};
|
||||||
|
println!("{info} {datetime} | {}", name.bold());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDCommands::Stat { path } => {
|
||||||
|
let sc64::ff::Entry {
|
||||||
|
info,
|
||||||
|
datetime,
|
||||||
|
name,
|
||||||
|
} = ff.stat(path)?;
|
||||||
|
let name = match info {
|
||||||
|
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||||
|
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||||
|
};
|
||||||
|
println!("{info} {datetime} | {}", name.bold());
|
||||||
|
}
|
||||||
|
SDCommands::Move { src, dst } => {
|
||||||
|
ff.rename(src, dst)?;
|
||||||
|
println!(
|
||||||
|
"Successfully moved {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::Delete { path } => {
|
||||||
|
ff.delete(path)?;
|
||||||
|
println!(
|
||||||
|
"Successfully deleted {}",
|
||||||
|
path.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::CreateDirectory { path } => {
|
||||||
|
ff.mkdir(path)?;
|
||||||
|
println!(
|
||||||
|
"Successfully created {}",
|
||||||
|
path.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::Download { src, dst } => {
|
||||||
|
let dst = &dst.clone().unwrap_or(
|
||||||
|
src.file_name()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||||
|
);
|
||||||
|
let mut src_file = ff.open(src)?;
|
||||||
|
let mut dst_file = std::fs::File::create(dst)?;
|
||||||
|
let mut buffer = vec![0; 128 * 1024];
|
||||||
|
log_wait(
|
||||||
|
format!(
|
||||||
|
"Downloading {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
),
|
||||||
|
|| loop {
|
||||||
|
match src_file.read(&mut buffer)? {
|
||||||
|
0 => return Ok(()),
|
||||||
|
bytes => {
|
||||||
|
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SDCommands::Upload { src, dst } => {
|
||||||
|
let dst = &dst.clone().unwrap_or(
|
||||||
|
src.file_name()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||||
|
);
|
||||||
|
let mut src_file = std::fs::File::open(src)?;
|
||||||
|
let mut dst_file = ff.create(dst)?;
|
||||||
|
let mut buffer = vec![0; 128 * 1024];
|
||||||
|
log_wait(
|
||||||
|
format!(
|
||||||
|
"Uploading {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
),
|
||||||
|
|| loop {
|
||||||
|
match src_file.read(&mut buffer)? {
|
||||||
|
0 => return Ok(()),
|
||||||
|
bytes => {
|
||||||
|
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SDCommands::Format => {
|
||||||
|
let answer = prompt(format!(
|
||||||
|
"{}",
|
||||||
|
"Do you really want to format the SD card? [y/N] ".bold()
|
||||||
|
));
|
||||||
|
if answer.to_ascii_lowercase() != "y" {
|
||||||
|
println!("{}", "Format operation aborted".red());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
log_wait(format!("Formatting the SD card"), || ff.mkfs())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||||
let mut sc64 = init_sc64(connection, true)?;
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
let (major, minor, revision) = sc64.check_firmware_version()?;
|
let (major, minor, revision) = sc64.check_firmware_version()?;
|
||||||
let state = sc64.get_device_state()?;
|
let state = sc64.get_device_state()?;
|
||||||
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
|
|
||||||
|
|
||||||
println!("{}", "SummerCart64 state information:".bold());
|
println!("{}", "SummerCart64 state information:".bold());
|
||||||
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
||||||
println!(" RTC datetime: {}", datetime);
|
println!(" RTC datetime: {}", state.datetime);
|
||||||
println!(" Boot mode: {}", state.boot_mode);
|
println!(" Boot mode: {}", state.boot_mode);
|
||||||
println!(" Save type: {}", state.save_type);
|
println!(" Save type: {}", state.save_type);
|
||||||
println!(" CIC seed: {}", state.cic_seed);
|
println!(" CIC seed: {}", state.cic_seed);
|
||||||
@ -793,6 +1020,7 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
|||||||
println!(" Button state: {}", state.button_state);
|
println!(" Button state: {}", state.button_state);
|
||||||
println!(" LED blink: {}", state.led_enable);
|
println!(" LED blink: {}", state.led_enable);
|
||||||
println!(" IS-Viewer 64: {}", state.isviewer);
|
println!(" IS-Viewer 64: {}", state.isviewer);
|
||||||
|
println!(" SD card status: {}", state.sd_card_status);
|
||||||
println!("{}", "SummerCart64 diagnostic information:".bold());
|
println!("{}", "SummerCart64 diagnostic information:".bold());
|
||||||
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
|
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
|
||||||
println!(
|
println!(
|
||||||
@ -921,19 +1149,30 @@ fn handle_firmware_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> {
|
fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||||
let mut sc64 = init_sc64(connection, false)?;
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
|
sc64.reset_state()?;
|
||||||
|
|
||||||
println!("{}: USB", "[SC64 Tests]".bold());
|
println!("{}: USB", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
print!(" Performing USB read speed test... ");
|
print!(" Performing USB read speed test... ");
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
let read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
let usb_read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
||||||
println!("{}", format!("{read_speed:.2} MiB/s",).bright_green());
|
println!("{}", format!("{usb_read_speed:.2} MiB/s",).bright_green());
|
||||||
|
|
||||||
print!(" Performing USB write speed test... ");
|
print!(" Performing USB write speed test... ");
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
let write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
let usb_write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
||||||
println!("{}", format!("{write_speed:.2} MiB/s",).bright_green());
|
println!("{}", format!("{usb_write_speed:.2} MiB/s",).bright_green());
|
||||||
|
|
||||||
|
println!("{}: SD card", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
|
print!(" Performing SD card read speed test... ");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
match sc64.test_sd_card() {
|
||||||
|
Ok(sd_read_speed) => println!("{}", format!("{sd_read_speed:.2} MiB/s",).bright_green()),
|
||||||
|
Err(result) => println!("{}", format!("error! {result}").bright_red()),
|
||||||
|
}
|
||||||
|
|
||||||
println!("{}: SDRAM (pattern)", "[SC64 Tests]".bold());
|
println!("{}: SDRAM (pattern)", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
|
@ -26,3 +26,9 @@ impl From<std::io::Error> for Error {
|
|||||||
Error::new(format!("IO error: {}", value).as_str())
|
Error::new(format!("IO error: {}", value).as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<super::ff::Error> for Error {
|
||||||
|
fn from(value: super::ff::Error) -> Self {
|
||||||
|
Error::new(format!("FatFs error: {}", value).as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
631
sw/deployer/src/sc64/ff.rs
Normal file
631
sw/deployer/src/sc64/ff.rs
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
use super::{SdCardResult, SC64, SD_CARD_SECTOR_SIZE};
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
|
mod fatfs {
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/fatfs_bindings.rs"));
|
||||||
|
|
||||||
|
pub type DSTATUS = BYTE;
|
||||||
|
pub const DSTATUS_STA_OK: DSTATUS = 0;
|
||||||
|
pub const DSTATUS_STA_NOINIT: DSTATUS = 1 << 0;
|
||||||
|
pub const DSTATUS_STA_NODISK: DSTATUS = 1 << 2;
|
||||||
|
pub const DSTATUS_STA_PROTECT: DSTATUS = 1 << 3;
|
||||||
|
|
||||||
|
pub type DRESULT = std::os::raw::c_uint;
|
||||||
|
pub const DRESULT_RES_OK: DRESULT = 0;
|
||||||
|
pub const DRESULT_RES_ERROR: DRESULT = 1;
|
||||||
|
pub const DRESULT_RES_WRPRT: DRESULT = 2;
|
||||||
|
pub const DRESULT_RES_NOTRDY: DRESULT = 3;
|
||||||
|
pub const DRESULT_RES_PARERR: DRESULT = 4;
|
||||||
|
|
||||||
|
pub const CTRL_SYNC: BYTE = 0;
|
||||||
|
pub const GET_SECTOR_COUNT: BYTE = 1;
|
||||||
|
pub const GET_SECTOR_SIZE: BYTE = 2;
|
||||||
|
pub const GET_BLOCK_SIZE: BYTE = 3;
|
||||||
|
pub const CTRL_TRIM: BYTE = 4;
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
DiskErr,
|
||||||
|
IntErr,
|
||||||
|
NotReady,
|
||||||
|
NoFile,
|
||||||
|
NoPath,
|
||||||
|
InvalidName,
|
||||||
|
Denied,
|
||||||
|
Exist,
|
||||||
|
InvalidObject,
|
||||||
|
WriteProtected,
|
||||||
|
InvalidDrive,
|
||||||
|
NotEnabled,
|
||||||
|
NoFilesystem,
|
||||||
|
MkfsAborted,
|
||||||
|
Timeout,
|
||||||
|
Locked,
|
||||||
|
NotEnoughCore,
|
||||||
|
TooManyOpenFiles,
|
||||||
|
InvalidParameter,
|
||||||
|
DriverInstalled,
|
||||||
|
DriverNotInstalled,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::DiskErr => "A hard error occurred in the low level disk I/O layer",
|
||||||
|
Self::IntErr => "Assertion failed",
|
||||||
|
Self::NotReady => "The physical drive cannot work",
|
||||||
|
Self::NoFile => "Could not find the file",
|
||||||
|
Self::NoPath => "Could not find the path",
|
||||||
|
Self::InvalidName => "The path name format is invalid",
|
||||||
|
Self::Denied => "Access denied due to prohibited access or directory full",
|
||||||
|
Self::Exist => "Access denied due to prohibited access",
|
||||||
|
Self::InvalidObject => "The file/directory object is invalid",
|
||||||
|
Self::WriteProtected => "The physical drive is write protected",
|
||||||
|
Self::InvalidDrive => "The logical drive number is invalid",
|
||||||
|
Self::NotEnabled => "The volume has no work area",
|
||||||
|
Self::NoFilesystem => "There is no valid FAT volume",
|
||||||
|
Self::MkfsAborted => "The f_mkfs() aborted due to any problem",
|
||||||
|
Self::Timeout => "Could not get a grant to access the volume within defined period",
|
||||||
|
Self::Locked => "The operation is rejected according to the file sharing policy",
|
||||||
|
Self::NotEnoughCore => "LFN working buffer could not be allocated",
|
||||||
|
Self::TooManyOpenFiles => "Number of open files > FF_FS_LOCK",
|
||||||
|
Self::InvalidParameter => "Given parameter is invalid",
|
||||||
|
Self::DriverInstalled => "FatFs driver is already installed",
|
||||||
|
Self::DriverNotInstalled => "FatFs driver is not installed",
|
||||||
|
Self::Unknown => "Unknown error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FRESULT> for Error {
|
||||||
|
fn from(value: FRESULT) -> Self {
|
||||||
|
match value {
|
||||||
|
FRESULT_FR_DISK_ERR => Error::DiskErr,
|
||||||
|
FRESULT_FR_INT_ERR => Error::IntErr,
|
||||||
|
FRESULT_FR_NOT_READY => Error::NotReady,
|
||||||
|
FRESULT_FR_NO_FILE => Error::NoFile,
|
||||||
|
FRESULT_FR_NO_PATH => Error::NoPath,
|
||||||
|
FRESULT_FR_INVALID_NAME => Error::InvalidName,
|
||||||
|
FRESULT_FR_DENIED => Error::Denied,
|
||||||
|
FRESULT_FR_EXIST => Error::Exist,
|
||||||
|
FRESULT_FR_INVALID_OBJECT => Error::InvalidObject,
|
||||||
|
FRESULT_FR_WRITE_PROTECTED => Error::WriteProtected,
|
||||||
|
FRESULT_FR_INVALID_DRIVE => Error::InvalidDrive,
|
||||||
|
FRESULT_FR_NOT_ENABLED => Error::NotEnabled,
|
||||||
|
FRESULT_FR_NO_FILESYSTEM => Error::NoFilesystem,
|
||||||
|
FRESULT_FR_MKFS_ABORTED => Error::MkfsAborted,
|
||||||
|
FRESULT_FR_TIMEOUT => Error::Timeout,
|
||||||
|
FRESULT_FR_LOCKED => Error::Locked,
|
||||||
|
FRESULT_FR_NOT_ENOUGH_CORE => Error::NotEnoughCore,
|
||||||
|
FRESULT_FR_TOO_MANY_OPEN_FILES => Error::TooManyOpenFiles,
|
||||||
|
FRESULT_FR_INVALID_PARAMETER => Error::InvalidParameter,
|
||||||
|
_ => Error::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path<P: AsRef<std::path::Path>>(path: P) -> Result<std::ffi::CString, Error> {
|
||||||
|
match path.as_ref().to_str() {
|
||||||
|
Some(path) => Ok(std::ffi::CString::new(path).map_err(|_| Error::InvalidParameter)?),
|
||||||
|
None => Err(Error::InvalidParameter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Error = fatfs::Error;
|
||||||
|
|
||||||
|
static mut DRIVER: std::sync::Mutex<Option<Box<dyn FFDriver>>> = std::sync::Mutex::new(None);
|
||||||
|
|
||||||
|
fn install_driver(driver: impl FFDriver + 'static) -> Result<(), Error> {
|
||||||
|
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||||
|
if d.is_some() {
|
||||||
|
return Err(Error::DriverInstalled);
|
||||||
|
}
|
||||||
|
d.replace(Box::new(driver));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall_driver() -> Result<(), Error> {
|
||||||
|
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||||
|
if d.is_none() {
|
||||||
|
return Err(Error::DriverNotInstalled);
|
||||||
|
}
|
||||||
|
d.take().unwrap().deinit();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FatFs {
|
||||||
|
fs: Box<fatfs::FATFS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FatFs {
|
||||||
|
pub fn new(driver: impl FFDriver + 'static) -> Result<Self, Error> {
|
||||||
|
install_driver(driver)?;
|
||||||
|
let mut ff = Self {
|
||||||
|
fs: Box::new(unsafe { std::mem::zeroed() }),
|
||||||
|
};
|
||||||
|
ff.mount(false)?;
|
||||||
|
Ok(ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount(&mut self, force: bool) -> Result<(), Error> {
|
||||||
|
let opt = if force { 1 } else { 0 };
|
||||||
|
match unsafe { fatfs::f_mount(&mut *self.fs, fatfs::path("")?.as_ptr(), opt) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmount(&mut self) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_mount(std::ptr::null_mut(), fatfs::path("")?.as_ptr(), 0) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||||
|
File::open(
|
||||||
|
path,
|
||||||
|
fatfs::FA_OPEN_EXISTING | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||||
|
File::open(
|
||||||
|
path,
|
||||||
|
fatfs::FA_CREATE_ALWAYS | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stat<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Entry, Error> {
|
||||||
|
let mut fno = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_stat(fatfs::path(path)?.as_ptr(), &mut fno) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(fno.into()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opendir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Directory, Error> {
|
||||||
|
Directory::open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Vec<Entry>, Error> {
|
||||||
|
let mut dir = self.opendir(path)?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
while let Some(entry) = dir.read()? {
|
||||||
|
list.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort_by_key(|k| (k.info, k.name.to_lowercase()));
|
||||||
|
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkdir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_mkdir(fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_unlink(fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rename<P: AsRef<std::path::Path>>(&mut self, old: P, new: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_rename(fatfs::path(old)?.as_ptr(), fatfs::path(new)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkfs(&mut self) -> Result<(), Error> {
|
||||||
|
let mut work = [0u8; 16 * 1024];
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_mkfs(
|
||||||
|
fatfs::path("")?.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
work.as_mut_ptr().cast(),
|
||||||
|
size_of_val(&work) as u32,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FatFs {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.unmount().ok();
|
||||||
|
uninstall_driver().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IOCtl {
|
||||||
|
Sync,
|
||||||
|
GetSectorCount(fatfs::LBA_t),
|
||||||
|
GetSectorSize(fatfs::WORD),
|
||||||
|
GetBlockSize(fatfs::DWORD),
|
||||||
|
Trim,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FFDriver {
|
||||||
|
fn init(&mut self) -> fatfs::DSTATUS;
|
||||||
|
fn deinit(&mut self);
|
||||||
|
fn status(&mut self) -> fatfs::DSTATUS;
|
||||||
|
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||||
|
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||||
|
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FFDriver for SC64 {
|
||||||
|
fn init(&mut self) -> fatfs::DSTATUS {
|
||||||
|
if let Ok(SdCardResult::OK) = self.init_sd_card() {
|
||||||
|
return fatfs::DSTATUS_STA_OK;
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(&mut self) {
|
||||||
|
self.deinit_sd_card().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&mut self) -> fatfs::DSTATUS {
|
||||||
|
if let Ok(status) = self.get_sd_card_status() {
|
||||||
|
if status.card_initialized {
|
||||||
|
return fatfs::DSTATUS_STA_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
|
||||||
|
if let Ok(SdCardResult::OK) = self.read_sd_card(buffer, sector) {
|
||||||
|
return fatfs::DRESULT_RES_OK;
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
|
||||||
|
if let Ok(SdCardResult::OK) = self.write_sd_card(buffer, sector) {
|
||||||
|
return fatfs::DRESULT_RES_OK;
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT {
|
||||||
|
match ioctl {
|
||||||
|
IOCtl::Sync => {}
|
||||||
|
IOCtl::GetSectorCount(_) => {
|
||||||
|
match self.get_sd_card_info() {
|
||||||
|
Ok(info) => *ioctl = IOCtl::GetSectorCount(info.sectors as fatfs::LBA_t),
|
||||||
|
Err(_) => return fatfs::DRESULT_RES_ERROR,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
IOCtl::GetSectorSize(_) => {
|
||||||
|
*ioctl = IOCtl::GetSectorSize(SD_CARD_SECTOR_SIZE as fatfs::WORD)
|
||||||
|
}
|
||||||
|
IOCtl::GetBlockSize(_) => {
|
||||||
|
*ioctl = IOCtl::GetBlockSize(1);
|
||||||
|
}
|
||||||
|
IOCtl::Trim => {}
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_status(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DSTATUS_STA_NOINIT;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.status();
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_initialize(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DSTATUS_STA_NOINIT;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.init();
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_read(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
buff: *mut fatfs::BYTE,
|
||||||
|
sector: fatfs::LBA_t,
|
||||||
|
count: fatfs::UINT,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.read(
|
||||||
|
&mut *std::ptr::slice_from_raw_parts_mut(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||||
|
sector,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_write(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
buff: *mut fatfs::BYTE,
|
||||||
|
sector: fatfs::LBA_t,
|
||||||
|
count: fatfs::UINT,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.write(
|
||||||
|
&*std::ptr::slice_from_raw_parts(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||||
|
sector,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_ioctl(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
cmd: fatfs::BYTE,
|
||||||
|
buff: *mut std::os::raw::c_void,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
let mut ioctl = match cmd {
|
||||||
|
fatfs::CTRL_SYNC => IOCtl::Sync,
|
||||||
|
fatfs::GET_SECTOR_COUNT => IOCtl::GetSectorCount(0),
|
||||||
|
fatfs::GET_SECTOR_SIZE => IOCtl::GetSectorSize(0),
|
||||||
|
fatfs::GET_BLOCK_SIZE => IOCtl::GetBlockSize(0),
|
||||||
|
fatfs::CTRL_TRIM => IOCtl::Trim,
|
||||||
|
_ => return fatfs::DRESULT_RES_PARERR,
|
||||||
|
};
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
let result = d.ioctl(&mut ioctl);
|
||||||
|
if result == fatfs::DRESULT_RES_OK {
|
||||||
|
match ioctl {
|
||||||
|
IOCtl::GetSectorCount(count) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(count).cast(), size_of_val(&count))
|
||||||
|
}
|
||||||
|
IOCtl::GetSectorSize(size) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||||
|
}
|
||||||
|
IOCtl::GetBlockSize(size) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn get_fattime() -> fatfs::DWORD {
|
||||||
|
let now = chrono::Local::now();
|
||||||
|
let year = now.year() as u32;
|
||||||
|
let month = now.month();
|
||||||
|
let day = now.day();
|
||||||
|
let hour = now.hour();
|
||||||
|
let minute = now.minute();
|
||||||
|
let second = now.second();
|
||||||
|
((year - 1980) << 25)
|
||||||
|
| (month << 21)
|
||||||
|
| (day << 16)
|
||||||
|
| (hour << 11)
|
||||||
|
| (minute << 5)
|
||||||
|
| (second << 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub info: EntryInfo,
|
||||||
|
pub name: String,
|
||||||
|
pub datetime: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Ord)]
|
||||||
|
pub enum EntryInfo {
|
||||||
|
Directory,
|
||||||
|
File { size: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for EntryInfo {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(match (self, other) {
|
||||||
|
(EntryInfo::Directory, EntryInfo::File { size: _ }) => std::cmp::Ordering::Less,
|
||||||
|
(EntryInfo::File { size: _ }, EntryInfo::Directory) => std::cmp::Ordering::Greater,
|
||||||
|
_ => std::cmp::Ordering::Equal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EntryInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Ok(match self {
|
||||||
|
EntryInfo::Directory => f.write_fmt(format_args!("d ----",))?,
|
||||||
|
EntryInfo::File { size } => {
|
||||||
|
const UNITS: [&str; 4] = ["K", "M", "G", "T"];
|
||||||
|
let mut reduced_size = *size as f64;
|
||||||
|
let mut reduced_unit = "B";
|
||||||
|
for unit in UNITS {
|
||||||
|
if reduced_size >= 1000.0 {
|
||||||
|
reduced_size /= 1024.0;
|
||||||
|
reduced_unit = unit;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let formatted_size = if *size >= 1000 && reduced_size <= 9.9 {
|
||||||
|
format!("{:>1.1}{}", reduced_size, reduced_unit)
|
||||||
|
} else {
|
||||||
|
format!("{:>3.0}{}", reduced_size, reduced_unit)
|
||||||
|
};
|
||||||
|
f.write_fmt(format_args!("f {formatted_size}"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fatfs::FILINFO> for Entry {
|
||||||
|
fn from(value: fatfs::FILINFO) -> Self {
|
||||||
|
let name = unsafe { std::ffi::CStr::from_ptr(value.fname.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let datetime = chrono::NaiveDateTime::new(
|
||||||
|
chrono::NaiveDate::from_ymd_opt(
|
||||||
|
(1980 + ((value.fdate >> 9) & 0x7F)).into(),
|
||||||
|
((value.fdate >> 5) & 0xF).into(),
|
||||||
|
(value.fdate & 0x1F).into(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
chrono::NaiveTime::from_hms_opt(
|
||||||
|
((value.ftime >> 11) & 0x1F).into(),
|
||||||
|
((value.ftime >> 5) & 0x3F).into(),
|
||||||
|
((value.ftime & 0x1F) * 2).into(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
let info = if (value.fattrib as u32 & fatfs::AM_DIR) == 0 {
|
||||||
|
EntryInfo::File { size: value.fsize }
|
||||||
|
} else {
|
||||||
|
EntryInfo::Directory
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
datetime,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Directory {
|
||||||
|
dir: fatfs::DIR,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directory {
|
||||||
|
fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
|
||||||
|
let mut dir = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_opendir(&mut dir, fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(Self { dir }),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Result<Option<Entry>, Error> {
|
||||||
|
let mut fno = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_readdir(&mut self.dir, &mut fno) } {
|
||||||
|
fatfs::FRESULT_FR_OK => {
|
||||||
|
if fno.fname[0] == 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(fno.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Directory {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { fatfs::f_closedir(&mut self.dir) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct File {
|
||||||
|
fil: fatfs::FIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
fn open<P: AsRef<std::path::Path>>(path: P, mode: u32) -> Result<File, Error> {
|
||||||
|
let mut fil = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_open(&mut fil, fatfs::path(path)?.as_ptr(), mode as u8) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(File { fil }),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Read for File {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let mut bytes_read = 0;
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_read(
|
||||||
|
&mut self.fil,
|
||||||
|
buf.as_mut_ptr().cast(),
|
||||||
|
buf.len() as fatfs::UINT,
|
||||||
|
&mut bytes_read,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(bytes_read as usize),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for File {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let mut bytes_written = 0;
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_write(
|
||||||
|
&mut self.fil,
|
||||||
|
buf.as_ptr().cast(),
|
||||||
|
buf.len() as fatfs::UINT,
|
||||||
|
&mut bytes_written,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(bytes_written as usize),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
match unsafe { fatfs::f_sync(&mut self.fil) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Seek for File {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
let ofs = match pos {
|
||||||
|
std::io::SeekFrom::Current(offset) => self.fil.fptr.saturating_add_signed(offset),
|
||||||
|
std::io::SeekFrom::Start(offset) => offset,
|
||||||
|
std::io::SeekFrom::End(offset) => self.fil.obj.objsize.saturating_add_signed(offset),
|
||||||
|
};
|
||||||
|
match unsafe { fatfs::f_lseek(&mut self.fil, ofs) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(self.fil.fptr),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for File {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { fatfs::f_close(&mut self.fil) };
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
mod cic;
|
mod cic;
|
||||||
mod error;
|
mod error;
|
||||||
|
pub mod ff;
|
||||||
pub mod firmware;
|
pub mod firmware;
|
||||||
mod ftdi;
|
mod ftdi;
|
||||||
mod link;
|
mod link;
|
||||||
@ -13,10 +14,11 @@ pub use self::{
|
|||||||
link::list_local_devices,
|
link::list_local_devices,
|
||||||
server::ServerEvent,
|
server::ServerEvent,
|
||||||
types::{
|
types::{
|
||||||
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState,
|
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, CicStep, DataPacket, DdDiskState,
|
||||||
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
|
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
|
||||||
FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
|
FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
|
||||||
SaveWriteback, SpeedTestDirection, Switch, TvType,
|
SaveWriteback, SdCardInfo, SdCardOpPacket, SdCardResult, SdCardStatus, SpeedTestDirection,
|
||||||
|
Switch, TvType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,7 +27,8 @@ use self::{
|
|||||||
link::Link,
|
link::Link,
|
||||||
time::{convert_from_datetime, convert_to_datetime},
|
time::{convert_from_datetime, convert_to_datetime},
|
||||||
types::{
|
types::{
|
||||||
get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus,
|
get_config, get_setting, Config, ConfigId, FirmwareStatus, SdCardOp, Setting, SettingId,
|
||||||
|
UpdateStatus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
@ -58,6 +61,7 @@ pub struct DeviceState {
|
|||||||
pub button_mode: ButtonMode,
|
pub button_mode: ButtonMode,
|
||||||
pub rom_extended_enable: Switch,
|
pub rom_extended_enable: Switch,
|
||||||
pub led_enable: Switch,
|
pub led_enable: Switch,
|
||||||
|
pub sd_card_status: SdCardStatus,
|
||||||
pub datetime: NaiveDateTime,
|
pub datetime: NaiveDateTime,
|
||||||
pub fpga_debug_data: FpgaDebugData,
|
pub fpga_debug_data: FpgaDebugData,
|
||||||
pub diagnostic_data: DiagnosticData,
|
pub diagnostic_data: DiagnosticData,
|
||||||
@ -94,13 +98,18 @@ const SRAM_1M_LENGTH: usize = 128 * 1024;
|
|||||||
|
|
||||||
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
||||||
|
|
||||||
|
const SD_CARD_BUFFER_ADDRESS: u32 = 0x03FE_0000; // Arbitrary offset in SDRAM memory
|
||||||
|
const SD_CARD_BUFFER_LENGTH: usize = 128 * 1024; // Arbitrary length in SDRAM memory
|
||||||
|
|
||||||
|
pub const SD_CARD_SECTOR_SIZE: usize = 512;
|
||||||
|
|
||||||
const FIRMWARE_ADDRESS_SDRAM: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
|
const FIRMWARE_ADDRESS_SDRAM: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
|
||||||
const FIRMWARE_ADDRESS_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
|
const FIRMWARE_ADDRESS_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
|
||||||
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
|
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
|
||||||
|
|
||||||
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
|
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
|
||||||
|
|
||||||
pub const MEMORY_LENGTH: usize = 0x0500_2980;
|
pub const MEMORY_LENGTH: usize = 0x0500_2C80;
|
||||||
|
|
||||||
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
||||||
|
|
||||||
@ -235,6 +244,53 @@ impl SC64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_operation(&mut self, op: SdCardOp) -> Result<SdCardOpPacket, Error> {
|
||||||
|
let data = self
|
||||||
|
.link
|
||||||
|
.execute_command_raw(b'i', op.into(), &[], false, true)?;
|
||||||
|
if data.len() != 8 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Invalid data length received for SD card operation command",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(SdCardOpPacket {
|
||||||
|
result: data.clone().try_into()?,
|
||||||
|
status: data.clone().try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_read(
|
||||||
|
&mut self,
|
||||||
|
address: u32,
|
||||||
|
sector: u32,
|
||||||
|
count: u32,
|
||||||
|
) -> Result<SdCardResult, Error> {
|
||||||
|
let data = self.link.execute_command_raw(
|
||||||
|
b's',
|
||||||
|
[address, count],
|
||||||
|
§or.to_be_bytes(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
Ok(data.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_write(
|
||||||
|
&mut self,
|
||||||
|
address: u32,
|
||||||
|
sector: u32,
|
||||||
|
count: u32,
|
||||||
|
) -> Result<SdCardResult, Error> {
|
||||||
|
let data = self.link.execute_command_raw(
|
||||||
|
b'S',
|
||||||
|
[address, count],
|
||||||
|
§or.to_be_bytes(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
Ok(data.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
||||||
self.link.execute_command(b'D', [error as u32, 0], &[])?;
|
self.link.execute_command(b'D', [error as u32, 0], &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -489,6 +545,7 @@ impl SC64 {
|
|||||||
button_mode: get_config!(self, ButtonMode)?,
|
button_mode: get_config!(self, ButtonMode)?,
|
||||||
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
|
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
|
||||||
led_enable: get_setting!(self, LedEnable)?,
|
led_enable: get_setting!(self, LedEnable)?,
|
||||||
|
sd_card_status: self.get_sd_card_status()?,
|
||||||
datetime: self.get_datetime()?,
|
datetime: self.get_datetime()?,
|
||||||
fpga_debug_data: self.command_fpga_debug_data_get()?,
|
fpga_debug_data: self.command_fpga_debug_data_get()?,
|
||||||
diagnostic_data: self.command_diagnostic_data_get()?,
|
diagnostic_data: self.command_diagnostic_data_get()?,
|
||||||
@ -606,6 +663,83 @@ impl SC64 {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::Init)
|
||||||
|
.map(|packet| packet.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::Deinit)
|
||||||
|
.map(|packet| packet.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sd_card_status(&mut self) -> Result<SdCardStatus, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::GetStatus)
|
||||||
|
.map(|reply| reply.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sd_card_info(&mut self) -> Result<SdCardInfo, Error> {
|
||||||
|
const SD_CARD_INFO_BUFFER_ADDRESS: u32 = 0x0500_2BE0;
|
||||||
|
let info =
|
||||||
|
match self.command_sd_card_operation(SdCardOp::GetInfo(SD_CARD_INFO_BUFFER_ADDRESS))? {
|
||||||
|
SdCardOpPacket {
|
||||||
|
result: SdCardResult::OK,
|
||||||
|
status: _,
|
||||||
|
} => self.command_memory_read(SD_CARD_INFO_BUFFER_ADDRESS, 32)?,
|
||||||
|
packet => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Couldn't get SD card info registers: {}", packet.result).as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(info.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_sd_card(&mut self, data: &mut [u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||||
|
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"SD card read length not aligned to the sector size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_sector = sector;
|
||||||
|
|
||||||
|
for mut chunk in data.chunks_mut(SD_CARD_BUFFER_LENGTH) {
|
||||||
|
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||||
|
match self.command_sd_card_read(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => return Ok(result),
|
||||||
|
}
|
||||||
|
let data = self.command_memory_read(SD_CARD_BUFFER_ADDRESS, chunk.len())?;
|
||||||
|
chunk.write_all(&data)?;
|
||||||
|
current_sector += sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SdCardResult::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_sd_card(&mut self, data: &[u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||||
|
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"SD card write length not aligned to the sector size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_sector = sector;
|
||||||
|
|
||||||
|
for chunk in data.chunks(SD_CARD_BUFFER_LENGTH) {
|
||||||
|
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||||
|
self.command_memory_write(SD_CARD_BUFFER_ADDRESS, chunk)?;
|
||||||
|
match self.command_sd_card_write(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => return Ok(result),
|
||||||
|
}
|
||||||
|
current_sector += sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SdCardResult::OK)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_device(&mut self) -> Result<(), Error> {
|
pub fn check_device(&mut self) -> Result<(), Error> {
|
||||||
let identifier = self.command_identifier_get().map_err(|e| {
|
let identifier = self.command_identifier_get().map_err(|e| {
|
||||||
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
|
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
|
||||||
@ -634,6 +768,14 @@ impl SC64 {
|
|||||||
self.command_state_reset()
|
self.command_state_reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_console_powered_on(&mut self) -> Result<bool, Error> {
|
||||||
|
let debug_data = self.command_fpga_debug_data_get()?;
|
||||||
|
Ok(match debug_data.cic_step {
|
||||||
|
CicStep::Unavailable | CicStep::PowerOff => false,
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
|
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
self.command_state_reset()?;
|
self.command_state_reset()?;
|
||||||
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS_SDRAM)?;
|
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS_SDRAM)?;
|
||||||
@ -738,6 +880,46 @@ impl SC64 {
|
|||||||
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_sd_card(&mut self) -> Result<f64, Error> {
|
||||||
|
const TEST_LENGTH: usize = 4 * 1024 * 1024;
|
||||||
|
const MIB_DIVIDER: f64 = 1024.0 * 1024.0;
|
||||||
|
|
||||||
|
let mut data = vec![0x00; TEST_LENGTH];
|
||||||
|
|
||||||
|
match self.init_sd_card()? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Init SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = std::time::Instant::now();
|
||||||
|
|
||||||
|
match self.read_sd_card(&mut data, 0)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Read SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = time.elapsed();
|
||||||
|
|
||||||
|
match self.deinit_sd_card()? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Deinit SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn test_sdram_pattern(
|
pub fn test_sdram_pattern(
|
||||||
&mut self,
|
&mut self,
|
||||||
pattern: MemoryTestPattern,
|
pattern: MemoryTestPattern,
|
||||||
|
@ -737,6 +737,241 @@ impl TryFrom<Vec<u8>> for DiskPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SdCardOp {
|
||||||
|
Deinit,
|
||||||
|
Init,
|
||||||
|
GetStatus,
|
||||||
|
GetInfo(u32),
|
||||||
|
ByteSwapOn,
|
||||||
|
ByteSwapOff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SdCardOp> for [u32; 2] {
|
||||||
|
fn from(value: SdCardOp) -> Self {
|
||||||
|
match value {
|
||||||
|
SdCardOp::Deinit => [0, 0],
|
||||||
|
SdCardOp::Init => [0, 1],
|
||||||
|
SdCardOp::GetStatus => [0, 2],
|
||||||
|
SdCardOp::GetInfo(address) => [address, 3],
|
||||||
|
SdCardOp::ByteSwapOn => [0, 4],
|
||||||
|
SdCardOp::ByteSwapOff => [0, 5],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SdCardResult {
|
||||||
|
OK,
|
||||||
|
NoCardInSlot,
|
||||||
|
NotInitialized,
|
||||||
|
InvalidArgument,
|
||||||
|
InvalidAddress,
|
||||||
|
InvalidOperation,
|
||||||
|
Cmd2IO,
|
||||||
|
Cmd3IO,
|
||||||
|
Cmd6CheckIO,
|
||||||
|
Cmd6CheckCRC,
|
||||||
|
Cmd6CheckTimeout,
|
||||||
|
Cmd6CheckResponse,
|
||||||
|
Cmd6SwitchIO,
|
||||||
|
Cmd6SwitchCRC,
|
||||||
|
Cmd6SwitchTimeout,
|
||||||
|
Cmd6SwitchResponse,
|
||||||
|
Cmd7IO,
|
||||||
|
Cmd8IO,
|
||||||
|
Cmd9IO,
|
||||||
|
Cmd10IO,
|
||||||
|
Cmd18IO,
|
||||||
|
Cmd18CRC,
|
||||||
|
Cmd18Timeout,
|
||||||
|
Cmd25IO,
|
||||||
|
Cmd25CRC,
|
||||||
|
Cmd25Timeout,
|
||||||
|
Acmd6IO,
|
||||||
|
Acmd41IO,
|
||||||
|
Acmd41OCR,
|
||||||
|
Acmd41Timeout,
|
||||||
|
Locked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SdCardResult {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::OK => "OK",
|
||||||
|
Self::NoCardInSlot => "No card in slot",
|
||||||
|
Self::NotInitialized => "Not initialized",
|
||||||
|
Self::InvalidArgument => "Invalid argument",
|
||||||
|
Self::InvalidAddress => "Invalid address",
|
||||||
|
Self::InvalidOperation => "Invalid operation",
|
||||||
|
Self::Cmd2IO => "CMD2 I/O",
|
||||||
|
Self::Cmd3IO => "CMD3 I/O",
|
||||||
|
Self::Cmd6CheckIO => "CMD6 I/O (check)",
|
||||||
|
Self::Cmd6CheckCRC => "CMD6 CRC (check)",
|
||||||
|
Self::Cmd6CheckTimeout => "CMD6 timeout (check)",
|
||||||
|
Self::Cmd6CheckResponse => "CMD6 invalid response (check)",
|
||||||
|
Self::Cmd6SwitchIO => "CMD6 I/O (switch)",
|
||||||
|
Self::Cmd6SwitchCRC => "CMD6 CRC (switch)",
|
||||||
|
Self::Cmd6SwitchTimeout => "CMD6 timeout (switch)",
|
||||||
|
Self::Cmd6SwitchResponse => "CMD6 invalid response (switch)",
|
||||||
|
Self::Cmd7IO => "CMD7 I/O",
|
||||||
|
Self::Cmd8IO => "CMD8 I/O",
|
||||||
|
Self::Cmd9IO => "CMD9 I/O",
|
||||||
|
Self::Cmd10IO => "CMD10 I/O",
|
||||||
|
Self::Cmd18IO => "CMD18 I/O",
|
||||||
|
Self::Cmd18CRC => "CMD18 CRC",
|
||||||
|
Self::Cmd18Timeout => "CMD18 timeout",
|
||||||
|
Self::Cmd25IO => "CMD25 I/O",
|
||||||
|
Self::Cmd25CRC => "CMD25 CRC",
|
||||||
|
Self::Cmd25Timeout => "CMD25 timeout",
|
||||||
|
Self::Acmd6IO => "ACMD6 I/O",
|
||||||
|
Self::Acmd41IO => "ACMD41 I/O",
|
||||||
|
Self::Acmd41OCR => "ACMD41 OCR",
|
||||||
|
Self::Acmd41Timeout => "ACMD41 timeout",
|
||||||
|
Self::Locked => "SD card is locked by the N64 side",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardResult {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() < 4 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card result data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(match u32::from_be_bytes(value[0..4].try_into().unwrap()) {
|
||||||
|
0 => Self::OK,
|
||||||
|
1 => Self::NoCardInSlot,
|
||||||
|
2 => Self::NotInitialized,
|
||||||
|
3 => Self::InvalidArgument,
|
||||||
|
4 => Self::InvalidAddress,
|
||||||
|
5 => Self::InvalidOperation,
|
||||||
|
6 => Self::Cmd2IO,
|
||||||
|
7 => Self::Cmd3IO,
|
||||||
|
8 => Self::Cmd6CheckIO,
|
||||||
|
9 => Self::Cmd6CheckCRC,
|
||||||
|
10 => Self::Cmd6CheckTimeout,
|
||||||
|
11 => Self::Cmd6CheckResponse,
|
||||||
|
12 => Self::Cmd6SwitchIO,
|
||||||
|
13 => Self::Cmd6SwitchCRC,
|
||||||
|
14 => Self::Cmd6SwitchTimeout,
|
||||||
|
15 => Self::Cmd6SwitchResponse,
|
||||||
|
16 => Self::Cmd7IO,
|
||||||
|
17 => Self::Cmd8IO,
|
||||||
|
18 => Self::Cmd9IO,
|
||||||
|
19 => Self::Cmd10IO,
|
||||||
|
20 => Self::Cmd18IO,
|
||||||
|
21 => Self::Cmd18CRC,
|
||||||
|
22 => Self::Cmd18Timeout,
|
||||||
|
23 => Self::Cmd25IO,
|
||||||
|
24 => Self::Cmd25CRC,
|
||||||
|
25 => Self::Cmd25Timeout,
|
||||||
|
26 => Self::Acmd6IO,
|
||||||
|
27 => Self::Acmd41IO,
|
||||||
|
28 => Self::Acmd41OCR,
|
||||||
|
29 => Self::Acmd41Timeout,
|
||||||
|
30 => Self::Locked,
|
||||||
|
_ => return Err(Error::new("Unknown SD card result code")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardStatus {
|
||||||
|
pub byte_swap: bool,
|
||||||
|
pub clock_mode_50mhz: bool,
|
||||||
|
pub card_type_block: bool,
|
||||||
|
pub card_initialized: bool,
|
||||||
|
pub card_inserted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SdCardStatus {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if self.card_initialized {
|
||||||
|
f.write_str("Initialized, ")?;
|
||||||
|
f.write_str(if self.clock_mode_50mhz {
|
||||||
|
"50 MHz"
|
||||||
|
} else {
|
||||||
|
"25 MHz"
|
||||||
|
})?;
|
||||||
|
if !self.card_type_block {
|
||||||
|
f.write_str(", byte addressed")?;
|
||||||
|
}
|
||||||
|
if self.byte_swap {
|
||||||
|
f.write_str(", byte swap enabled")?;
|
||||||
|
}
|
||||||
|
} else if self.card_inserted {
|
||||||
|
f.write_str("Not initialized")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("Not inserted")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardOpPacket {
|
||||||
|
pub result: SdCardResult,
|
||||||
|
pub status: SdCardStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardStatus {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != 8 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card status data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let status = u32::from_be_bytes(value[4..8].try_into().unwrap());
|
||||||
|
return Ok(Self {
|
||||||
|
byte_swap: status & (1 << 4) != 0,
|
||||||
|
clock_mode_50mhz: status & (1 << 3) != 0,
|
||||||
|
card_type_block: status & (1 << 2) != 0,
|
||||||
|
card_initialized: status & (1 << 1) != 0,
|
||||||
|
card_inserted: status & (1 << 0) != 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardInfo {
|
||||||
|
pub sectors: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardInfo {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != 32 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card info data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let csd = u128::from_be_bytes(value[0..16].try_into().unwrap());
|
||||||
|
let csd_structure = (csd >> 126) & 0x3;
|
||||||
|
match csd_structure {
|
||||||
|
0 => {
|
||||||
|
let c_size = ((csd >> 62) & 0xFFF) as u64;
|
||||||
|
let c_size_mult = ((csd >> 47) & 0x7) as u32;
|
||||||
|
let read_bl_len = ((csd >> 80) & 0xF) as u32;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 2u64.pow(c_size_mult + 2) * 2u64.pow(read_bl_len) / 512,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let c_size = ((csd >> 48) & 0x3FFFFF) as u64;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 1024,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let c_size = ((csd >> 48) & 0xFFFFFFF) as u64;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 1024,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(Error::new("Unknown CSD structure value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DiskBlock {
|
pub struct DiskBlock {
|
||||||
pub address: u32,
|
pub address: u32,
|
||||||
pub track: u32,
|
pub track: u32,
|
||||||
|
Loading…
Reference in New Issue
Block a user