mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-22 14:09:16 +01:00
[SC64][SW] Added access to the SD card via USB interface
This commit is contained in:
parent
3146cc8c99
commit
bf721acf5a
4
sw/bootloader/src/fatfs/ffconf.h
vendored
4
sw/bootloader/src/fatfs/ffconf.h
vendored
@ -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
|
||||
|
@ -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)";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
124
sw/deployer/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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
19
sw/deployer/build.rs
Normal file
@ -0,0 +1,19 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
cc::Build::new()
|
||||
.file("../bootloader/src/fatfs/ff.c")
|
||||
.file("../bootloader/src/fatfs/ffsystem.c")
|
||||
.file("../bootloader/src/fatfs/ffunicode.c")
|
||||
.compile("fatfs");
|
||||
|
||||
bindgen::Builder::default()
|
||||
.header("../bootloader/src/fatfs/ff.h")
|
||||
.blocklist_function("get_fattime")
|
||||
.generate()
|
||||
.expect("Unable to generate FatFs bindings")
|
||||
.write_to_file(out_dir.join("fatfs_bindings.rs"))
|
||||
.expect("Unable to write FatFs bindings");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -11,12 +11,13 @@ use panic_message::panic_message;
|
||||
use std::{
|
||||
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());
|
||||
|
||||
|
@ -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
648
sw/deployer/src/sc64/ff.rs
Normal 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) };
|
||||
}
|
||||
}
|
@ -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],
|
||||
§or.to_be_bytes(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
|
||||
fn command_sd_card_write(
|
||||
&mut self,
|
||||
address: u32,
|
||||
sector: u32,
|
||||
count: u32,
|
||||
) -> Result<SdCardResult, Error> {
|
||||
let data = self.link.execute_command_raw(
|
||||
b'S',
|
||||
[address, count],
|
||||
§or.to_be_bytes(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
|
||||
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
||||
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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user