[SC64][SW] Implement new libftdi backend in the sc64deployer (#72)

This commit is contained in:
Mateusz Faderewski 2024-07-21 10:48:20 +02:00 committed by GitHub
parent 5adc95b6e1
commit 5e33e516a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 2074 additions and 747 deletions

View File

@ -71,7 +71,7 @@ jobs:
- version: linux - version: linux
os: ubuntu-latest os: ubuntu-latest
linux-packages: libudev-dev apt-packages: libudev-dev
executable: target/release/sc64deployer executable: target/release/sc64deployer
package-name: sc64-deployer-linux package-name: sc64-deployer-linux
package-params: -czf package-params: -czf
@ -98,11 +98,11 @@ jobs:
string: '${{ github.ref_name }}' string: '${{ github.ref_name }}'
replace-with: '-' replace-with: '-'
- name: Install linux packages - name: Install apt packages
if: matrix.linux-packages if: matrix.apt-packages
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install ${{ matrix.linux-packages }} sudo apt-get -y install ${{ matrix.apt-packages }}
- name: Build deployer - name: Build deployer
run: cargo b -r ${{ matrix.build-params }} run: cargo b -r ${{ matrix.build-params }}

View File

@ -394,13 +394,13 @@ module mcu_top (
REG_USB_SCR: begin REG_USB_SCR: begin
reg_rdata <= { reg_rdata <= {
2'd0, 1'd0,
usb_scb.fifo_flush_busy,
usb_scb.pwrsav, usb_scb.pwrsav,
usb_scb.reset_state, usb_scb.reset_state,
usb_scb.tx_count, usb_scb.tx_count,
usb_scb.rx_count, usb_scb.rx_count,
2'b00, 3'b000,
usb_scb.reset_pending,
~fifo_bus.tx_full, ~fifo_bus.tx_full,
~fifo_bus.rx_empty, ~fifo_bus.rx_empty,
1'b0 1'b0
@ -681,9 +681,10 @@ module mcu_top (
mem_start <= 1'b0; mem_start <= 1'b0;
mem_stop <= 1'b0; mem_stop <= 1'b0;
usb_scb.write_buffer_flush <= 1'b0;
usb_scb.reset_ack <= 1'b0;
usb_scb.fifo_flush <= 1'b0; usb_scb.fifo_flush <= 1'b0;
usb_scb.write_buffer_flush <= 1'b0;
usb_scb.reset_on_ack <= 1'b0;
usb_scb.reset_off_ack <= 1'b0;
usb_dma_scb.start <= 1'b0; usb_dma_scb.start <= 1'b0;
usb_dma_scb.stop <= 1'b0; usb_dma_scb.stop <= 1'b0;
@ -770,11 +771,10 @@ module mcu_top (
end end
REG_USB_SCR: begin REG_USB_SCR: begin
{ usb_scb.write_buffer_flush <= reg_wdata[5];
usb_scb.write_buffer_flush, usb_scb.reset_off_ack <= reg_wdata[4];
usb_scb.reset_ack, usb_scb.reset_on_ack <= reg_wdata[3];
usb_scb.fifo_flush usb_scb.fifo_flush <= reg_wdata[0];
} <= {reg_wdata[5:4], reg_wdata[0]};
end end
REG_USB_DMA_ADDRESS: begin REG_USB_DMA_ADDRESS: begin

View File

@ -45,12 +45,26 @@ module memory_dma (
logic dma_start; logic dma_start;
logic dma_stop; logic dma_stop;
logic dma_stop_requested;
always_comb begin always_comb begin
dma_start = dma_scb.start && !dma_scb.stop && !dma_scb.busy; dma_start = dma_scb.start && !dma_scb.stop && !dma_scb.busy;
dma_stop = dma_scb.stop; dma_stop = dma_scb.stop;
end end
always_ff @(posedge clk) begin
if (reset) begin
dma_stop_requested <= 1'b0;
end else begin
if (dma_stop) begin
dma_stop_requested <= 1'b1;
end
if (dma_start) begin
dma_stop_requested <= 1'b0;
end
end
end
// Remaining counter and FIFO enable // Remaining counter and FIFO enable
@ -197,7 +211,7 @@ module memory_dma (
remaining <= dma_scb.transfer_length; remaining <= dma_scb.transfer_length;
end end
if ((mem_bus.write && rx_rdata_pop) || (!mem_bus.write && tx_wdata_push)) begin if (!dma_stop_requested && ((mem_bus.write && rx_rdata_pop) || (!mem_bus.write && tx_wdata_push))) begin
remaining <= remaining - 1'd1; remaining <= remaining - 1'd1;
end end
end end

View File

@ -1,34 +1,37 @@
interface usb_scb (); interface usb_scb ();
logic fifo_flush; logic fifo_flush;
logic reset_pending; logic fifo_flush_busy;
logic reset_ack;
logic write_buffer_flush; logic write_buffer_flush;
logic [10:0] rx_count; logic [10:0] rx_count;
logic [10:0] tx_count; logic [10:0] tx_count;
logic pwrsav; logic pwrsav;
logic reset_state; logic reset_state;
logic reset_on_ack;
logic reset_off_ack;
modport controller ( modport controller (
output fifo_flush, output fifo_flush,
input reset_pending, input fifo_flush_busy,
output reset_ack,
output write_buffer_flush, output write_buffer_flush,
input rx_count, input rx_count,
input tx_count, input tx_count,
input pwrsav, input pwrsav,
input reset_state input reset_state,
output reset_on_ack,
output reset_off_ack
); );
modport usb ( modport usb (
input fifo_flush, input fifo_flush,
output reset_pending, output fifo_flush_busy,
input reset_ack,
input write_buffer_flush, input write_buffer_flush,
output rx_count, output rx_count,
output tx_count, output tx_count,
output pwrsav, output pwrsav,
output reset_state output reset_state,
input reset_on_ack,
input reset_off_ack
); );
endinterface endinterface
@ -59,9 +62,11 @@ module usb_ft1248 (
logic tx_read; logic tx_read;
logic [7:0] tx_rdata; logic [7:0] tx_rdata;
logic fifo_flush;
fifo_8kb fifo_8kb_rx_inst ( fifo_8kb fifo_8kb_rx_inst (
.clk(clk), .clk(clk),
.reset(reset || usb_scb.fifo_flush), .reset(reset || fifo_flush),
.empty(fifo_bus.rx_empty), .empty(fifo_bus.rx_empty),
.almost_empty(fifo_bus.rx_almost_empty), .almost_empty(fifo_bus.rx_almost_empty),
@ -78,7 +83,7 @@ module usb_ft1248 (
fifo_8kb fifo_8kb_tx_inst ( fifo_8kb fifo_8kb_tx_inst (
.clk(clk), .clk(clk),
.reset(reset || usb_scb.fifo_flush), .reset(reset || fifo_flush),
.empty(tx_empty), .empty(tx_empty),
.almost_empty(tx_almost_empty), .almost_empty(tx_almost_empty),
@ -142,7 +147,6 @@ module usb_ft1248 (
logic [3:0] phase; logic [3:0] phase;
logic last_tx_failed; logic last_tx_failed;
logic reset_reply; logic reset_reply;
logic last_reset_status;
logic [4:0] modem_status_counter; logic [4:0] modem_status_counter;
logic write_modem_status_pending; logic write_modem_status_pending;
logic write_buffer_flush_pending; logic write_buffer_flush_pending;
@ -152,7 +156,7 @@ module usb_ft1248 (
cmd <= next_cmd; cmd <= next_cmd;
usb_scb.pwrsav <= !ft_pwrsav; usb_scb.pwrsav <= !ft_pwrsav;
usb_scb.reset_state <= last_reset_status; fifo_flush <= 1'b0;
phase <= {phase[2:0], phase[3]}; phase <= {phase[2:0], phase[3]};
if (state == STATE_IDLE) begin if (state == STATE_IDLE) begin
@ -160,25 +164,38 @@ module usb_ft1248 (
end end
if (reset) begin if (reset) begin
usb_scb.fifo_flush_busy <= 1'b0;
usb_scb.reset_state <= 1'b0;
last_tx_failed <= 1'b0; last_tx_failed <= 1'b0;
usb_scb.reset_pending <= 1'b0; reset_reply <= 1'b0;
last_reset_status <= 1'b0;
modem_status_counter <= 5'd0; modem_status_counter <= 5'd0;
write_modem_status_pending <= 1'b0; write_modem_status_pending <= 1'b1;
write_buffer_flush_pending <= 1'b0; write_buffer_flush_pending <= 1'b0;
end else begin end else begin
if (usb_scb.reset_ack) begin if (usb_scb.fifo_flush) begin
usb_scb.reset_pending <= 1'b0; usb_scb.fifo_flush_busy <= 1'b1;
end
if (usb_scb.reset_on_ack) begin
reset_reply <= 1'b1; reset_reply <= 1'b1;
write_modem_status_pending <= 1'b1; write_modem_status_pending <= 1'b1;
end end
if (usb_scb.reset_off_ack) begin
reset_reply <= 1'b0;
write_modem_status_pending <= 1'b1;
end
if (usb_scb.write_buffer_flush) begin if (usb_scb.write_buffer_flush) begin
write_buffer_flush_pending <= 1'b1; write_buffer_flush_pending <= 1'b1;
end end
if (state == STATE_IDLE) begin if (state == STATE_IDLE) begin
modem_status_counter <= modem_status_counter + 1'd1; modem_status_counter <= modem_status_counter + 1'd1;
if (usb_scb.fifo_flush_busy) begin
usb_scb.fifo_flush_busy <= 1'b0;
fifo_flush <= 1'b1;
end
end end
if ((state == STATE_DATA) && (cmd == CMD_WRITE) && phase[3]) begin if ((state == STATE_DATA) && (cmd == CMD_WRITE) && phase[3]) begin
@ -187,14 +204,7 @@ module usb_ft1248 (
if (!ft_miso && (state == STATE_DATA) && phase[3]) begin if (!ft_miso && (state == STATE_DATA) && phase[3]) begin
if (cmd == CMD_READ_MODEM_STATUS) begin if (cmd == CMD_READ_MODEM_STATUS) begin
last_reset_status <= ft_miosi_in[0]; usb_scb.reset_state <= ft_miosi_in[0];
if (!last_reset_status && ft_miosi_in[0]) begin
usb_scb.reset_pending <= 1'b1;
end
if (last_reset_status && !ft_miosi_in[0]) begin
reset_reply <= 1'b0;
write_modem_status_pending <= 1'b1;
end
end end
if (cmd == CMD_WRITE_MODEM_STATUS) begin if (cmd == CMD_WRITE_MODEM_STATUS) begin
write_modem_status_pending <= 1'b0; write_modem_status_pending <= 1'b0;
@ -283,7 +293,7 @@ module usb_ft1248 (
end else begin end else begin
case (state) case (state)
STATE_IDLE: begin STATE_IDLE: begin
if (ft_pwrsav) begin if (ft_pwrsav && !(usb_scb.fifo_flush || usb_scb.fifo_flush_busy || fifo_flush)) begin
if (write_modem_status_pending) begin if (write_modem_status_pending) begin
next_state = STATE_SELECT; next_state = STATE_SELECT;
next_cmd = CMD_WRITE_MODEM_STATUS; next_cmd = CMD_WRITE_MODEM_STATUS;

View File

@ -79,8 +79,8 @@ typedef enum {
#define USB_SCR_FIFO_FLUSH (1 << 0) #define USB_SCR_FIFO_FLUSH (1 << 0)
#define USB_SCR_RXNE (1 << 1) #define USB_SCR_RXNE (1 << 1)
#define USB_SCR_TXE (1 << 2) #define USB_SCR_TXE (1 << 2)
#define USB_SCR_RESET_PENDING (1 << 3) #define USB_SCR_RESET_ON_ACK (1 << 3)
#define USB_SCR_RESET_ACK (1 << 4) #define USB_SCR_RESET_OFF_ACK (1 << 4)
#define USB_SCR_WRITE_FLUSH (1 << 5) #define USB_SCR_WRITE_FLUSH (1 << 5)
#define USB_SCR_RX_COUNT_BIT (6) #define USB_SCR_RX_COUNT_BIT (6)
#define USB_SCR_RX_COUNT_MASK (0x7FF << USB_SCR_RX_COUNT_BIT) #define USB_SCR_RX_COUNT_MASK (0x7FF << USB_SCR_RX_COUNT_BIT)
@ -88,6 +88,7 @@ typedef enum {
#define USB_SCR_TX_COUNT_MASK (0x7FF << USB_SCR_TX_COUNT_BIT) #define USB_SCR_TX_COUNT_MASK (0x7FF << USB_SCR_TX_COUNT_BIT)
#define USB_SCR_RESET_STATE (1 << 28) #define USB_SCR_RESET_STATE (1 << 28)
#define USB_SCR_PWRSAV (1 << 29) #define USB_SCR_PWRSAV (1 << 29)
#define USB_SCR_FIFO_FLUSH_BUSY (1 << 30)
#define DMA_SCR_START (1 << 0) #define DMA_SCR_START (1 << 0)
#define DMA_SCR_STOP (1 << 1) #define DMA_SCR_STOP (1 << 1)

View File

@ -43,6 +43,8 @@ enum tx_state {
struct process { struct process {
bool last_reset_state;
enum rx_state rx_state; enum rx_state rx_state;
uint8_t rx_counter; uint8_t rx_counter;
uint8_t rx_cmd; uint8_t rx_cmd;
@ -80,10 +82,6 @@ static const uint32_t ERR_TOKEN = (0x45525200UL);
static const uint32_t PKT_TOKEN = (0x504B5400UL); static const uint32_t PKT_TOKEN = (0x504B5400UL);
static bool usb_dma_ready (void) {
return !((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY));
}
static bool usb_rx_byte (uint8_t *data) { static bool usb_rx_byte (uint8_t *data) {
if (fpga_usb_status_get() & USB_STATUS_RXNE) { if (fpga_usb_status_get() & USB_STATUS_RXNE) {
*data = fpga_usb_pop(); *data = fpga_usb_pop();
@ -149,6 +147,59 @@ static bool usb_rx_cmd (uint8_t *cmd) {
return false; return false;
} }
static void usb_reset (void) {
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP);
while (fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY);
fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH);
while (fpga_reg_get(REG_USB_SCR) & USB_SCR_FIFO_FLUSH_BUSY);
p.rx_state = RX_STATE_IDLE;
p.tx_state = TX_STATE_IDLE;
p.response_pending = false;
p.packet_pending = false;
p.read_ready = true;
p.read_length = 0;
p.read_address = 0;
usb_rx_word_counter = 0;
usb_rx_word_buffer = 0;
usb_tx_word_counter = 0;
usb_rx_cmd_counter = 0;
}
static void usb_flush_packet (void) {
if (p.packet_pending && p.packet_info.done_callback) {
p.packet_pending = false;
p.packet_info.done_callback();
}
if (p.tx_state != TX_STATE_IDLE && p.tx_info.done_callback) {
p.tx_info.done_callback();
p.tx_info.done_callback = NULL;
}
}
static bool usb_is_active (void) {
uint32_t scr = fpga_reg_get(REG_USB_SCR);
bool reset_state = (scr & USB_SCR_RESET_STATE);
if (p.last_reset_state != reset_state) {
p.last_reset_state = reset_state;
if (reset_state) {
usb_flush_packet();
usb_reset();
fpga_reg_set(REG_USB_SCR, USB_SCR_WRITE_FLUSH);
}
fpga_reg_set(REG_USB_SCR, reset_state ? USB_SCR_RESET_ON_ACK : USB_SCR_RESET_OFF_ACK);
return false;
}
return !(reset_state || (scr & USB_SCR_PWRSAV));
}
static bool usb_dma_ready (void) {
return !((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY));
}
static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) { static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) {
if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) { if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) {
return true; return true;
@ -410,27 +461,34 @@ static void usb_rx_process (void) {
} }
if (p.rx_state == RX_STATE_FLUSH) { if (p.rx_state == RX_STATE_FLUSH) {
if (usb_dma_ready()) { if (p.rx_args[1] > 0) {
if (p.rx_args[1] != 0) { if (usb_dma_ready()) {
uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1]; uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1];
fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS); if (!p.rx_dma_running) {
fpga_reg_set(REG_USB_DMA_LENGTH, length); fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS);
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); fpga_reg_set(REG_USB_DMA_LENGTH, length);
p.rx_args[1] -= length; fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
} else { p.rx_dma_running = true;
if (p.flush_response) {
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
p.response_error = true;
} else if (p.flush_packet) {
usb_tx_info_t packet_info;
usb_create_packet(&packet_info, PACKET_CMD_DATA_FLUSHED);
if (usb_enqueue_packet(&packet_info)) {
p.rx_state = RX_STATE_IDLE;
}
} else { } else {
p.rx_args[1] -= length;
p.rx_dma_running = false;
}
}
}
if (p.rx_args[1] == 0) {
if (p.flush_response) {
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
p.response_error = true;
} else if (p.flush_packet) {
usb_tx_info_t packet_info;
usb_create_packet(&packet_info, PACKET_CMD_DATA_FLUSHED);
if (usb_enqueue_packet(&packet_info)) {
p.rx_state = RX_STATE_IDLE; p.rx_state = RX_STATE_IDLE;
} }
} else {
p.rx_state = RX_STATE_IDLE;
} }
} }
} }
@ -556,42 +614,16 @@ void usb_get_read_info (uint32_t *args) {
void usb_init (void) { void usb_init (void) {
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP); p.last_reset_state = false;
fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH); usb_reset();
p.rx_state = RX_STATE_IDLE;
p.tx_state = TX_STATE_IDLE;
p.response_pending = false;
p.packet_pending = false;
p.read_ready = true;
p.read_length = 0;
p.read_address = 0;
usb_rx_word_counter = 0;
usb_rx_word_buffer = 0;
usb_tx_word_counter = 0;
usb_rx_cmd_counter = 0;
} }
void usb_process (void) { void usb_process (void) {
uint32_t scr = fpga_reg_get(REG_USB_SCR); if (usb_is_active()) {
if (scr & (USB_SCR_PWRSAV | USB_SCR_RESET_STATE | USB_SCR_RESET_PENDING)) {
if (p.packet_pending && p.packet_info.done_callback) {
p.packet_pending = false;
p.packet_info.done_callback();
}
if (scr & USB_SCR_RESET_PENDING) {
if (p.tx_state != TX_STATE_IDLE && p.tx_info.done_callback) {
p.tx_info.done_callback();
}
usb_init();
fpga_reg_set(REG_USB_SCR, USB_SCR_RESET_ACK);
}
} else {
usb_rx_process(); usb_rx_process();
usb_tx_process(); usb_tx_process();
} else {
usb_flush_packet();
} }
} }

828
sw/deployer/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,22 +7,24 @@ description = "SummerCart64 loader and control software"
documentation = "https://github.com/Polprzewodnikowy/SummerCart64" documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
[dependencies] [dependencies]
chrono = "0.4.23" chrono = "0.4.38"
clap = { version = "4.1.6", features = ["derive"] } clap = { version = "4.5.8", features = ["derive"] }
clap-num = "1.0.2" clap-num = "1.1.1"
colored = "2.0.0" colored = "2.1.0"
crc32fast = "1.3.2" crc32fast = "1.4.2"
ctrlc = "3.2.5" ctrlc = "3.4.4"
encoding_rs = "0.8.32" encoding_rs = "0.8.34"
hex = "0.4.3" hex = "0.4.3"
image = "0.24.5" image = "0.25.1"
include-flate = { version = "0.2.0", features = ["stable"] } include-flate = { version = "0.2.0", features = ["stable"] }
libftdi1-sys = { version = "1.1.3", features = ["libusb1-sys", "vendored"] }
libusb1-sys = { version = "0.6.5", features = ["vendored"] }
md5 = "0.7.0" md5 = "0.7.0"
panic-message = "0.3.0" panic-message = "0.3.0"
rand = "0.8.5" rand = "0.8.5"
rust-ini = "0.18.0" rust-ini = "0.18.0"
serial2 = "0.2.20" serial2 = "0.2.26"
serialport = "4.3.0" serialport = "4.4.0"
[profile.release] [profile.release]
lto = true lto = true

View File

@ -291,17 +291,7 @@ impl Handler {
let filename = &if let Some(path) = path { let filename = &if let Some(path) = path {
path.to_string_lossy().to_string() path.to_string_lossy().to_string()
} else { } else {
generate_filename( generate_filename("save", "sav")
"save",
match save_writeback.save {
sc64::SaveType::Eeprom4k | sc64::SaveType::Eeprom16k => "eep",
sc64::SaveType::Sram | sc64::SaveType::SramBanked | sc64::SaveType::Sram1m => {
"srm"
}
sc64::SaveType::Flashram => "fla",
_ => "sav",
},
)
}; };
match File::create(filename) { match File::create(filename) {
Ok(mut file) => { Ok(mut file) => {

View File

@ -25,7 +25,7 @@ struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Commands, command: Commands,
/// Connect to SC64 device on provided serial port /// Connect to SC64 device on provided local port
#[arg(short, long)] #[arg(short, long)]
port: Option<String>, port: Option<String>,
@ -350,7 +350,13 @@ fn handle_list_command() -> Result<(), sc64::Error> {
println!("{}", "Found devices:".bold()); println!("{}", "Found devices:".bold());
for (i, d) in devices.iter().enumerate() { for (i, d) in devices.iter().enumerate() {
println!(" {i}: [{}] at port [{}]", d.serial_number, d.port); let index = i + 1;
println!(
" {index}: [{}] at port [{}] (using \"{}\" backend)",
d.serial.bold(),
d.port.bold(),
d.backend.to_string().bold()
);
} }
Ok(()) Ok(())
@ -733,35 +739,35 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S"); let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
println!("{}", "SummerCart64 state information:".bold()); println!("{}", "SummerCart64 state information:".bold());
println!(" Firmware version: v{}.{}.{}", major, minor, revision); println!(" Firmware version: v{}.{}.{}", major, minor, revision);
println!(" RTC datetime: {}", datetime); println!(" RTC datetime: {}", datetime);
println!(" Boot mode: {}", state.boot_mode); println!(" Boot mode: {}", state.boot_mode);
println!(" Save type: {}", state.save_type); println!(" Save type: {}", state.save_type);
println!(" CIC seed: {}", state.cic_seed); println!(" CIC seed: {}", state.cic_seed);
println!(" TV type: {}", state.tv_type); println!(" TV type: {}", state.tv_type);
println!(" Bootloader switch: {}", state.bootloader_switch); println!(" Bootloader switch: {}", state.bootloader_switch);
println!(" ROM write: {}", state.rom_write_enable); println!(" ROM write: {}", state.rom_write_enable);
println!(" ROM shadow: {}", state.rom_shadow_enable); println!(" ROM shadow: {}", state.rom_shadow_enable);
println!(" ROM extended: {}", state.rom_extended_enable); println!(" ROM extended: {}", state.rom_extended_enable);
println!(" 64DD mode: {}", state.dd_mode); println!(" 64DD mode: {}", state.dd_mode);
println!(" 64DD SD card mode: {}", state.dd_sd_enable); println!(" 64DD SD card mode: {}", state.dd_sd_enable);
println!(" 64DD drive type: {}", state.dd_drive_type); println!(" 64DD drive type: {}", state.dd_drive_type);
println!(" 64DD disk state: {}", state.dd_disk_state); println!(" 64DD disk state: {}", state.dd_disk_state);
println!(" Button mode: {}", state.button_mode); println!(" Button mode: {}", state.button_mode);
println!(" Button state: {}", state.button_state); println!(" Button state: {}", state.button_state);
println!(" LED blink: {}", state.led_enable); println!(" LED blink: {}", state.led_enable);
println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address); println!(" IS-Viewer 64: {}", state.isviewer);
println!("{}", "SummerCart64 diagnostic information:".bold()); println!("{}", "SummerCart64 diagnostic information:".bold());
println!( println!(
" Last PI address: 0x{:08X}", " Last PI address: 0x{:08X}",
state.fpga_debug_data.last_pi_address state.fpga_debug_data.last_pi_address
); );
println!( println!(
" PI FIFO flags: {}", " PI FIFO flags: {}",
state.fpga_debug_data.pi_fifo_flags state.fpga_debug_data.pi_fifo_flags
); );
println!(" Current CIC step: {}", state.fpga_debug_data.cic_step); println!(" Current CIC step: {}", state.fpga_debug_data.cic_step);
println!(" Diagnostic data: {}", state.diagnostic_data); println!(" Diagnostic data: {}", state.diagnostic_data);
Ok(()) Ok(())
} }

View File

@ -26,9 +26,3 @@ impl From<std::io::Error> for Error {
Error::new(format!("IO error: {}", value).as_str()) Error::new(format!("IO error: {}", value).as_str())
} }
} }
impl From<serialport::Error> for Error {
fn from(value: serialport::Error) -> Self {
Error::new(format!("SerialPort error: {}", value.description).as_str())
}
}

View File

@ -0,0 +1,540 @@
pub struct DeviceInfo {
pub description: String,
pub serial: String,
pub port: String,
}
#[allow(dead_code)]
enum InterfaceIndex {
Any,
A,
B,
C,
D,
}
#[allow(dead_code)]
enum ModuleDetachMode {
AutoDetach,
DontDetach,
AutoDetachReattach,
}
struct ModemStatus {
dsr: bool,
}
struct Wrapper {
context: *mut libftdi1_sys::ftdi_context,
unclog_buffer: std::collections::VecDeque<u8>,
write_buffer: Vec<u8>,
read_timeout: std::time::Duration,
write_timeout: std::time::Duration,
read_chunksize: usize,
write_chunksize: usize,
}
impl Wrapper {
const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16);
const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
fn new(
read_timeout: Option<std::time::Duration>,
write_timeout: Option<std::time::Duration>,
) -> std::io::Result<Self> {
let context = unsafe { libftdi1_sys::ftdi_new() };
if context.is_null() {
return Err(std::io::ErrorKind::OutOfMemory.into());
}
let mut wrapper = Self {
context,
unclog_buffer: std::collections::VecDeque::new(),
write_buffer: vec![],
read_timeout: Self::DEFAULT_RW_TIMEOUT,
write_timeout: Self::DEFAULT_RW_TIMEOUT,
read_chunksize: 4096,
write_chunksize: 4096,
};
wrapper.set_timeouts(read_timeout, write_timeout)?;
wrapper.read_data_set_chunksize(wrapper.read_chunksize)?;
wrapper.write_data_set_chunksize(wrapper.write_chunksize)?;
Ok(wrapper)
}
fn list_devices(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
let wrapper = Self::new(None, None)?;
let mut device_list: *mut libftdi1_sys::ftdi_device_list = std::ptr::null_mut();
let devices = unsafe {
libftdi1_sys::ftdi_usb_find_all(
wrapper.context,
&mut device_list,
vendor as i32,
product as i32,
)
};
let result = if devices > 0 {
let mut list: Vec<DeviceInfo> = vec![];
let mut description = [0i8; 128];
let mut serial = [0i8; 128];
let mut device = device_list;
let mut index = 0;
while !device.is_null() {
let result = unsafe {
libftdi1_sys::ftdi_usb_get_strings(
wrapper.context,
(*device).dev,
std::ptr::null_mut(),
0,
description.as_mut_ptr(),
description.len() as i32,
serial.as_mut_ptr(),
serial.len() as i32,
)
};
let description = unsafe { std::ffi::CStr::from_ptr(description.as_ptr()) }
.to_string_lossy()
.into_owned();
let serial = unsafe { std::ffi::CStr::from_ptr(serial.as_ptr()) }
.to_string_lossy()
.into_owned();
let port = if list.binary_search_by(|d| d.serial.cmp(&serial)).is_ok() {
format!("i:0x{vendor:04X}:0x{product:04X}:{index}")
} else {
format!("s:0x{vendor:04X}:0x{product:04X}:{serial}")
};
if result == 0 {
list.push(DeviceInfo {
description,
serial,
port,
});
}
device = unsafe { (*device).next };
index += 1;
}
list.sort_by(|a, b| a.serial.cmp(&b.serial));
Ok(list)
} else {
match devices {
0 => Ok(vec![]),
-3 => Err(std::io::ErrorKind::OutOfMemory.into()),
-5 => Err(std::io::ErrorKind::BrokenPipe.into()),
-6 => Err(std::io::ErrorKind::BrokenPipe.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_usb_find_all: {result}"
))),
}
};
unsafe { libftdi1_sys::ftdi_list_free(&mut device_list) }
result
}
fn libusb_convert_result(&self, result: i32) -> std::io::Error {
if result == libusb1_sys::constants::LIBUSB_ERROR_OVERFLOW {
return std::io::Error::other("libusb overflow");
}
match result {
libusb1_sys::constants::LIBUSB_ERROR_IO => std::io::ErrorKind::UnexpectedEof,
libusb1_sys::constants::LIBUSB_ERROR_INVALID_PARAM => std::io::ErrorKind::InvalidInput,
libusb1_sys::constants::LIBUSB_ERROR_ACCESS => std::io::ErrorKind::PermissionDenied,
libusb1_sys::constants::LIBUSB_ERROR_NO_DEVICE => std::io::ErrorKind::NotConnected,
libusb1_sys::constants::LIBUSB_ERROR_NOT_FOUND => std::io::ErrorKind::NotFound,
libusb1_sys::constants::LIBUSB_ERROR_BUSY => std::io::ErrorKind::WouldBlock,
libusb1_sys::constants::LIBUSB_ERROR_TIMEOUT => std::io::ErrorKind::TimedOut,
libusb1_sys::constants::LIBUSB_ERROR_PIPE => std::io::ErrorKind::BrokenPipe,
libusb1_sys::constants::LIBUSB_ERROR_INTERRUPTED => std::io::ErrorKind::Interrupted,
libusb1_sys::constants::LIBUSB_ERROR_NO_MEM => std::io::ErrorKind::OutOfMemory,
libusb1_sys::constants::LIBUSB_ERROR_NOT_SUPPORTED => std::io::ErrorKind::Unsupported,
_ => std::io::ErrorKind::Other,
}
.into()
}
fn set_timeouts(
&mut self,
read_timeout: Option<std::time::Duration>,
write_timeout: Option<std::time::Duration>,
) -> std::io::Result<()> {
let read_timeout = read_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT);
let write_timeout = write_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT);
unsafe {
(*self.context).usb_read_timeout = i32::try_from(read_timeout.as_millis())
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
(*self.context).usb_write_timeout = i32::try_from(write_timeout.as_millis())
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
}
self.read_timeout = read_timeout;
self.write_timeout = write_timeout;
Ok(())
}
fn set_module_detach_mode(&mut self, mode: ModuleDetachMode) {
let mode = match mode {
ModuleDetachMode::AutoDetach => {
libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_SIO_MODULE
}
ModuleDetachMode::DontDetach => {
libftdi1_sys::ftdi_module_detach_mode::DONT_DETACH_SIO_MODULE
}
ModuleDetachMode::AutoDetachReattach => {
libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_REATACH_SIO_MODULE
}
};
unsafe {
(*self.context).module_detach_mode = mode;
};
}
fn set_interface(&mut self, interface: InterfaceIndex) -> std::io::Result<()> {
let interface = match interface {
InterfaceIndex::Any => libftdi1_sys::ftdi_interface::INTERFACE_ANY,
InterfaceIndex::A => libftdi1_sys::ftdi_interface::INTERFACE_A,
InterfaceIndex::B => libftdi1_sys::ftdi_interface::INTERFACE_B,
InterfaceIndex::C => libftdi1_sys::ftdi_interface::INTERFACE_C,
InterfaceIndex::D => libftdi1_sys::ftdi_interface::INTERFACE_D,
};
match unsafe { libftdi1_sys::ftdi_set_interface(self.context, interface) } {
0 => Ok(()),
-1 => Err(std::io::ErrorKind::InvalidInput.into()),
-2 => Err(std::io::ErrorKind::NotConnected.into()),
-3 => Err(std::io::ErrorKind::InvalidData.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_set_interface: {result}"
))),
}
}
fn usb_open_string(&mut self, description: &str) -> std::io::Result<()> {
let description = std::ffi::CString::new(description)
.unwrap_or_default()
.into_raw();
match unsafe { libftdi1_sys::ftdi_usb_open_string(self.context, description) } {
0 => Ok(()),
-2 => Err(std::io::ErrorKind::ConnectionRefused.into()),
-3 => Err(std::io::ErrorKind::NotFound.into()),
-4 => Err(std::io::ErrorKind::PermissionDenied.into()),
-5 => Err(std::io::ErrorKind::PermissionDenied.into()),
-6 => Err(std::io::ErrorKind::ConnectionRefused.into()),
-7 => Err(std::io::ErrorKind::ConnectionRefused.into()),
-8 => Err(std::io::ErrorKind::ConnectionRefused.into()),
-9 => Err(std::io::ErrorKind::ConnectionRefused.into()),
-10 => Err(std::io::ErrorKind::BrokenPipe.into()),
-11 => Err(std::io::ErrorKind::InvalidInput.into()),
-12 => Err(std::io::ErrorKind::InvalidData.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_usb_open_string: {result}"
))),
}
}
fn usb_reset(&mut self) -> std::io::Result<()> {
match unsafe { libftdi1_sys::ftdi_usb_reset(self.context) } {
0 => Ok(()),
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
-2 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_usb_reset: {result}"
))),
}
}
fn set_latency_timer(&mut self, latency: Option<std::time::Duration>) -> std::io::Result<()> {
let latency = u8::try_from(latency.unwrap_or(Self::DEFAULT_POLL_TIMEOUT).as_millis())
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
match unsafe { libftdi1_sys::ftdi_set_latency_timer(self.context, latency) } {
0 => Ok(()),
-1 => Err(std::io::ErrorKind::InvalidInput.into()),
-2 => Err(std::io::ErrorKind::BrokenPipe.into()),
-3 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_set_latency_timer: {result}"
))),
}
}
fn read_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> {
match unsafe {
libftdi1_sys::ftdi_read_data_set_chunksize(
self.context,
u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?,
)
} {
0 => {
self.read_chunksize = chunksize;
Ok(())
}
-1 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_read_data_set_chunksize: {result}"
))),
}
}
fn write_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> {
match unsafe {
libftdi1_sys::ftdi_write_data_set_chunksize(
self.context,
u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?,
)
} {
0 => {
self.write_chunksize = chunksize;
self.commit_write()
}
-1 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_write_data_set_chunksize: {result}"
))),
}
}
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
let state = if value { 1 } else { 0 };
match unsafe { libftdi1_sys::ftdi_setdtr(self.context, state) } {
0 => Ok(()),
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
-2 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_setdtr: {result}"
))),
}
}
fn poll_modem_status(&mut self) -> std::io::Result<ModemStatus> {
const DSR_BIT: u16 = 1 << 5;
let mut status = 0;
match unsafe { libftdi1_sys::ftdi_poll_modem_status(self.context, &mut status) } {
0 => Ok(ModemStatus {
dsr: (status & DSR_BIT) != 0,
}),
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
-2 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_poll_modem_status: {result}"
))),
}
}
fn tciflush(&mut self) -> std::io::Result<()> {
let timeout = std::time::Instant::now();
loop {
match self.read(&mut vec![0u8; self.read_chunksize]) {
Ok(_) => {}
Err(error) => match error.kind() {
std::io::ErrorKind::Interrupted
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::WouldBlock => {
return Ok(());
}
_ => return Err(error),
},
};
if timeout.elapsed() > self.read_timeout {
return Err(std::io::ErrorKind::TimedOut.into());
}
}
}
fn tcoflush(&mut self) -> std::io::Result<()> {
self.write_buffer.clear();
match unsafe { libftdi1_sys::ftdi_tcoflush(self.context) } {
0 => Ok(()),
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
-2 => Err(std::io::ErrorKind::BrokenPipe.into()),
-3 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(std::io::Error::other(format!(
"Unexpected response from ftdi_tcoflush: {result}"
))),
}
}
pub fn read_data(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
let length = i32::try_from(buffer.len()).map_err(|_| std::io::ErrorKind::InvalidInput)?;
let result =
unsafe { libftdi1_sys::ftdi_read_data(self.context, buffer.as_mut_ptr(), length) };
match result {
1.. => Ok(result as usize),
0 => Err(std::io::ErrorKind::WouldBlock.into()),
-666 => Err(std::io::ErrorKind::NotConnected.into()),
result => Err(self.libusb_convert_result(result)),
}
}
fn write_data(&mut self, buffer: &[u8], written: &mut usize) -> std::io::Result<()> {
let mut transferred = 0;
let result = unsafe {
// NOTE: Nasty hack to overcome libftdi1 API limitation.
// Write can partially succeed, but the default ftdi_write_data
// function doesn't report number of transferred bytes in that case.
libusb1_sys::libusb_bulk_transfer(
(*self.context).usb_dev,
(*self.context).in_ep as u8,
Vec::from(buffer).as_mut_ptr(),
buffer.len() as i32,
&mut transferred,
Self::WRITE_CHUNK_TIMEOUT.as_millis() as u32,
)
};
*written = transferred as usize;
if result < 0 {
return Err(self.libusb_convert_result(result));
}
Ok(())
}
fn unclog_pipe(&mut self) -> std::io::Result<()> {
let mut buffer = vec![0u8; self.read_chunksize];
let read = match self.read_data(&mut buffer) {
Ok(read) => read,
Err(error) => match error.kind() {
std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock => 0,
_ => return Err(error),
},
};
self.unclog_buffer.extend(buffer[0..read].iter());
Ok(())
}
fn commit_write(&mut self) -> std::io::Result<()> {
let timeout = std::time::Instant::now();
while !self.write_buffer.is_empty() {
let mut written = 0;
let result = self.write_data(&self.write_buffer.clone(), &mut written);
self.write_buffer.drain(..written);
if let Err(error) = result {
match error.kind() {
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
_ => return Err(error),
}
}
if timeout.elapsed() > self.write_timeout {
return Err(std::io::ErrorKind::TimedOut.into());
}
}
Ok(())
}
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
if buffer.is_empty() {
Err(std::io::ErrorKind::InvalidInput.into())
} else if self.unclog_buffer.is_empty() {
self.read_data(buffer)
} else {
for (index, item) in buffer.iter_mut().enumerate() {
if let Some(byte) = self.unclog_buffer.pop_front() {
*item = byte;
} else {
return Ok(index);
}
}
Ok(buffer.len())
}
}
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
let remaining_space = self.write_chunksize - self.write_buffer.len();
let length = buffer.len().min(remaining_space);
self.write_buffer.extend(&buffer[..length]);
if self.write_buffer.len() >= self.write_chunksize {
self.commit_write()?
}
Ok(length)
}
fn flush(&mut self) -> std::io::Result<()> {
self.commit_write()
}
}
impl Drop for Wrapper {
fn drop(&mut self) {
unsafe { libftdi1_sys::ftdi_free(self.context) }
}
}
pub struct FtdiDevice {
wrapper: Wrapper,
}
impl FtdiDevice {
pub fn list(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
Wrapper::list_devices(vendor, product)
}
pub fn open(
description: &str,
poll_timeout: Option<std::time::Duration>,
read_timeout: Option<std::time::Duration>,
write_timeout: Option<std::time::Duration>,
) -> std::io::Result<FtdiDevice> {
let mut wrapper = Wrapper::new(read_timeout, write_timeout)?;
wrapper.set_module_detach_mode(ModuleDetachMode::AutoDetachReattach);
wrapper.set_interface(InterfaceIndex::A)?;
const CHUNK_SIZE: usize = 16 * 1024;
wrapper.read_data_set_chunksize(CHUNK_SIZE)?;
wrapper.write_data_set_chunksize(CHUNK_SIZE)?;
wrapper.usb_open_string(description)?;
wrapper.usb_reset()?;
wrapper.set_latency_timer(poll_timeout)?;
Ok(FtdiDevice { wrapper })
}
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
self.wrapper.set_dtr(value)
}
pub fn read_dsr(&mut self) -> std::io::Result<bool> {
Ok(self.wrapper.poll_modem_status()?.dsr)
}
pub fn discard_input(&mut self) -> std::io::Result<()> {
self.wrapper.tciflush()
}
pub fn discard_output(&mut self) -> std::io::Result<()> {
self.wrapper.tcoflush()
}
}
impl std::io::Read for FtdiDevice {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
self.wrapper.read(buffer)
}
}
impl std::io::Write for FtdiDevice {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
self.wrapper.write(buffer)
}
fn flush(&mut self) -> std::io::Result<()> {
self.wrapper.flush()
}
}
impl Drop for FtdiDevice {
fn drop(&mut self) {
unsafe { libftdi1_sys::ftdi_usb_close(self.wrapper.context) };
}
}

