[SC64][SW] Added access to the SD card via USB interface

This commit is contained in:
Mateusz Faderewski 2024-09-27 01:30:54 +02:00
parent 3146cc8c99
commit bf721acf5a
17 changed files with 1616 additions and 81 deletions

View File

@ -133,7 +133,7 @@
/ 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.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
@ -145,7 +145,7 @@
/ 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
/* 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

View File

@ -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_OCR: return "ACMD41 OCR";
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)";
}
}

View File

@ -55,6 +55,7 @@ typedef enum {
SD_ERROR_ACMD41_IO = 27,
SD_ERROR_ACMD41_OCR = 28,
SD_ERROR_ACMD41_TIMEOUT = 29,
SD_ERROR_LOCKED = 30,
} sc64_sd_error_t;
typedef uint32_t sc64_error_t;

View File

@ -141,6 +141,10 @@ static struct process p;
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);
if (reg & CFG_CMD_AUX_PENDING) {
@ -634,21 +638,25 @@ void cfg_process (void) {
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
break;
case CMD_ID_SD_CARD_OP:
case CMD_ID_SD_CARD_OP: {
sd_error_t error = SD_OK;
switch (p.data[1]) {
case SD_CARD_OP_DEINIT:
sd_card_deinit();
break;
case SD_CARD_OP_INIT: {
led_activity_on();
sd_error_t error = sd_card_init();
led_activity_off();
if (error != SD_OK) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
sd_card_deinit();
sd_release_lock(SD_LOCK_N64);
}
break;
case SD_CARD_OP_INIT:
error = sd_try_lock(SD_LOCK_N64);
if (error == SD_OK) {
led_activity_on();
error = sd_card_init();
led_activity_off();
}
break;
}
case SD_CARD_OP_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))) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
}
sd_error_t error = sd_card_get_info(p.data[0]);
if (error != SD_OK) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
error = sd_card_get_info(p.data[0]);
}
break;
case SD_CARD_OP_BYTE_SWAP_ON: {
sd_error_t error = sd_set_byte_swap(true);
if (error != SD_OK) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
case SD_CARD_OP_BYTE_SWAP_ON:
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
error = sd_set_byte_swap(true);
}
break;
}
case SD_CARD_OP_BYTE_SWAP_OFF: {
sd_error_t error = sd_set_byte_swap(false);
if (error != SD_OK) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
case SD_CARD_OP_BYTE_SWAP_OFF:
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
error = sd_set_byte_swap(false);
}
break;
}
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;
}
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];
break;
}
case CMD_ID_SD_READ: {
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))) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
}
led_activity_on();
sd_error_t error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
led_activity_off();
sd_error_t error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
led_activity_on();
error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
led_activity_off();
}
if (error != SD_OK) {
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))) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
}
led_activity_on();
sd_error_t error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
led_activity_off();
sd_error_t error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
led_activity_on();
error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
led_activity_off();
}
if (error != SD_OK) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
}

View File

@ -134,11 +134,14 @@ static bool dd_block_read_request (void) {
uint16_t index = dd_track_head_block();
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
if (p.sd_mode) {
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
led_activity_on();
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
led_activity_off();
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 sectors = dd_fill_sd_sector_table(index, sector_table, false);
led_activity_on();
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
led_activity_off();
}
dd_set_block_ready(error == SD_OK);
} else {
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 buffer_address = DD_BLOCK_BUFFER_ADDRESS;
if (p.sd_mode) {
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
led_activity_on();
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
led_activity_off();
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 sectors = dd_fill_sd_sector_table(index, sector_table, true);
led_activity_on();
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
led_activity_off();
}
dd_set_block_ready(error == SD_OK);
} else {
usb_tx_info_t packet_info;

View File

@ -86,6 +86,7 @@ struct process {
uint8_t csd[16];
uint8_t cid[16];
bool byte_swap;
sd_lock_t lock;
};
@ -428,6 +429,7 @@ sd_error_t sd_card_init (void) {
void sd_card_deinit (void) {
if (p.card_initialized) {
p.card_initialized = false;
p.card_type_block = false;
p.byte_swap = false;
sd_set_clock(CLOCK_400KHZ);
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;
}
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) {
p.card_initialized = false;
p.byte_swap = false;
p.lock = SD_LOCK_NONE;
sd_set_clock(CLOCK_STOP);
}

View File

@ -41,10 +41,17 @@ typedef enum {
SD_ERROR_ACMD41_IO = 27,
SD_ERROR_ACMD41_OCR = 28,
SD_ERROR_ACMD41_TIMEOUT = 29,
SD_ERROR_LOCKED = 30,
} sd_error_t;
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);
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_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_process (void);

View File

@ -4,7 +4,9 @@
#include "flash.h"
#include "fpga.h"
#include "hw.h"
#include "led.h"
#include "rtc.h"
#include "sd.h"
#include "timer.h"
#include "update.h"
#include "usb.h"
@ -268,6 +270,7 @@ static void usb_rx_process (void) {
case 'R':
cfg_reset_state();
cic_reset_parameters();
sd_release_lock(SD_LOCK_USB);
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
break;
@ -384,6 +387,119 @@ static void usb_rx_process (void) {
p.response_pending = true;
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(&sector)) {
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(&sector)) {
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':
dd_set_block_ready(p.rx_args[0] == 0);
p.rx_state = RX_STATE_IDLE;
@ -635,5 +751,6 @@ void usb_process (void) {
usb_tx_process();
} else {
usb_flush_packet();
sd_release_lock(SD_LOCK_USB);
}
}

View File

@ -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) {
save_type_t save;
uint32_t address;
uint32_t length;
save = writeback_get_address_length(&address, &length);
if (save == SAVE_TYPE_NONE) {
if (writeback_get_address_length(&address, &length) == SAVE_TYPE_NONE) {
writeback_disable();
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();
return;
}

124
sw/deployer/Cargo.lock generated
View File

@ -124,7 +124,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -162,6 +162,26 @@ dependencies = [
"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]]
name = "bit_field"
version = "0.10.2"
@ -218,13 +238,22 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.0.103"
version = "1.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
dependencies = [
"jobserver",
"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]]
@ -263,6 +292,17 @@ dependencies = [
"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]]
name = "clap"
version = "4.5.8"
@ -303,7 +343,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -482,6 +522,12 @@ dependencies = [
"weezl",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "half"
version = "2.4.1"
@ -633,7 +679,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -755,6 +801,16 @@ dependencies = [
"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]]
name = "libudev"
version = "0.3.0"
@ -922,7 +978,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -1007,6 +1063,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@ -1038,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -1219,10 +1285,18 @@ dependencies = [
"ordered-multimap",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "sc64deployer"
version = "2.19.0"
dependencies = [
"bindgen",
"cc",
"chrono",
"clap",
"clap-num",
@ -1266,14 +1340,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -1308,6 +1382,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.7"
@ -1357,9 +1437,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.68"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@ -1402,7 +1482,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
]
[[package]]
@ -1430,18 +1510,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.14"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
@ -1527,7 +1607,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
"wasm-bindgen-shared",
]
@ -1549,7 +1629,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.77",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -1738,9 +1818,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.13"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View File

@ -6,6 +6,10 @@ authors = ["Polprzewodnikowy"]
description = "SummerCart64 loader and control software"
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
[build-dependencies]
bindgen = "0.70.1"
cc = "1.1.18"
[dependencies]
chrono = "0.4.38"
clap = { version = "4.5.8", features = ["derive"] }

19
sw/deployer/build.rs Normal file
View 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(())
}

View File

@ -11,12 +11,13 @@ use panic_message::panic_message;
use std::{
fs::File,
io::{stdin, stdout, Read, Write},
panic,
path::PathBuf,
process,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
{panic, process},
};
#[derive(Parser)]
@ -57,6 +58,12 @@ enum Commands {
/// Dump data from arbitrary location in SC64 memory space
Dump(DumpArgs),
/// Perform operations on the SD card
SD {
#[command(subcommand)]
command: SDCommands,
},
/// Print information about connected SC64 device
Info,
@ -202,6 +209,67 @@ struct DumpArgs {
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>,
},
}
#[derive(Subcommand)]
enum SetCommands {
/// Synchronize real time clock (RTC) on the SC64 with local system time
@ -340,6 +408,7 @@ fn handle_command(command: &Commands, port: Option<String>, remote: Option<Strin
Commands::_64DD(args) => handle_64dd_command(connection, args),
Commands::Debug(args) => handle_debug_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::Reset => handle_reset_command(connection),
Commands::Set { command } => handle_set_command(connection, command),
@ -767,16 +836,122 @@ fn handle_dump_command(connection: Connection, args: &DumpArgs) -> Result<(), sc
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(),
))
}
}
sc64.reset_state()?;
sc64::ff::run(sc64, |ff| {
match command {
SDCommands::List { path } => {
for item in ff.list(path.clone().unwrap_or(PathBuf::from("/")))? {
println!("{item}");
}
}
SDCommands::Stat { path } => {
let info = ff.stat(path)?;
println!("{info}");
}
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];
print!(
"{}",
format!(
"Downloading {} to {}... ",
src.to_str().unwrap_or_default().bright_green(),
dst.to_str().unwrap_or_default().bright_green()
)
);
stdout().flush().unwrap();
loop {
match src_file.read(&mut buffer)? {
0 => break,
bytes => dst_file.write_all(&buffer[0..bytes])?,
}
}
println!("{}", "done!".bright_green());
}
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];
print!(
"{}",
format!(
"Uploading {} to {}... ",
src.to_str().unwrap_or_default().bright_green(),
dst.to_str().unwrap_or_default().bright_green()
)
);
stdout().flush().unwrap();
loop {
match src_file.read(&mut buffer)? {
0 => break,
bytes => dst_file.write_all(&buffer[0..bytes])?,
}
}
println!("{}", "done!".bright_green());
}
}
Ok(())
})?;
Ok(())
}
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(connection, true)?;
let (major, minor, revision) = sc64.check_firmware_version()?;
let state = sc64.get_device_state()?;
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
println!("{}", "SummerCart64 state information:".bold());
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
println!(" RTC datetime: {}", datetime);
println!(" RTC datetime: {}", state.datetime);
println!(" Boot mode: {}", state.boot_mode);
println!(" Save type: {}", state.save_type);
println!(" CIC seed: {}", state.cic_seed);
@ -793,6 +968,7 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
println!(" Button state: {}", state.button_state);
println!(" LED blink: {}", state.led_enable);
println!(" IS-Viewer 64: {}", state.isviewer);
println!(" SD card status: {}", state.sd_card_status);
println!("{}", "SummerCart64 diagnostic information:".bold());
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
println!(
@ -921,19 +1097,30 @@ fn handle_firmware_command(
}
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());
print!(" Performing USB read speed test... ");
stdout().flush().unwrap();
let read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
println!("{}", format!("{read_speed:.2} MiB/s",).bright_green());
let usb_read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
println!("{}", format!("{usb_read_speed:.2} MiB/s",).bright_green());
print!(" Performing USB write speed test... ");
stdout().flush().unwrap();
let write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
println!("{}", format!("{write_speed:.2} MiB/s",).bright_green());
let usb_write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
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());

View File

@ -26,3 +26,9 @@ impl From<std::io::Error> for Error {
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())
}
}

648
sw/deployer/src/sc64/ff.rs Normal file
View File

@ -0,0 +1,648 @@
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,
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::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);
pub struct FF {
fs: Box<fatfs::FATFS>,
}
impl FF {
fn new() -> Self {
Self {
fs: Box::new(unsafe { std::mem::zeroed() }),
}
}
pub fn open<P: AsRef<std::path::Path>>(&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>>(&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>>(&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>>(&self, path: P) -> Result<Directory, Error> {
Directory::open(path)
}
pub fn list<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Vec<Entry>, Error> {
let mut dir = self.opendir(path)?;
let mut list = Vec::<Entry>::new();
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>>(&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>>(&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>>(&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 run<F: FnOnce(FF) -> Result<(), super::Error>>(
driver: impl FFDriver + 'static,
runner: F,
) -> Result<(), super::Error> {
let fs = start(driver)?;
let result = runner(fs);
stop()?;
result
}
fn start(fatfs_driver: impl FFDriver + 'static) -> Result<FF, Error> {
driver_install(fatfs_driver)?;
let mut ff = FF::new();
match unsafe { fatfs::f_mount(&mut *ff.fs, fatfs::path("")?.as_ptr(), 1) } {
fatfs::FRESULT_FR_OK => Ok(ff),
error => {
driver_uninstall().ok();
Err(error.into())
}
}
}
fn stop() -> Result<(), Error> {
match unsafe { fatfs::f_mount(std::ptr::null_mut(), fatfs::path("")?.as_ptr(), 0) } {
fatfs::FRESULT_FR_OK => driver_uninstall(),
error => Err(error.into()),
}
}
fn driver_install(new_driver: impl FFDriver + 'static) -> Result<(), Error> {
match unsafe { DRIVER.lock() } {
Ok(mut driver) => {
if driver.is_none() {
driver.replace(Box::new(new_driver));
Ok(())
} else {
Err(Error::IntErr)
}
}
Err(_) => Err(Error::IntErr),
}
}
fn driver_uninstall() -> Result<(), Error> {
match unsafe { DRIVER.lock() } {
Ok(mut driver) => {
if let Some(mut driver) = driver.take() {
driver.deinit();
Ok(())
} else {
Err(Error::IntErr)
}
}
Err(_) => Err(Error::IntErr),
}
}
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 Ok(mut driver) = DRIVER.lock() {
if let Some(driver) = driver.as_mut() {
return driver.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 Ok(mut driver) = DRIVER.lock() {
if let Some(driver) = driver.as_mut() {
return driver.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 Ok(mut driver) = DRIVER.lock() {
if let Some(driver) = driver.as_mut() {
return driver.read(
&mut *std::ptr::slice_from_raw_parts_mut(
buff,
(count as usize) * SD_CARD_SECTOR_SIZE,
),
sector,
);
}
}
fatfs::DRESULT_RES_ERROR
}
#[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 Ok(mut driver) = DRIVER.lock() {
if let Some(driver) = driver.as_mut() {
return driver.write(
&*std::ptr::slice_from_raw_parts(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
sector,
);
}
}
fatfs::DRESULT_RES_ERROR
}
#[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 Ok(mut driver) = DRIVER.lock() {
if let Some(driver) = driver.as_mut() {
let result = driver.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_OK
}
#[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 Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let size_formatter = |size: u64| -> String {
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;
}
}
if size >= 1000 && reduced_size <= 9.9 {
format!("{:>1.1}{}", reduced_size, reduced_unit)
} else {
format!("{:>3.0}{}", reduced_size, reduced_unit)
}
};
Ok(match self.info {
EntryInfo::Directory => f.write_fmt(format_args!(
"d ---- {} | {}",
self.datetime,
format!("/{}", self.name)
))?,
EntryInfo::File { size } => f.write_fmt(format_args!(
"f {} {} | {}",
size_formatter(size),
self.datetime,
self.name
))?,
})
}
}
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: fatfs::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: fatfs::FILINFO = 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: fatfs::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) };
}
}

View File

@ -1,5 +1,6 @@
mod cic;
mod error;
pub mod ff;
pub mod firmware;
mod ftdi;
mod link;
@ -16,7 +17,8 @@ pub use self::{
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState,
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
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,
time::{convert_from_datetime, convert_to_datetime},
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;
@ -58,6 +61,7 @@ pub struct DeviceState {
pub button_mode: ButtonMode,
pub rom_extended_enable: Switch,
pub led_enable: Switch,
pub sd_card_status: SdCardStatus,
pub datetime: NaiveDateTime,
pub fpga_debug_data: FpgaDebugData,
pub diagnostic_data: DiagnosticData,
@ -94,6 +98,11 @@ const SRAM_1M_LENGTH: usize = 128 * 1024;
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
const SD_CARD_BUFFER_ADDRESS: u32 = 0x03BA_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_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
@ -235,6 +244,53 @@ impl SC64 {
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],
&sector.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],
&sector.to_be_bytes(),
false,
true,
)?;
Ok(data.try_into()?)
}
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
self.link.execute_command(b'D', [error as u32, 0], &[])?;
Ok(())
@ -489,6 +545,7 @@ impl SC64 {
button_mode: get_config!(self, ButtonMode)?,
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
led_enable: get_setting!(self, LedEnable)?,
sd_card_status: self.get_sd_card_status()?,
datetime: self.get_datetime()?,
fpga_debug_data: self.command_fpga_debug_data_get()?,
diagnostic_data: self.command_diagnostic_data_get()?,
@ -606,6 +663,82 @@ impl SC64 {
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> {
let info =
match self.command_sd_card_operation(SdCardOp::GetInfo(SD_CARD_BUFFER_ADDRESS))? {
SdCardOpPacket {
result: SdCardResult::OK,
status: _,
} => self.command_memory_read(SD_CARD_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> {
let identifier = self.command_identifier_get().map_err(|e| {
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
@ -738,6 +871,46 @@ impl SC64 {
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(
&mut self,
pattern: MemoryTestPattern,

View File

@ -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 address: u32,
pub track: u32,