From bf721acf5aeca0ca8f9b435cf55a63904d9fa00a Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 27 Sep 2024 01:30:54 +0200 Subject: [PATCH] [SC64][SW] Added access to the SD card via USB interface --- sw/bootloader/src/fatfs/ffconf.h | 4 +- sw/bootloader/src/sc64.c | 1 + sw/bootloader/src/sc64.h | 1 + sw/controller/src/cfg.c | 86 ++-- sw/controller/src/dd.c | 26 +- sw/controller/src/sd.c | 24 ++ sw/controller/src/sd.h | 11 + sw/controller/src/usb.c | 117 ++++++ sw/controller/src/writeback.c | 11 +- sw/deployer/Cargo.lock | 124 ++++-- sw/deployer/Cargo.toml | 4 + sw/deployer/build.rs | 19 + sw/deployer/src/main.rs | 203 +++++++++- sw/deployer/src/sc64/error.rs | 6 + sw/deployer/src/sc64/ff.rs | 648 +++++++++++++++++++++++++++++++ sw/deployer/src/sc64/mod.rs | 177 ++++++++- sw/deployer/src/sc64/types.rs | 235 +++++++++++ 17 files changed, 1616 insertions(+), 81 deletions(-) create mode 100644 sw/deployer/build.rs create mode 100644 sw/deployer/src/sc64/ff.rs diff --git a/sw/bootloader/src/fatfs/ffconf.h b/sw/bootloader/src/fatfs/ffconf.h index 00a3818..e7b1b68 100644 --- a/sw/bootloader/src/fatfs/ffconf.h +++ b/sw/bootloader/src/fatfs/ffconf.h @@ -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 diff --git a/sw/bootloader/src/sc64.c b/sw/bootloader/src/sc64.c index c6a71dc..7fd7a8d 100644 --- a/sw/bootloader/src/sc64.c +++ b/sw/bootloader/src/sc64.c @@ -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)"; } } diff --git a/sw/bootloader/src/sc64.h b/sw/bootloader/src/sc64.h index 90e4b42..1c684c6 100644 --- a/sw/bootloader/src/sc64.h +++ b/sw/bootloader/src/sc64.h @@ -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; diff --git a/sw/controller/src/cfg.c b/sw/controller/src/cfg.c index 99317c2..d7db3f8 100644 --- a/sw/controller/src/cfg.c +++ b/sw/controller/src/cfg.c @@ -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); } diff --git a/sw/controller/src/dd.c b/sw/controller/src/dd.c index aad3ae9..e842622 100644 --- a/sw/controller/src/dd.c +++ b/sw/controller/src/dd.c @@ -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; diff --git a/sw/controller/src/sd.c b/sw/controller/src/sd.c index f6e94b3..372d39d 100644 --- a/sw/controller/src/sd.c +++ b/sw/controller/src/sd.c @@ -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); } diff --git a/sw/controller/src/sd.h b/sw/controller/src/sd.h index d3e326d..a6c8b03 100644 --- a/sw/controller/src/sd.h +++ b/sw/controller/src/sd.h @@ -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); diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index 8a61d04..e0a345e 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -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(§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': 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); } } diff --git a/sw/controller/src/writeback.c b/sw/controller/src/writeback.c index f089d96..6c943fc 100644 --- a/sw/controller/src/writeback.c +++ b/sw/controller/src/writeback.c @@ -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; } diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index e09cfe6..85c87c8 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -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", ] diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 20b0378..56c7459 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -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"] } diff --git a/sw/deployer/build.rs b/sw/deployer/build.rs new file mode 100644 index 0000000..98a0874 --- /dev/null +++ b/sw/deployer/build.rs @@ -0,0 +1,19 @@ +fn main() -> Result<(), Box> { + 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(()) +} diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index 3915324..31f310b 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -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, + }, + + /// 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, + }, + + /// 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, + }, +} + #[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, remote: Option 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()); diff --git a/sw/deployer/src/sc64/error.rs b/sw/deployer/src/sc64/error.rs index 0702fd5..0a23b9c 100644 --- a/sw/deployer/src/sc64/error.rs +++ b/sw/deployer/src/sc64/error.rs @@ -26,3 +26,9 @@ impl From for Error { Error::new(format!("IO error: {}", value).as_str()) } } + +impl From for Error { + fn from(value: super::ff::Error) -> Self { + Error::new(format!("FatFs error: {}", value).as_str()) + } +} diff --git a/sw/deployer/src/sc64/ff.rs b/sw/deployer/src/sc64/ff.rs new file mode 100644 index 0000000..b93370f --- /dev/null +++ b/sw/deployer/src/sc64/ff.rs @@ -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 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>(path: P) -> Result { + 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>> = std::sync::Mutex::new(None); + +pub struct FF { + fs: Box, +} + +impl FF { + fn new() -> Self { + Self { + fs: Box::new(unsafe { std::mem::zeroed() }), + } + } + + pub fn open>(&self, path: P) -> Result { + File::open( + path, + fatfs::FA_OPEN_EXISTING | fatfs::FA_READ | fatfs::FA_WRITE, + ) + } + + pub fn create>(&self, path: P) -> Result { + File::open( + path, + fatfs::FA_CREATE_ALWAYS | fatfs::FA_READ | fatfs::FA_WRITE, + ) + } + + pub fn stat>(&self, path: P) -> Result { + 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>(&self, path: P) -> Result { + Directory::open(path) + } + + pub fn list>(&self, path: P) -> Result, Error> { + let mut dir = self.opendir(path)?; + + let mut list = Vec::::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>(&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>(&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>(&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 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 { + 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 { + 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 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>(path: P) -> Result { + 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, 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>(path: P, mode: u32) -> Result { + 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 { + 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 { + 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 { + 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) }; + } +} diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index 0c74005..a38f2e3 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -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 { + 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 { + 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 { + 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> { 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 { + self.command_sd_card_operation(SdCardOp::Init) + .map(|packet| packet.result) + } + + pub fn deinit_sd_card(&mut self) -> Result { + self.command_sd_card_operation(SdCardOp::Deinit) + .map(|packet| packet.result) + } + + pub fn get_sd_card_status(&mut self) -> Result { + self.command_sd_card_operation(SdCardOp::GetStatus) + .map(|reply| reply.status) + } + + pub fn get_sd_card_info(&mut self) -> Result { + 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 { + 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 { + 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 { + 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, diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 585acee..541e8de 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -737,6 +737,241 @@ impl TryFrom> for DiskPacket { } } +pub enum SdCardOp { + Deinit, + Init, + GetStatus, + GetInfo(u32), + ByteSwapOn, + ByteSwapOff, +} + +impl From 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> for SdCardResult { + type Error = Error; + fn try_from(value: Vec) -> Result { + 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> for SdCardStatus { + type Error = Error; + fn try_from(value: Vec) -> Result { + 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> for SdCardInfo { + type Error = Error; + fn try_from(value: Vec) -> Result { + 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,