View File

@ -1,10 +1,9 @@
use super::error::Error; use super::{error::Error, ftdi::FtdiDevice, serial::SerialDevice};
use serial2::SerialPort;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
io::{BufReader, BufWriter, ErrorKind, Read, Write}, fmt::Display,
io::{BufReader, BufWriter, Read, Write},
net::TcpStream, net::TcpStream,
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -51,82 +50,97 @@ pub struct Response {
pub error: bool, pub error: bool,
} }
pub struct Packet { pub struct AsynchronousPacket {
pub id: u8, pub id: u8,
pub data: Vec<u8>, pub data: Vec<u8>,
} }
pub struct Serial { pub enum UsbPacket {
serial: SerialPort, Response(Response),
AsynchronousPacket(AsynchronousPacket),
} }
impl Serial { const SERIAL_PREFIX: &str = "serial://";
fn reset(&self) -> Result<(), Error> { const FTDI_PREFIX: &str = "ftdi://";
const RESET_WAIT_DURATION: Duration = Duration::from_millis(10);
const RESET_RETRY_COUNT: i32 = 100;
const FLUSH_TIMEOUT: Duration = Duration::from_secs(1);
self.serial.set_dtr(true)?; const RESET_TIMEOUT: Duration = Duration::from_secs(1);
for n in 0..=RESET_RETRY_COUNT { const POLL_TIMEOUT: Duration = Duration::from_millis(5);
self.serial.discard_buffers()?; const READ_TIMEOUT: Duration = Duration::from_secs(5);
thread::sleep(RESET_WAIT_DURATION); const WRITE_TIMEOUT: Duration = Duration::from_secs(5);
if self.serial.read_dsr()? {
break;
}
if n == RESET_RETRY_COUNT {
return Err(Error::new("Couldn't reset SC64 device (on)"));
}
}
let flush_timeout = Instant::now(); pub trait Backend {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize>;
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()>;
fn flush(&mut self) -> std::io::Result<()>;
fn discard_input(&mut self) -> std::io::Result<()> {
Ok(())
}
fn discard_output(&mut self) -> std::io::Result<()> {
Ok(())
}
fn set_dtr(&mut self, _value: bool) -> std::io::Result<()> {
Ok(())
}
fn read_dsr(&mut self) -> std::io::Result<bool> {
Ok(false)
}
fn close(&mut self) {}
fn reset(&mut self) -> std::io::Result<()> {
self.discard_output()?;
let timeout = Instant::now();
self.set_dtr(true)?;
loop { loop {
match self.serial.read(&mut vec![0; 1]) { if self.read_dsr()? {
Ok(length) => match length { break;
0 => break,
_ => {}
},
Err(error) => match error.kind() {
ErrorKind::TimedOut => break,
_ => {
return Err(Error::new(
format!("Couldn't flush SC64 serial buffer: {error}").as_str(),
))
}
},
} }
if flush_timeout.elapsed() >= FLUSH_TIMEOUT { if timeout.elapsed() > RESET_TIMEOUT {
return Err(Error::new("SC64 serial buffer flush took too long")); return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Couldn't reset SC64 device (on)",
));
} }
} }
self.serial.set_dtr(false)?; self.discard_input()?;
for n in 0..=RESET_RETRY_COUNT {
thread::sleep(RESET_WAIT_DURATION); let timeout = Instant::now();
if !self.serial.read_dsr()? { self.set_dtr(false)?;
loop {
if !self.read_dsr()? {
break; break;
} }
if n == RESET_RETRY_COUNT { if timeout.elapsed() > RESET_TIMEOUT {
return Err(Error::new("Couldn't reset SC64 device (off)")); return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Couldn't reset SC64 device (off)",
));
} }
} }
Ok(()) Ok(())
} }
fn read_data(&self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> { fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result<Option<()>> {
let timeout = Instant::now();
let mut position = 0; let mut position = 0;
let length = buffer.len(); let length = buffer.len();
let timeout = Instant::now();
while position < length { while position < length {
if timeout.elapsed() > Duration::from_secs(10) { match self.read(&mut buffer[position..length]) {
return Err(Error::new("Serial read timeout")); Ok(0) => return Err(std::io::ErrorKind::UnexpectedEof.into()),
}
match self.serial.read(&mut buffer[position..length]) {
Ok(0) => return Err(Error::new("Unexpected end of serial data")),
Ok(bytes) => position += bytes, Ok(bytes) => position += bytes,
Err(error) => match error.kind() { Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => { std::io::ErrorKind::Interrupted
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::WouldBlock => {
if !block && position == 0 { if !block && position == 0 {
return Ok(None); return Ok(None);
} }
@ -134,48 +148,53 @@ impl Serial {
_ => return Err(error.into()), _ => return Err(error.into()),
}, },
} }
if timeout.elapsed() > READ_TIMEOUT {
return Err(std::io::ErrorKind::TimedOut.into());
}
} }
Ok(Some(())) Ok(Some(()))
} }
fn read_exact(&self, buffer: &mut [u8]) -> Result<(), Error> { fn try_read_header(&mut self, block: bool) -> std::io::Result<Option<[u8; 4]>> {
match self.read_data(buffer, true)? { let mut header = [0u8; 4];
Ok(self.try_read_exact(&mut header, block)?.map(|_| header))
}
fn read_exact(&mut self, buffer: &mut [u8]) -> std::io::Result<()> {
match self.try_read_exact(buffer, true)? {
Some(()) => Ok(()), Some(()) => Ok(()),
None => Err(Error::new("Unexpected end of serial data")), None => Err(std::io::ErrorKind::UnexpectedEof.into()),
} }
} }
fn read_header(&self, block: bool) -> Result<Option<[u8; 4]>, Error> { fn send_command(&mut self, command: &Command) -> std::io::Result<()> {
let mut header = [0u8; 4]; self.write_all(b"CMD")?;
Ok(self.read_data(&mut header, block)?.map(|_| header)) self.write_all(&command.id.to_be_bytes())?;
}
pub fn send_command(&self, command: &Command) -> Result<(), Error> { self.write_all(&command.args[0].to_be_bytes())?;
self.serial.write_all(b"CMD")?; self.write_all(&command.args[1].to_be_bytes())?;
self.serial.write_all(&command.id.to_be_bytes())?;
self.serial.write_all(&command.args[0].to_be_bytes())?;
self.serial.write_all(&command.args[1].to_be_bytes())?;
self.serial.write_all(&command.data)?; self.write_all(&command.data)?;
self.serial.flush()?; self.flush()?;
Ok(()) Ok(())
} }
pub fn process_incoming_data( fn process_incoming_data(
&self, &mut self,
data_type: DataType, data_type: DataType,
packets: &mut VecDeque<Packet>, packets: &mut VecDeque<AsynchronousPacket>,
) -> Result<Option<Response>, Error> { ) -> std::io::Result<Option<Response>> {
let block = matches!(data_type, DataType::Response); let block = matches!(data_type, DataType::Response);
while let Some(header) = self.read_header(block)? {
let (packet_token, error) = (match &header[0..3] { while let Some(header) = self.try_read_header(block)? {
b"CMP" => Ok((false, false)), let (packet_token, error) = match &header[0..3] {
b"PKT" => Ok((true, false)), b"CMP" => (false, false),
b"ERR" => Ok((false, true)), b"PKT" => (true, false),
_ => Err(Error::new("Unknown response token")), b"ERR" => (false, true),
})?; _ => return Err(std::io::ErrorKind::InvalidData.into()),
};
let id = header[3]; let id = header[3];
let mut buffer = [0u8; 4]; let mut buffer = [0u8; 4];
@ -187,7 +206,7 @@ impl Serial {
self.read_exact(&mut data)?; self.read_exact(&mut data)?;
if packet_token { if packet_token {
packets.push_back(Packet { id, data }); packets.push_back(AsynchronousPacket { id, data });
if matches!(data_type, DataType::Packet) { if matches!(data_type, DataType::Packet) {
break; break;
} }
@ -200,48 +219,94 @@ impl Serial {
} }
} }
pub fn new_serial(port: &str) -> Result<Serial, Error> { pub struct SerialBackend {
let mut serial = SerialPort::open(port, 115_200)?; device: SerialDevice,
serial.set_write_timeout(Duration::from_secs(10))?;
serial.set_read_timeout(Duration::from_millis(10))?;
let backend = Serial { serial };
backend.reset()?;
Ok(backend)
}
trait Backend {
fn send_command(&mut self, command: &Command) -> Result<(), Error>;
fn process_incoming_data(
&mut self,
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error>;
fn close(&self) {}
}
struct SerialBackend {
inner: Serial,
} }
impl Backend for SerialBackend { impl Backend for SerialBackend {
fn send_command(&mut self, command: &Command) -> Result<(), Error> { fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
self.inner.send_command(command) self.device.read(buffer)
} }
fn process_incoming_data( fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
&mut self, self.device.write_all(buffer)
data_type: DataType, }
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error> { fn flush(&mut self) -> std::io::Result<()> {
self.inner.process_incoming_data(data_type, packets) self.device.flush()
}
fn discard_input(&mut self) -> std::io::Result<()> {
self.device.discard_input()
}
fn discard_output(&mut self) -> std::io::Result<()> {
self.device.discard_output()
}
fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
self.device.set_dtr(value)
}
fn read_dsr(&mut self) -> std::io::Result<bool> {
self.device.read_dsr()
} }
} }
fn new_serial_backend(port: &str) -> Result<SerialBackend, Error> { fn new_serial_backend(port: &str) -> std::io::Result<SerialBackend> {
let backend = SerialBackend { Ok(SerialBackend {
inner: new_serial(port)?, device: SerialDevice::new(
}; port,
Ok(backend) Some(POLL_TIMEOUT),
Some(READ_TIMEOUT),
Some(WRITE_TIMEOUT),
)?,
})
}
struct FtdiBackend {
device: FtdiDevice,
}
impl Backend for FtdiBackend {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
self.device.read(buffer)
}
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
self.device.write_all(buffer)
}
fn flush(&mut self) -> std::io::Result<()> {
self.device.flush()
}
fn discard_input(&mut self) -> std::io::Result<()> {
self.device.discard_input()
}
fn discard_output(&mut self) -> std::io::Result<()> {
self.device.discard_output()
}
fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
self.device.set_dtr(value)
}
fn read_dsr(&mut self) -> std::io::Result<bool> {
self.device.read_dsr()
}
}
fn new_ftdi_backend(port: &str) -> std::io::Result<FtdiBackend> {
Ok(FtdiBackend {
device: FtdiDevice::open(
port,
Some(POLL_TIMEOUT),
Some(READ_TIMEOUT),
Some(WRITE_TIMEOUT),
)?,
})
} }
struct TcpBackend { struct TcpBackend {
@ -250,58 +315,36 @@ struct TcpBackend {
writer: BufWriter<TcpStream>, writer: BufWriter<TcpStream>,
} }
impl TcpBackend {
fn read_data(&mut self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
let timeout = Instant::now();
let mut position = 0;
let length = buffer.len();
while position < length {
if timeout.elapsed() > Duration::from_secs(10) {
return Err(Error::new("Stream read timeout"));
}
match self.reader.read(&mut buffer[position..length]) {
Ok(0) => return Err(Error::new("Unexpected end of stream data")),
Ok(bytes) => position += bytes,
Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {
if !block && position == 0 {
return Ok(None);
}
}
_ => return Err(error.into()),
},
}
}
Ok(Some(()))
}
fn read_exact(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
match self.read_data(buffer, true)? {
Some(()) => Ok(()),
None => Err(Error::new("Unexpected end of stream data")),
}
}
fn read_header(&mut self, block: bool) -> Result<Option<[u8; 4]>, Error> {
let mut header = [0u8; 4];
Ok(self.read_data(&mut header, block)?.map(|_| header))
}
}
impl Backend for TcpBackend { impl Backend for TcpBackend {
fn send_command(&mut self, command: &Command) -> Result<(), Error> { fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
let payload_data_type: u32 = DataType::Command.into(); self.reader.read(buffer)
self.writer.write_all(&payload_data_type.to_be_bytes())?; }
self.writer.write_all(&command.id.to_be_bytes())?; fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
self.writer.write_all(&command.args[0].to_be_bytes())?; self.writer.write_all(buffer)
self.writer.write_all(&command.args[1].to_be_bytes())?; }
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
fn close(&mut self) {
self.stream.shutdown(std::net::Shutdown::Both).ok();
}
fn send_command(&mut self, command: &Command) -> std::io::Result<()> {
let payload_data_type: u32 = DataType::Command.into();
self.write_all(&payload_data_type.to_be_bytes())?;
self.write_all(&command.id.to_be_bytes())?;
self.write_all(&command.args[0].to_be_bytes())?;
self.write_all(&command.args[1].to_be_bytes())?;
let command_data_length = command.data.len() as u32; let command_data_length = command.data.len() as u32;
self.writer.write_all(&command_data_length.to_be_bytes())?; self.write_all(&command_data_length.to_be_bytes())?;
self.writer.write_all(&command.data)?; self.write_all(&command.data)?;
self.writer.flush()?; self.flush()?;
Ok(()) Ok(())
} }
@ -309,11 +352,13 @@ impl Backend for TcpBackend {
fn process_incoming_data( fn process_incoming_data(
&mut self, &mut self,
data_type: DataType, data_type: DataType,
packets: &mut VecDeque<Packet>, packets: &mut VecDeque<AsynchronousPacket>,
) -> Result<Option<Response>, Error> { ) -> std::io::Result<Option<Response>> {
let block = matches!(data_type, DataType::Response); let block = matches!(data_type, DataType::Response);
while let Some(header) = self.read_header(block)? { while let Some(header) = self.try_read_header(block)? {
let payload_data_type: DataType = u32::from_be_bytes(header).try_into()?; let payload_data_type: DataType = u32::from_be_bytes(header)
.try_into()
.map_err(|_| std::io::ErrorKind::InvalidData)?;
let mut buffer = [0u8; 4]; let mut buffer = [0u8; 4];
match payload_data_type { match payload_data_type {
DataType::Response => { DataType::Response => {
@ -342,7 +387,7 @@ impl Backend for TcpBackend {
let mut data = vec![0u8; packet_data_length]; let mut data = vec![0u8; packet_data_length];
self.read_exact(&mut data)?; self.read_exact(&mut data)?;
packets.push_back(Packet { packets.push_back(AsynchronousPacket {
id: packet_info[0], id: packet_info[0],
data, data,
}); });
@ -351,31 +396,20 @@ impl Backend for TcpBackend {
} }
} }
DataType::KeepAlive => {} DataType::KeepAlive => {}
_ => return Err(Error::new("Unexpected payload data type received")), _ => return Err(std::io::ErrorKind::InvalidData.into()),
}; };
} }
Ok(None) Ok(None)
} }
fn close(&self) {
self.stream.shutdown(std::net::Shutdown::Both).ok();
}
} }
fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> { fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
let stream = match TcpStream::connect(address) { let stream = TcpStream::connect(address).map_err(|error| {
Ok(stream) => { Error::new(format!("Couldn't connect to [{address}]: {error}").as_str())
stream.set_write_timeout(Some(Duration::from_secs(10)))?; })?;
stream.set_read_timeout(Some(Duration::from_millis(10)))?; stream.set_read_timeout(Some(POLL_TIMEOUT))?;
stream stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
}
Err(error) => {
return Err(Error::new(
format!("Couldn't connect to [{address}]: {error}").as_str(),
))
}
};
let reader = BufReader::new(stream.try_clone()?); let reader = BufReader::new(stream.try_clone()?);
let writer = BufWriter::new(stream.try_clone()?); let writer = BufWriter::new(stream.try_clone()?);
Ok(TcpBackend { Ok(TcpBackend {
@ -385,9 +419,29 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
}) })
} }
fn new_local_backend(port: &str) -> Result<Box<dyn Backend>, Error> {
let mut backend: Box<dyn Backend> = if port.starts_with(SERIAL_PREFIX) {
Box::new(new_serial_backend(
port.strip_prefix(SERIAL_PREFIX).unwrap_or_default(),
)?)
} else if port.starts_with(FTDI_PREFIX) {
Box::new(new_ftdi_backend(
port.strip_prefix(FTDI_PREFIX).unwrap_or_default(),
)?)
} else {
return Err(Error::new("Invalid port prefix provided"));
};
backend.reset()?;
Ok(backend)
}
fn new_remote_backend(address: &str) -> Result<Box<dyn Backend>, Error> {
Ok(Box::new(new_tcp_backend(address)?))
}
pub struct Link { pub struct Link {
backend: Box<dyn Backend>, backend: Box<dyn Backend>,
packets: VecDeque<Packet>, packets: VecDeque<AsynchronousPacket>,
} }
impl Link { impl Link {
@ -415,7 +469,7 @@ impl Link {
Ok(response.data) Ok(response.data)
} }
fn receive_response(&mut self) -> Result<Response, Error> { pub fn receive_response(&mut self) -> Result<Response, Error> {
match self match self
.backend .backend
.process_incoming_data(DataType::Response, &mut self.packets) .process_incoming_data(DataType::Response, &mut self.packets)
@ -430,7 +484,7 @@ impl Link {
} }
} }
pub fn receive_packet(&mut self) -> Result<Option<Packet>, Error> { pub fn receive_packet(&mut self) -> Result<Option<AsynchronousPacket>, Error> {
if self.packets.len() == 0 { if self.packets.len() == 0 {
let response = self let response = self
.backend .backend
@ -441,6 +495,19 @@ impl Link {
} }
Ok(self.packets.pop_front()) Ok(self.packets.pop_front())
} }
pub fn receive_response_or_packet(&mut self) -> Result<Option<UsbPacket>, Error> {
let response = self
.backend
.process_incoming_data(DataType::Packet, &mut self.packets)?;
if let Some(response) = response {
return Ok(Some(UsbPacket::Response(response)));
}
if let Some(packet) = self.packets.pop_front() {
return Ok(Some(UsbPacket::AsynchronousPacket(packet)));
}
Ok(None)
}
} }
impl Drop for Link { impl Drop for Link {
@ -451,45 +518,75 @@ impl Drop for Link {
pub fn new_local(port: &str) -> Result<Link, Error> { pub fn new_local(port: &str) -> Result<Link, Error> {
Ok(Link { Ok(Link {
backend: Box::new(new_serial_backend(port)?), backend: new_local_backend(port)?,
packets: VecDeque::new(), packets: VecDeque::new(),
}) })
} }
pub fn new_remote(address: &str) -> Result<Link, Error> { pub fn new_remote(address: &str) -> Result<Link, Error> {
Ok(Link { Ok(Link {
backend: Box::new(new_tcp_backend(address)?), backend: new_remote_backend(address)?,
packets: VecDeque::new(), packets: VecDeque::new(),
}) })
} }
pub struct LocalDevice { pub enum BackendType {
pub port: String, Serial,
pub serial_number: String, Ftdi,
} }
pub fn list_local_devices() -> Result<Vec<LocalDevice>, Error> { impl Display for BackendType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Serial => "serial",
Self::Ftdi => "libftdi",
})
}
}
pub struct DeviceInfo {
pub backend: BackendType,
pub port: String,
pub serial: String,
}
pub fn list_local_devices() -> Result<Vec<DeviceInfo>, Error> {
const SC64_VID: u16 = 0x0403; const SC64_VID: u16 = 0x0403;
const SC64_PID: u16 = 0x6014; const SC64_PID: u16 = 0x6014;
const SC64_SID: &str = "SC64"; const SC64_SERIAL_PREFIX: &str = "SC64";
const SC64_DESCRIPTION: &str = "SC64";
let mut serial_devices: Vec<LocalDevice> = Vec::new(); let mut devices: Vec<DeviceInfo> = Vec::new();
for device in serialport::available_ports()?.into_iter() { if let Ok(list) = FtdiDevice::list(SC64_VID, SC64_PID) {
if let serialport::SerialPortType::UsbPort(info) = device.port_type { for device in list.into_iter() {
let serial_number = info.serial_number.unwrap_or("".to_string()); if device.description == SC64_DESCRIPTION {
if info.vid == SC64_VID && info.pid == SC64_PID && serial_number.starts_with(SC64_SID) { devices.push(DeviceInfo {
serial_devices.push(LocalDevice { backend: BackendType::Ftdi,
port: device.port_name, port: format!("{FTDI_PREFIX}{}", device.port),
serial_number, serial: device.serial,
})
}
}
}
if let Ok(list) = SerialDevice::list(SC64_VID, SC64_PID) {
for device in list.into_iter() {
let is_sc64_device = device.description == SC64_DESCRIPTION
|| device.serial.starts_with(SC64_SERIAL_PREFIX);
if is_sc64_device {
devices.push(DeviceInfo {
backend: BackendType::Serial,
port: format!("{SERIAL_PREFIX}{}", device.port),
serial: device.serial,
}); });
} }
} }
} }
if serial_devices.len() == 0 { if devices.len() == 0 {
return Err(Error::new("No SC64 devices found")); return Err(Error::new("No SC64 devices found"));
} }
return Ok(serial_devices); return Ok(devices);
} }

View File

@ -1,7 +1,9 @@
mod cic; mod cic;
mod error; mod error;
pub mod firmware; pub mod firmware;
mod ftdi;
mod link; mod link;
mod serial;
pub mod server; pub mod server;
mod time; mod time;
mod types; mod types;
@ -12,8 +14,8 @@ pub use self::{
server::ServerEvent, server::ServerEvent,
types::{ types::{
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode,
DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, MemoryTestPattern, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, ISViewer,
MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType, MemoryTestPattern, MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType,
}, },
}; };
@ -43,7 +45,7 @@ pub struct DeviceState {
pub rom_write_enable: Switch, pub rom_write_enable: Switch,
pub rom_shadow_enable: Switch, pub rom_shadow_enable: Switch,
pub dd_mode: DdMode, pub dd_mode: DdMode,
pub isv_address: u32, pub isviewer: ISViewer,
pub boot_mode: BootMode, pub boot_mode: BootMode,
pub save_type: SaveType, pub save_type: SaveType,
pub cic_seed: CicSeed, pub cic_seed: CicSeed,
@ -547,7 +549,7 @@ impl SC64 {
rom_write_enable: get_config!(self, RomWriteEnable)?, rom_write_enable: get_config!(self, RomWriteEnable)?,
rom_shadow_enable: get_config!(self, RomShadowEnable)?, rom_shadow_enable: get_config!(self, RomShadowEnable)?,
dd_mode: get_config!(self, DdMode)?, dd_mode: get_config!(self, DdMode)?,
isv_address: get_config!(self, IsvAddress)?, isviewer: get_config!(self, ISViewer)?,
boot_mode: get_config!(self, BootMode)?, boot_mode: get_config!(self, BootMode)?,
save_type: get_config!(self, SaveType)?, save_type: get_config!(self, SaveType)?,
cic_seed: get_config!(self, CicSeed)?, cic_seed: get_config!(self, CicSeed)?,
@ -597,10 +599,10 @@ impl SC64 {
} }
} }
self.command_config_set(Config::RomWriteEnable(Switch::On))?; self.command_config_set(Config::RomWriteEnable(Switch::On))?;
self.command_config_set(Config::IsvAddress(offset))?; self.command_config_set(Config::ISViewer(ISViewer::Enabled(offset)))?;
} else { } else {
self.command_config_set(Config::RomWriteEnable(Switch::Off))?; self.command_config_set(Config::RomWriteEnable(Switch::Off))?;
self.command_config_set(Config::IsvAddress(0))?; self.command_config_set(Config::ISViewer(ISViewer::Disabled))?;
} }
Ok(()) Ok(())
} }
@ -875,13 +877,8 @@ impl SC64 {
impl SC64 { impl SC64 {
pub fn open_local(port: Option<String>) -> Result<Self, Error> { pub fn open_local(port: Option<String>) -> Result<Self, Error> {
let port = if let Some(port) = port {
port
} else {
list_local_devices()?[0].port.clone()
};
let mut sc64 = SC64 { let mut sc64 = SC64 {
link: link::new_local(&port)?, link: link::new_local(&port.unwrap_or(list_local_devices()?[0].port.clone()))?,
}; };
sc64.check_device()?; sc64.check_device()?;
Ok(sc64) Ok(sc64)

View File

@ -0,0 +1,159 @@
pub struct DeviceInfo {
pub description: String,
pub serial: String,
pub port: String,
}
pub struct SerialDevice {
serial: serial2::SerialPort,
unclog_buffer: std::collections::VecDeque<u8>,
poll_timeout: std::time::Duration,
read_timeout: std::time::Duration,
write_timeout: std::time::Duration,
}
impl SerialDevice {
const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16);
const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
const BUFFER_SIZE: usize = 16 * 1024;
pub fn new(
port: &str,
poll_timeout: Option<std::time::Duration>,
read_timeout: Option<std::time::Duration>,
write_timeout: Option<std::time::Duration>,
) -> std::io::Result<Self> {
let mut device = Self {
serial: serial2::SerialPort::open(port, 115_200)?,
unclog_buffer: std::collections::VecDeque::new(),
poll_timeout: poll_timeout.unwrap_or(Self::DEFAULT_POLL_TIMEOUT),
read_timeout: read_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT),
write_timeout: write_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT),
};
device.serial.set_read_timeout(device.poll_timeout)?;
device.serial.set_write_timeout(Self::WRITE_CHUNK_TIMEOUT)?;
Ok(device)
}
pub fn list(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
let mut devices = vec![];
for port in serialport::available_ports()? {
if let serialport::SerialPortType::UsbPort(info) = port.port_type {
if info.vid == vendor && info.pid == product {
devices.push(DeviceInfo {
description: info.product.unwrap_or_default(),
serial: info.serial_number.unwrap_or_default(),
port: port.port_name,
})
}
}
}
devices.sort_by(|a, b| a.serial.cmp(&b.serial));
Ok(devices)
}
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
self.serial.set_dtr(value)
}
pub fn read_dsr(&mut self) -> std::io::Result<bool> {
self.serial.read_dsr()
}
pub fn discard_input(&mut self) -> std::io::Result<()> {
let timeout = std::time::Instant::now();
self.serial.discard_input_buffer()?;
loop {
match self.serial.read(&mut vec![0u8; Self::BUFFER_SIZE]) {
Ok(_) => {}
Err(error) => match error.kind() {
std::io::ErrorKind::Interrupted
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::WouldBlock => {
return Ok(());
}
_ => return Err(error),
},
};
if timeout.elapsed() > self.read_timeout {
return Err(std::io::ErrorKind::TimedOut.into());
}
}
}
pub fn discard_output(&mut self) -> std::io::Result<()> {
self.serial.discard_output_buffer()
}
fn unclog_pipe(&mut self) -> std::io::Result<()> {
let mut buffer = vec![0u8; Self::BUFFER_SIZE];
let read = match self.serial.read(&mut buffer) {
Ok(read) => read,
Err(error) => match error.kind() {
std::io::ErrorKind::Interrupted
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::WouldBlock => 0,
_ => return Err(error),
},
};
self.unclog_buffer.extend(buffer[0..read].iter());
Ok(())
}
}
impl std::io::Read for SerialDevice {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
if buffer.is_empty() {
Err(std::io::ErrorKind::InvalidInput.into())
} else if self.unclog_buffer.is_empty() {
self.serial.read(buffer)
} else {
for (index, item) in buffer.iter_mut().enumerate() {
if let Some(byte) = self.unclog_buffer.pop_front() {
*item = byte;
} else {
return Ok(index);
}
}
Ok(buffer.len())
}
}
}
impl std::io::Write for SerialDevice {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
let timeout = std::time::Instant::now();
loop {
match self.serial.write(buffer) {
Ok(bytes) => return Ok(bytes),
Err(error) => match error.kind() {
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
_ => return Err(error),
},
};
if timeout.elapsed() > self.write_timeout {
return Err(std::io::ErrorKind::TimedOut.into());
}
}
}
fn flush(&mut self) -> std::io::Result<()> {
let timeout = std::time::Instant::now();
loop {
match self.serial.flush() {
Ok(()) => return Ok(()),
Err(error) => match error.kind() {
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
_ => return Err(error),
},
};
if timeout.elapsed() > self.write_timeout {
return Err(std::io::ErrorKind::TimedOut.into());
}
}
}
}

View File

@ -1,19 +1,10 @@
use super::{ use super::{
error::Error, error::Error,
link::{list_local_devices, new_serial, Command, DataType, Packet, Response, Serial}, link::{
}; list_local_devices, new_local, AsynchronousPacket, Command, DataType, Response, UsbPacket,
use std::{
collections::VecDeque,
io::{BufReader, BufWriter, ErrorKind, Read, Write},
net::{TcpListener, TcpStream},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{channel, Receiver, Sender},
Arc,
}, },
thread,
time::{Duration, Instant},
}; };
use std::io::{Read, Write};
pub enum ServerEvent { pub enum ServerEvent {
Listening(String), Listening(String),
@ -22,237 +13,160 @@ pub enum ServerEvent {
Err(String), Err(String),
} }
struct StreamHandler {
stream: std::net::TcpStream,
reader: std::io::BufReader<std::net::TcpStream>,
writer: std::io::BufWriter<std::net::TcpStream>,
}
const READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const WRITE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const KEEPALIVE_PERIOD: std::time::Duration = std::time::Duration::from_secs(5);
impl StreamHandler {
fn new(stream: std::net::TcpStream) -> std::io::Result<StreamHandler> {
let reader = std::io::BufReader::new(stream.try_clone()?);
let writer = std::io::BufWriter::new(stream.try_clone()?);
stream.set_read_timeout(Some(READ_TIMEOUT))?;
stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
Ok(StreamHandler {
stream,
reader,
writer,
})
}
fn try_read_header(&mut self) -> std::io::Result<Option<[u8; 4]>> {
self.stream.set_nonblocking(true)?;
let mut header = [0u8; 4];
let result = match self.reader.read_exact(&mut header) {
Ok(()) => Ok(Some(header)),
Err(error) => match error.kind() {
std::io::ErrorKind::WouldBlock => Ok(None),
_ => Err(error),
},
};
self.stream.set_nonblocking(false)?;
result
}
fn receive_command(&mut self) -> std::io::Result<Option<Command>> {
if let Some(header) = self.try_read_header()? {
if let Ok(data_type) = TryInto::<DataType>::try_into(u32::from_be_bytes(header)) {
if !matches!(data_type, DataType::Command) {
return Err(std::io::Error::other(
"Received data type was not a command data type",
));
}
}
let mut buffer = [0u8; 4];
let mut id_buffer = [0u8; 1];
let mut args = [0u32; 2];
self.reader.read_exact(&mut id_buffer)?;
let id = id_buffer[0];
self.reader.read_exact(&mut buffer)?;
args[0] = u32::from_be_bytes(buffer);
self.reader.read_exact(&mut buffer)?;
args[1] = u32::from_be_bytes(buffer);
self.reader.read_exact(&mut buffer)?;
let command_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; command_data_length];
self.reader.read_exact(&mut data)?;
Ok(Some(Command { id, args, data }))
} else {
Ok(None)
}
}
fn send_response(&mut self, response: Response) -> std::io::Result<()> {
self.writer
.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
self.writer.write_all(&[response.id])?;
self.writer.write_all(&[response.error as u8])?;
self.writer
.write_all(&(response.data.len() as u32).to_be_bytes())?;
self.writer.write_all(&response.data)?;
self.writer.flush()?;
Ok(())
}
fn send_packet(&mut self, packet: AsynchronousPacket) -> std::io::Result<()> {
self.writer
.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
self.writer.write_all(&[packet.id])?;
self.writer
.write_all(&(packet.data.len() as u32).to_be_bytes())?;
self.writer.write_all(&packet.data)?;
self.writer.flush()?;
Ok(())
}
fn send_keepalive(&mut self) -> std::io::Result<()> {
self.writer
.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
self.writer.flush()?;
Ok(())
}
}
fn server_accept_connection(port: String, connection: &mut StreamHandler) -> Result<(), Error> {
let mut link = new_local(&port)?;
let mut keepalive = std::time::Instant::now();
loop {
match connection.receive_command() {
Ok(Some(command)) => {
link.execute_command_raw(&command, true, true)?;
}
Ok(None) => {}
Err(error) => match error.kind() {
std::io::ErrorKind::UnexpectedEof => return Ok(()),
_ => return Err(error.into()),
},
};
if let Some(usb_packet) = link.receive_response_or_packet()? {
match usb_packet {
UsbPacket::Response(response) => connection.send_response(response)?,
UsbPacket::AsynchronousPacket(packet) => connection.send_packet(packet)?,
}
}
if keepalive.elapsed() > KEEPALIVE_PERIOD {
keepalive = std::time::Instant::now();
connection.send_keepalive().ok();
}
}
}
pub fn run( pub fn run(
port: Option<String>, port: Option<String>,
address: String, address: String,
event_callback: fn(ServerEvent), event_callback: fn(ServerEvent),
) -> Result<(), Error> { ) -> Result<(), Error> {
let port = if let Some(port) = port { let port = port.unwrap_or(list_local_devices()?[0].port.clone());
port let listener = std::net::TcpListener::bind(address)?;
} else {
list_local_devices()?[0].port.clone()
};
let listener = TcpListener::bind(address)?;
let listening_address = listener.local_addr()?; let listening_address = listener.local_addr()?;
event_callback(ServerEvent::Listening(listening_address.to_string())); event_callback(ServerEvent::Listening(listening_address.to_string()));
for stream in listener.incoming() { for incoming in listener.incoming() {
match stream { let stream = incoming?;
Ok(mut stream) => { let peer = stream.peer_addr()?.to_string();
let peer = stream.peer_addr()?.to_string();
event_callback(ServerEvent::Connected(peer.clone())); event_callback(ServerEvent::Connected(peer.clone()));
match server_accept_connection(port.clone(), &mut stream) {
Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())), match server_accept_connection(port.clone(), &mut StreamHandler::new(stream)?) {
Err(error) => event_callback(ServerEvent::Err(error.to_string())), Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())),
} Err(error) => event_callback(ServerEvent::Err(error.to_string())),
}
Err(error) => match error.kind() {
_ => return Err(error.into()),
},
} }
} }
Ok(()) Ok(())
} }
enum Event {
Command(Command),
Response(Response),
Packet(Packet),
KeepAlive,
Closed(Option<Error>),
}
fn server_accept_connection(port: String, stream: &mut TcpStream) -> Result<(), Error> {
let (event_sender, event_receiver) = channel::<Event>();
let exit_flag = Arc::new(AtomicBool::new(false));
let mut stream_writer = BufWriter::new(stream.try_clone()?);
let mut stream_reader = stream.try_clone()?;
let serial = Arc::new(new_serial(&port)?);
let serial_writer = serial.clone();
let serial_reader = serial.clone();
let stream_event_sender = event_sender.clone();
let stream_exit_flag = exit_flag.clone();
let stream_thread = thread::spawn(move || {
let closed_sender = stream_event_sender.clone();
match server_stream_thread(&mut stream_reader, stream_event_sender, stream_exit_flag) {
Ok(()) => closed_sender.send(Event::Closed(None)),
Err(error) => closed_sender.send(Event::Closed(Some(error))),
}
.ok();
});
let serial_event_sender = event_sender.clone();
let serial_exit_flag = exit_flag.clone();
let serial_thread = thread::spawn(move || {
let closed_sender = serial_event_sender.clone();
match server_serial_thread(serial_reader, serial_event_sender, serial_exit_flag) {
Ok(()) => closed_sender.send(Event::Closed(None)),
Err(error) => closed_sender.send(Event::Closed(Some(error))),
}
.ok();
});
let keepalive_event_sender = event_sender.clone();
let keepalive_exit_flag = exit_flag.clone();
let keepalive_thread = thread::spawn(move || {
server_keepalive_thread(keepalive_event_sender, keepalive_exit_flag);
});
let result = server_process_events(&mut stream_writer, serial_writer, event_receiver);
exit_flag.store(true, Ordering::Relaxed);
stream_thread.join().ok();
serial_thread.join().ok();
keepalive_thread.join().ok();
result
}
fn server_process_events(
stream_writer: &mut BufWriter<TcpStream>,
serial_writer: Arc<Serial>,
event_receiver: Receiver<Event>,
) -> Result<(), Error> {
for event in event_receiver.into_iter() {
match event {
Event::Command(command) => {
serial_writer.send_command(&command)?;
}
Event::Response(response) => {
stream_writer.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
stream_writer.write_all(&[response.id])?;
stream_writer.write_all(&[response.error as u8])?;
stream_writer.write_all(&(response.data.len() as u32).to_be_bytes())?;
stream_writer.write_all(&response.data)?;
stream_writer.flush()?;
}
Event::Packet(packet) => {
stream_writer.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
stream_writer.write_all(&[packet.id])?;
stream_writer.write_all(&(packet.data.len() as u32).to_be_bytes())?;
stream_writer.write_all(&packet.data)?;
stream_writer.flush()?;
}
Event::KeepAlive => {
stream_writer.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
stream_writer.flush()?;
}
Event::Closed(result) => match result {
Some(error) => return Err(error),
None => {
break;
}
},
}
}
Ok(())
}
fn server_stream_thread(
stream: &mut TcpStream,
event_sender: Sender<Event>,
exit_flag: Arc<AtomicBool>,
) -> Result<(), Error> {
let mut stream_reader = BufReader::new(stream.try_clone()?);
let mut header = [0u8; 4];
let header_length = header.len();
loop {
let mut header_position = 0;
let timeout = stream.read_timeout()?;
stream.set_read_timeout(Some(Duration::from_millis(10)))?;
while header_position < header_length {
if exit_flag.load(Ordering::Relaxed) {
return Ok(());
}
match stream_reader.read(&mut header[header_position..header_length]) {
Ok(0) => return Ok(()),
Ok(bytes) => header_position += bytes,
Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {}
_ => return Err(error.into()),
},
}
}
stream.set_read_timeout(timeout)?;
let data_type: DataType = u32::from_be_bytes(header).try_into()?;
if !matches!(data_type, DataType::Command) {
return Err(Error::new("Received data type was not a command data type"));
}
let mut buffer = [0u8; 4];
let mut id_buffer = [0u8; 1];
let mut args = [0u32; 2];
stream_reader.read_exact(&mut id_buffer)?;
let id = id_buffer[0];
stream_reader.read_exact(&mut buffer)?;
args[0] = u32::from_be_bytes(buffer);
stream_reader.read_exact(&mut buffer)?;
args[1] = u32::from_be_bytes(buffer);
stream_reader.read_exact(&mut buffer)?;
let command_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; command_data_length];
stream_reader.read_exact(&mut data)?;
if event_sender
.send(Event::Command(Command { id, args, data }))
.is_err()
{
break;
}
}
Ok(())
}
fn server_serial_thread(
serial_reader: Arc<Serial>,
event_sender: Sender<Event>,
exit_flag: Arc<AtomicBool>,
) -> Result<(), Error> {
let mut packets: VecDeque<Packet> = VecDeque::new();
while !exit_flag.load(Ordering::Relaxed) {
let response = serial_reader.process_incoming_data(DataType::Packet, &mut packets)?;
if let Some(response) = response {
if event_sender.send(Event::Response(response)).is_err() {
break;
}
}
if let Some(packet) = packets.pop_front() {
if event_sender.send(Event::Packet(packet)).is_err() {
break;
}
}
}
Ok(())
}
fn server_keepalive_thread(event_sender: Sender<Event>, exit_flag: Arc<AtomicBool>) {
let mut keepalive = Instant::now();
while !exit_flag.load(Ordering::Relaxed) {
if keepalive.elapsed() >= Duration::from_secs(5) {
keepalive = Instant::now();
if event_sender.send(Event::KeepAlive).is_err() {
break;
}
} else {
thread::sleep(Duration::from_millis(10));
}
}
}

View File

@ -1,4 +1,4 @@
use super::{link::Packet, Error}; use super::{link::AsynchronousPacket, Error};
use std::fmt::Display; use std::fmt::Display;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -7,7 +7,7 @@ pub enum ConfigId {
RomWriteEnable, RomWriteEnable,
RomShadowEnable, RomShadowEnable,
DdMode, DdMode,
IsvAddress, ISViewer,
BootMode, BootMode,
SaveType, SaveType,
CicSeed, CicSeed,
@ -25,7 +25,7 @@ pub enum Config {
RomWriteEnable(Switch), RomWriteEnable(Switch),
RomShadowEnable(Switch), RomShadowEnable(Switch),
DdMode(DdMode), DdMode(DdMode),
IsvAddress(u32), ISViewer(ISViewer),
BootMode(BootMode), BootMode(BootMode),
SaveType(SaveType), SaveType(SaveType),
CicSeed(CicSeed), CicSeed(CicSeed),
@ -45,7 +45,7 @@ impl From<ConfigId> for u32 {
ConfigId::RomWriteEnable => 1, ConfigId::RomWriteEnable => 1,
ConfigId::RomShadowEnable => 2, ConfigId::RomShadowEnable => 2,
ConfigId::DdMode => 3, ConfigId::DdMode => 3,
ConfigId::IsvAddress => 4, ConfigId::ISViewer => 4,
ConfigId::BootMode => 5, ConfigId::BootMode => 5,
ConfigId::SaveType => 6, ConfigId::SaveType => 6,
ConfigId::CicSeed => 7, ConfigId::CicSeed => 7,
@ -69,7 +69,7 @@ impl TryFrom<(ConfigId, u32)> for Config {
ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?), ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?),
ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?), ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?),
ConfigId::DdMode => Self::DdMode(config.try_into()?), ConfigId::DdMode => Self::DdMode(config.try_into()?),
ConfigId::IsvAddress => Self::IsvAddress(config), ConfigId::ISViewer => Self::ISViewer(config.try_into()?),
ConfigId::BootMode => Self::BootMode(config.try_into()?), ConfigId::BootMode => Self::BootMode(config.try_into()?),
ConfigId::SaveType => Self::SaveType(config.try_into()?), ConfigId::SaveType => Self::SaveType(config.try_into()?),
ConfigId::CicSeed => Self::CicSeed(config.try_into()?), ConfigId::CicSeed => Self::CicSeed(config.try_into()?),
@ -91,7 +91,7 @@ impl From<Config> for [u32; 2] {
Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()], Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()],
Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.into(), val.into()], Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.into(), val.into()],
Config::DdMode(val) => [ConfigId::DdMode.into(), val.into()], Config::DdMode(val) => [ConfigId::DdMode.into(), val.into()],
Config::IsvAddress(val) => [ConfigId::IsvAddress.into(), val.into()], Config::ISViewer(val) => [ConfigId::ISViewer.into(), val.into()],
Config::BootMode(val) => [ConfigId::BootMode.into(), val.into()], Config::BootMode(val) => [ConfigId::BootMode.into(), val.into()],
Config::SaveType(val) => [ConfigId::SaveType.into(), val.into()], Config::SaveType(val) => [ConfigId::SaveType.into(), val.into()],
Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()], Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()],
@ -199,6 +199,41 @@ impl From<DdMode> for u32 {
} }
} }
pub enum ISViewer {
Disabled,
Enabled(u32),
}
impl Display for ISViewer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Disabled => f.write_str("Not listening"),
Self::Enabled(offset) => {
f.write_fmt(format_args!("Listening at 0x{:08X}", 0x1000_0000 + offset))
}
}
}
}
impl TryFrom<u32> for ISViewer {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Disabled,
offset => Self::Enabled(offset),
})
}
}
impl From<ISViewer> for u32 {
fn from(value: ISViewer) -> Self {
match value {
ISViewer::Disabled => 0,
ISViewer::Enabled(offset) => offset,
}
}
}
pub enum BootMode { pub enum BootMode {
Menu, Menu,
Rom, Rom,
@ -588,9 +623,9 @@ pub enum DataPacket {
UpdateStatus(UpdateStatus), UpdateStatus(UpdateStatus),
} }
impl TryFrom<Packet> for DataPacket { impl TryFrom<AsynchronousPacket> for DataPacket {
type Error = Error; type Error = Error;
fn try_from(value: Packet) -> Result<Self, Self::Error> { fn try_from(value: AsynchronousPacket) -> Result<Self, Self::Error> {
Ok(match value.id { Ok(match value.id {
b'B' => Self::Button, b'B' => Self::Button,
b'G' => Self::DataFlushed, b'G' => Self::DataFlushed,