diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc16f03..af4ea64 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: - version: linux os: ubuntu-latest - linux-packages: libudev-dev + apt-packages: libudev-dev executable: target/release/sc64deployer package-name: sc64-deployer-linux package-params: -czf @@ -98,11 +98,11 @@ jobs: string: '${{ github.ref_name }}' replace-with: '-' - - name: Install linux packages - if: matrix.linux-packages + - name: Install apt packages + if: matrix.apt-packages run: | sudo apt-get update - sudo apt-get -y install ${{ matrix.linux-packages }} + sudo apt-get -y install ${{ matrix.apt-packages }} - name: Build deployer run: cargo b -r ${{ matrix.build-params }} diff --git a/fw/rtl/mcu/mcu_top.sv b/fw/rtl/mcu/mcu_top.sv index a0540d5..41b9627 100644 --- a/fw/rtl/mcu/mcu_top.sv +++ b/fw/rtl/mcu/mcu_top.sv @@ -394,13 +394,13 @@ module mcu_top ( REG_USB_SCR: begin reg_rdata <= { - 2'd0, + 1'd0, + usb_scb.fifo_flush_busy, usb_scb.pwrsav, usb_scb.reset_state, usb_scb.tx_count, usb_scb.rx_count, - 2'b00, - usb_scb.reset_pending, + 3'b000, ~fifo_bus.tx_full, ~fifo_bus.rx_empty, 1'b0 @@ -681,9 +681,10 @@ module mcu_top ( mem_start <= 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.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.stop <= 1'b0; @@ -770,11 +771,10 @@ module mcu_top ( end REG_USB_SCR: begin - { - usb_scb.write_buffer_flush, - usb_scb.reset_ack, - usb_scb.fifo_flush - } <= {reg_wdata[5:4], reg_wdata[0]}; + usb_scb.write_buffer_flush <= reg_wdata[5]; + usb_scb.reset_off_ack <= reg_wdata[4]; + usb_scb.reset_on_ack <= reg_wdata[3]; + usb_scb.fifo_flush <= reg_wdata[0]; end REG_USB_DMA_ADDRESS: begin diff --git a/fw/rtl/memory/memory_dma.sv b/fw/rtl/memory/memory_dma.sv index b401ddf..bfd15d6 100644 --- a/fw/rtl/memory/memory_dma.sv +++ b/fw/rtl/memory/memory_dma.sv @@ -45,12 +45,26 @@ module memory_dma ( logic dma_start; logic dma_stop; + logic dma_stop_requested; always_comb begin dma_start = dma_scb.start && !dma_scb.stop && !dma_scb.busy; dma_stop = dma_scb.stop; 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 @@ -197,7 +211,7 @@ module memory_dma ( remaining <= dma_scb.transfer_length; 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; end end diff --git a/fw/rtl/usb/usb_ft1248.sv b/fw/rtl/usb/usb_ft1248.sv index 9b2153b..d60d8ad 100644 --- a/fw/rtl/usb/usb_ft1248.sv +++ b/fw/rtl/usb/usb_ft1248.sv @@ -1,34 +1,37 @@ interface usb_scb (); logic fifo_flush; - logic reset_pending; - logic reset_ack; + logic fifo_flush_busy; logic write_buffer_flush; logic [10:0] rx_count; logic [10:0] tx_count; logic pwrsav; logic reset_state; + logic reset_on_ack; + logic reset_off_ack; modport controller ( output fifo_flush, - input reset_pending, - output reset_ack, + input fifo_flush_busy, output write_buffer_flush, input rx_count, input tx_count, input pwrsav, - input reset_state + input reset_state, + output reset_on_ack, + output reset_off_ack ); modport usb ( input fifo_flush, - output reset_pending, - input reset_ack, + output fifo_flush_busy, input write_buffer_flush, output rx_count, output tx_count, output pwrsav, - output reset_state + output reset_state, + input reset_on_ack, + input reset_off_ack ); endinterface @@ -59,9 +62,11 @@ module usb_ft1248 ( logic tx_read; logic [7:0] tx_rdata; + logic fifo_flush; + fifo_8kb fifo_8kb_rx_inst ( .clk(clk), - .reset(reset || usb_scb.fifo_flush), + .reset(reset || fifo_flush), .empty(fifo_bus.rx_empty), .almost_empty(fifo_bus.rx_almost_empty), @@ -78,7 +83,7 @@ module usb_ft1248 ( fifo_8kb fifo_8kb_tx_inst ( .clk(clk), - .reset(reset || usb_scb.fifo_flush), + .reset(reset || fifo_flush), .empty(tx_empty), .almost_empty(tx_almost_empty), @@ -142,7 +147,6 @@ module usb_ft1248 ( logic [3:0] phase; logic last_tx_failed; logic reset_reply; - logic last_reset_status; logic [4:0] modem_status_counter; logic write_modem_status_pending; logic write_buffer_flush_pending; @@ -152,7 +156,7 @@ module usb_ft1248 ( cmd <= next_cmd; usb_scb.pwrsav <= !ft_pwrsav; - usb_scb.reset_state <= last_reset_status; + fifo_flush <= 1'b0; phase <= {phase[2:0], phase[3]}; if (state == STATE_IDLE) begin @@ -160,25 +164,38 @@ module usb_ft1248 ( end if (reset) begin + usb_scb.fifo_flush_busy <= 1'b0; + usb_scb.reset_state <= 1'b0; last_tx_failed <= 1'b0; - usb_scb.reset_pending <= 1'b0; - last_reset_status <= 1'b0; + reset_reply <= 1'b0; modem_status_counter <= 5'd0; - write_modem_status_pending <= 1'b0; + write_modem_status_pending <= 1'b1; write_buffer_flush_pending <= 1'b0; end else begin - if (usb_scb.reset_ack) begin - usb_scb.reset_pending <= 1'b0; + if (usb_scb.fifo_flush) begin + usb_scb.fifo_flush_busy <= 1'b1; + end + + if (usb_scb.reset_on_ack) begin reset_reply <= 1'b1; write_modem_status_pending <= 1'b1; 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 write_buffer_flush_pending <= 1'b1; end if (state == STATE_IDLE) begin 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 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 (cmd == CMD_READ_MODEM_STATUS) begin - last_reset_status <= 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 + usb_scb.reset_state <= ft_miosi_in[0]; end if (cmd == CMD_WRITE_MODEM_STATUS) begin write_modem_status_pending <= 1'b0; @@ -283,7 +293,7 @@ module usb_ft1248 ( end else begin case (state) 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 next_state = STATE_SELECT; next_cmd = CMD_WRITE_MODEM_STATUS; diff --git a/sw/controller/src/fpga.h b/sw/controller/src/fpga.h index 7c3f0d5..652cf54 100644 --- a/sw/controller/src/fpga.h +++ b/sw/controller/src/fpga.h @@ -79,8 +79,8 @@ typedef enum { #define USB_SCR_FIFO_FLUSH (1 << 0) #define USB_SCR_RXNE (1 << 1) #define USB_SCR_TXE (1 << 2) -#define USB_SCR_RESET_PENDING (1 << 3) -#define USB_SCR_RESET_ACK (1 << 4) +#define USB_SCR_RESET_ON_ACK (1 << 3) +#define USB_SCR_RESET_OFF_ACK (1 << 4) #define USB_SCR_WRITE_FLUSH (1 << 5) #define USB_SCR_RX_COUNT_BIT (6) #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_RESET_STATE (1 << 28) #define USB_SCR_PWRSAV (1 << 29) +#define USB_SCR_FIFO_FLUSH_BUSY (1 << 30) #define DMA_SCR_START (1 << 0) #define DMA_SCR_STOP (1 << 1) diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index 760f68d..3287b98 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -43,6 +43,8 @@ enum tx_state { struct process { + bool last_reset_state; + enum rx_state rx_state; uint8_t rx_counter; uint8_t rx_cmd; @@ -80,10 +82,6 @@ static const uint32_t ERR_TOKEN = (0x45525200UL); 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) { if (fpga_usb_status_get() & USB_STATUS_RXNE) { *data = fpga_usb_pop(); @@ -149,6 +147,59 @@ static bool usb_rx_cmd (uint8_t *cmd) { 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) { if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) { return true; @@ -410,27 +461,34 @@ static void usb_rx_process (void) { } 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]; - fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS); - fpga_reg_set(REG_USB_DMA_LENGTH, length); - fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); - p.rx_args[1] -= length; - } else { - 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; - } + if (!p.rx_dma_running) { + fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS); + fpga_reg_set(REG_USB_DMA_LENGTH, length); + fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); + p.rx_dma_running = true; } 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; } + } else { + p.rx_state = RX_STATE_IDLE; } } } @@ -556,42 +614,16 @@ void usb_get_read_info (uint32_t *args) { void usb_init (void) { - fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP); - fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH); - - 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; + p.last_reset_state = false; + usb_reset(); } void usb_process (void) { - uint32_t scr = fpga_reg_get(REG_USB_SCR); - 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 { + if (usb_is_active()) { usb_rx_process(); usb_tx_process(); + } else { + usb_flush_packet(); } } diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index 29aa09d..6712a7e 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -27,13 +27,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -51,57 +57,110 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "anyhow" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] [[package]] name = "bit_field" @@ -117,21 +176,33 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitstream-io" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "415f8399438eb5e4b2f73ed3152a3448b98149dda642a957ee704e1daa5cf1d8" + +[[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -140,10 +211,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "cc" -version = "1.0.90" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] [[package]] name = "cfg-if" @@ -159,23 +251,23 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] name = "clap" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -192,9 +284,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -204,21 +296,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "color_quant" @@ -228,9 +320,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "colored" @@ -250,9 +342,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -278,9 +370,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -306,19 +398,25 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "exr" version = "1.72.0" @@ -346,9 +444,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -365,9 +463,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -386,9 +484,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -404,10 +502,16 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -440,22 +544,43 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "include-flate" version = "0.2.0" @@ -491,23 +616,65 @@ dependencies = [ ] [[package]] -name = "io-kit-sys" -version = "0.4.0" +name = "indexmap" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ "core-foundation-sys", "mach2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -520,9 +687,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" @@ -532,9 +699,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" @@ -556,6 +723,38 @@ dependencies = [ "rle-decode-fast", ] +[[package]] +name = "libftdi1-source-lgpl" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4412918d38c7b78f59c5b0cefec46e2099876dfd95f07f9bb2269d1d7b09fdb1" + +[[package]] +name = "libftdi1-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575286f9af093f56c7805f4f94874871b84216d1545f499d2cbad9e370000fe7" +dependencies = [ + "cc", + "cfg-if", + "libc", + "libftdi1-source-lgpl", + "libusb1-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libudev" version = "0.3.0" @@ -577,10 +776,22 @@ dependencies = [ ] [[package]] -name = "lock_api" -version = "0.4.11" +name = "libusb1-sys" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "17f6bace2f39082e9787c851afce469e7b2fe0f1cc64bbc68ca96653b63d8f17" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -588,9 +799,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] [[package]] name = "mach2" @@ -601,6 +821,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md5" version = "0.7.0" @@ -609,20 +839,32 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.26.4" @@ -640,17 +882,74 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] -name = "num-traits" -version = "0.2.18" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -668,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -677,6 +976,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pkg-config" version = "0.3.30" @@ -710,13 +1015,32 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.68", +] + [[package]] name = "qoi" version = "0.4.1" @@ -727,10 +1051,16 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.35" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -766,10 +1096,60 @@ dependencies = [ ] [[package]] -name = "rayon" -version = "1.9.0" +name = "rav1e" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67376f469e7e7840d0040bbf4b9b3334005bb167f814621326e4c7ab8cd6e944" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -787,9 +1167,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -799,9 +1179,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -810,9 +1190,18 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rgb" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" +dependencies = [ + "bytemuck", +] [[package]] name = "rle-decode-fast" @@ -844,6 +1233,8 @@ dependencies = [ "hex", "image", "include-flate", + "libftdi1-sys", + "libusb1-sys", "md5", "panic-message", "rand", @@ -859,10 +1250,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serial2" -version = "0.2.20" +name = "serde" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532d7a51dc8416e952987dbd39dd5e24fb9d1cbe86f1263521ec5032cb7ff0fb" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "serial2" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f39915da34f43a64c66d53c228b8ba9a4e21fed4894af24304e47f96996acafd" dependencies = [ "cfg-if", "libc", @@ -871,11 +1291,11 @@ dependencies = [ [[package]] name = "serialport" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828" +checksum = "de7c4f0cce25b9b3518eea99618112f9ee4549f974480c8f43d3c06f03c131a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "core-foundation-sys", "io-kit-sys", @@ -895,10 +1315,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] -name = "smallvec" -version = "1.13.1" +name = "simd_helpers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" @@ -911,9 +1340,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -928,9 +1357,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -938,23 +1367,42 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.58" +name = "system-deps" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.68", ] [[package]] @@ -969,10 +1417,44 @@ dependencies = [ ] [[package]] -name = "unescaper" -version = "0.1.4" +name = "toml" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adf6ad32eb5b3cadff915f7b770faaac8f7ff0476633aa29eb0d9584d889d34" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" dependencies = [ "thiserror", ] @@ -985,9 +1467,32 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" @@ -1022,7 +1527,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -1044,7 +1549,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1089,7 +1594,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1107,7 +1612,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1127,17 +1632,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1148,9 +1654,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1160,9 +1666,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1172,9 +1678,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1184,9 +1696,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1196,9 +1708,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1208,9 +1720,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1220,9 +1732,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-inflate" @@ -1232,3 +1759,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 7eac40d..b6f5ff0 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -7,22 +7,24 @@ description = "SummerCart64 loader and control software" documentation = "https://github.com/Polprzewodnikowy/SummerCart64" [dependencies] -chrono = "0.4.23" -clap = { version = "4.1.6", features = ["derive"] } -clap-num = "1.0.2" -colored = "2.0.0" -crc32fast = "1.3.2" -ctrlc = "3.2.5" -encoding_rs = "0.8.32" +chrono = "0.4.38" +clap = { version = "4.5.8", features = ["derive"] } +clap-num = "1.1.1" +colored = "2.1.0" +crc32fast = "1.4.2" +ctrlc = "3.4.4" +encoding_rs = "0.8.34" hex = "0.4.3" -image = "0.24.5" +image = "0.25.1" 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" panic-message = "0.3.0" rand = "0.8.5" rust-ini = "0.18.0" -serial2 = "0.2.20" -serialport = "4.3.0" +serial2 = "0.2.26" +serialport = "4.4.0" [profile.release] lto = true diff --git a/sw/deployer/src/debug.rs b/sw/deployer/src/debug.rs index d78244b..35f29af 100644 --- a/sw/deployer/src/debug.rs +++ b/sw/deployer/src/debug.rs @@ -291,17 +291,7 @@ impl Handler { let filename = &if let Some(path) = path { path.to_string_lossy().to_string() } else { - generate_filename( - "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", - }, - ) + generate_filename("save", "sav") }; match File::create(filename) { Ok(mut file) => { diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index a346caa..f538822 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -25,7 +25,7 @@ struct Cli { #[command(subcommand)] command: Commands, - /// Connect to SC64 device on provided serial port + /// Connect to SC64 device on provided local port #[arg(short, long)] port: Option, @@ -350,7 +350,13 @@ fn handle_list_command() -> Result<(), sc64::Error> { println!("{}", "Found devices:".bold()); 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(()) @@ -733,35 +739,35 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> { 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!(" Boot mode: {}", state.boot_mode); - println!(" Save type: {}", state.save_type); - println!(" CIC seed: {}", state.cic_seed); - println!(" TV type: {}", state.tv_type); - println!(" Bootloader switch: {}", state.bootloader_switch); - println!(" ROM write: {}", state.rom_write_enable); - println!(" ROM shadow: {}", state.rom_shadow_enable); - println!(" ROM extended: {}", state.rom_extended_enable); - println!(" 64DD mode: {}", state.dd_mode); - println!(" 64DD SD card mode: {}", state.dd_sd_enable); - println!(" 64DD drive type: {}", state.dd_drive_type); - println!(" 64DD disk state: {}", state.dd_disk_state); - println!(" Button mode: {}", state.button_mode); - println!(" Button state: {}", state.button_state); - println!(" LED blink: {}", state.led_enable); - println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address); + println!(" Firmware version: v{}.{}.{}", major, minor, revision); + println!(" RTC datetime: {}", datetime); + println!(" Boot mode: {}", state.boot_mode); + println!(" Save type: {}", state.save_type); + println!(" CIC seed: {}", state.cic_seed); + println!(" TV type: {}", state.tv_type); + println!(" Bootloader switch: {}", state.bootloader_switch); + println!(" ROM write: {}", state.rom_write_enable); + println!(" ROM shadow: {}", state.rom_shadow_enable); + println!(" ROM extended: {}", state.rom_extended_enable); + println!(" 64DD mode: {}", state.dd_mode); + println!(" 64DD SD card mode: {}", state.dd_sd_enable); + println!(" 64DD drive type: {}", state.dd_drive_type); + println!(" 64DD disk state: {}", state.dd_disk_state); + println!(" Button mode: {}", state.button_mode); + println!(" Button state: {}", state.button_state); + println!(" LED blink: {}", state.led_enable); + println!(" IS-Viewer 64: {}", state.isviewer); println!("{}", "SummerCart64 diagnostic information:".bold()); println!( - " Last PI address: 0x{:08X}", + " Last PI address: 0x{:08X}", state.fpga_debug_data.last_pi_address ); println!( - " PI FIFO flags: {}", + " PI FIFO flags: {}", state.fpga_debug_data.pi_fifo_flags ); - println!(" Current CIC step: {}", state.fpga_debug_data.cic_step); - println!(" Diagnostic data: {}", state.diagnostic_data); + println!(" Current CIC step: {}", state.fpga_debug_data.cic_step); + println!(" Diagnostic data: {}", state.diagnostic_data); Ok(()) } diff --git a/sw/deployer/src/sc64/error.rs b/sw/deployer/src/sc64/error.rs index 416bebb..0702fd5 100644 --- a/sw/deployer/src/sc64/error.rs +++ b/sw/deployer/src/sc64/error.rs @@ -26,9 +26,3 @@ impl From for Error { Error::new(format!("IO error: {}", value).as_str()) } } - -impl From for Error { - fn from(value: serialport::Error) -> Self { - Error::new(format!("SerialPort error: {}", value.description).as_str()) - } -} diff --git a/sw/deployer/src/sc64/ftdi.rs b/sw/deployer/src/sc64/ftdi.rs new file mode 100644 index 0000000..f99c728 --- /dev/null +++ b/sw/deployer/src/sc64/ftdi.rs @@ -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, + write_buffer: Vec, + 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, + write_timeout: Option, + ) -> std::io::Result { + 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> { + 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 = 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, + write_timeout: Option, + ) -> 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::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 { + 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 { + 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 { + 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 { + 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> { + Wrapper::list_devices(vendor, product) + } + + pub fn open( + description: &str, + poll_timeout: Option, + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result { + 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 { + 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 { + self.wrapper.read(buffer) + } +} + +impl std::io::Write for FtdiDevice { + fn write(&mut self, buffer: &[u8]) -> std::io::Result { + 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) }; + } +} diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index 88fe748..029870e 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -1,10 +1,9 @@ -use super::error::Error; -use serial2::SerialPort; +use super::{error::Error, ftdi::FtdiDevice, serial::SerialDevice}; use std::{ collections::VecDeque, - io::{BufReader, BufWriter, ErrorKind, Read, Write}, + fmt::Display, + io::{BufReader, BufWriter, Read, Write}, net::TcpStream, - thread, time::{Duration, Instant}, }; @@ -51,82 +50,97 @@ pub struct Response { pub error: bool, } -pub struct Packet { +pub struct AsynchronousPacket { pub id: u8, pub data: Vec, } -pub struct Serial { - serial: SerialPort, +pub enum UsbPacket { + Response(Response), + AsynchronousPacket(AsynchronousPacket), } -impl Serial { - fn reset(&self) -> Result<(), Error> { - const RESET_WAIT_DURATION: Duration = Duration::from_millis(10); - const RESET_RETRY_COUNT: i32 = 100; - const FLUSH_TIMEOUT: Duration = Duration::from_secs(1); +const SERIAL_PREFIX: &str = "serial://"; +const FTDI_PREFIX: &str = "ftdi://"; - self.serial.set_dtr(true)?; - for n in 0..=RESET_RETRY_COUNT { - self.serial.discard_buffers()?; - thread::sleep(RESET_WAIT_DURATION); - if self.serial.read_dsr()? { - break; - } - if n == RESET_RETRY_COUNT { - return Err(Error::new("Couldn't reset SC64 device (on)")); - } - } +const RESET_TIMEOUT: Duration = Duration::from_secs(1); +const POLL_TIMEOUT: Duration = Duration::from_millis(5); +const READ_TIMEOUT: Duration = Duration::from_secs(5); +const WRITE_TIMEOUT: Duration = Duration::from_secs(5); - let flush_timeout = Instant::now(); +pub trait Backend { + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result; + 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 { + 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 { - match self.serial.read(&mut vec![0; 1]) { - Ok(length) => match length { - 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 self.read_dsr()? { + break; } - if flush_timeout.elapsed() >= FLUSH_TIMEOUT { - return Err(Error::new("SC64 serial buffer flush took too long")); + if timeout.elapsed() > RESET_TIMEOUT { + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (on)", + )); } } - self.serial.set_dtr(false)?; - for n in 0..=RESET_RETRY_COUNT { - thread::sleep(RESET_WAIT_DURATION); - if !self.serial.read_dsr()? { + self.discard_input()?; + + let timeout = Instant::now(); + self.set_dtr(false)?; + loop { + if !self.read_dsr()? { break; } - if n == RESET_RETRY_COUNT { - return Err(Error::new("Couldn't reset SC64 device (off)")); + if timeout.elapsed() > RESET_TIMEOUT { + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (off)", + )); } } Ok(()) } - fn read_data(&self, buffer: &mut [u8], block: bool) -> Result, Error> { - let timeout = Instant::now(); + fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result> { let mut position = 0; let length = buffer.len(); + let timeout = Instant::now(); while position < length { - if timeout.elapsed() > Duration::from_secs(10) { - return Err(Error::new("Serial read timeout")); - } - match self.serial.read(&mut buffer[position..length]) { - Ok(0) => return Err(Error::new("Unexpected end of serial data")), + match self.read(&mut buffer[position..length]) { + Ok(0) => return Err(std::io::ErrorKind::UnexpectedEof.into()), Ok(bytes) => position += bytes, 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 { return Ok(None); } @@ -134,48 +148,53 @@ impl Serial { _ => return Err(error.into()), }, } + if timeout.elapsed() > READ_TIMEOUT { + return Err(std::io::ErrorKind::TimedOut.into()); + } } Ok(Some(())) } - fn read_exact(&self, buffer: &mut [u8]) -> Result<(), Error> { - match self.read_data(buffer, true)? { + fn try_read_header(&mut self, block: bool) -> std::io::Result> { + 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(()), - None => Err(Error::new("Unexpected end of serial data")), + None => Err(std::io::ErrorKind::UnexpectedEof.into()), } } - fn read_header(&self, block: bool) -> Result, Error> { - let mut header = [0u8; 4]; - Ok(self.read_data(&mut header, block)?.map(|_| header)) - } + fn send_command(&mut self, command: &Command) -> std::io::Result<()> { + self.write_all(b"CMD")?; + self.write_all(&command.id.to_be_bytes())?; - pub fn send_command(&self, command: &Command) -> Result<(), Error> { - self.serial.write_all(b"CMD")?; - 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.write_all(&command.args[0].to_be_bytes())?; + self.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(()) } - pub fn process_incoming_data( - &self, + fn process_incoming_data( + &mut self, data_type: DataType, - packets: &mut VecDeque, - ) -> Result, Error> { + packets: &mut VecDeque, + ) -> std::io::Result> { let block = matches!(data_type, DataType::Response); - while let Some(header) = self.read_header(block)? { - let (packet_token, error) = (match &header[0..3] { - b"CMP" => Ok((false, false)), - b"PKT" => Ok((true, false)), - b"ERR" => Ok((false, true)), - _ => Err(Error::new("Unknown response token")), - })?; + + while let Some(header) = self.try_read_header(block)? { + let (packet_token, error) = match &header[0..3] { + b"CMP" => (false, false), + b"PKT" => (true, false), + b"ERR" => (false, true), + _ => return Err(std::io::ErrorKind::InvalidData.into()), + }; let id = header[3]; let mut buffer = [0u8; 4]; @@ -187,7 +206,7 @@ impl Serial { self.read_exact(&mut data)?; if packet_token { - packets.push_back(Packet { id, data }); + packets.push_back(AsynchronousPacket { id, data }); if matches!(data_type, DataType::Packet) { break; } @@ -200,48 +219,94 @@ impl Serial { } } -pub fn new_serial(port: &str) -> Result { - let mut serial = SerialPort::open(port, 115_200)?; - 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, - ) -> Result, Error>; - fn close(&self) {} -} - -struct SerialBackend { - inner: Serial, +pub struct SerialBackend { + device: SerialDevice, } impl Backend for SerialBackend { - fn send_command(&mut self, command: &Command) -> Result<(), Error> { - self.inner.send_command(command) + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + self.device.read(buffer) } - fn process_incoming_data( - &mut self, - data_type: DataType, - packets: &mut VecDeque, - ) -> Result, Error> { - self.inner.process_incoming_data(data_type, packets) + 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 { + self.device.read_dsr() } } -fn new_serial_backend(port: &str) -> Result { - let backend = SerialBackend { - inner: new_serial(port)?, - }; - Ok(backend) +fn new_serial_backend(port: &str) -> std::io::Result { + Ok(SerialBackend { + device: SerialDevice::new( + port, + 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 { + 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 { + self.device.read_dsr() + } +} + +fn new_ftdi_backend(port: &str) -> std::io::Result { + Ok(FtdiBackend { + device: FtdiDevice::open( + port, + Some(POLL_TIMEOUT), + Some(READ_TIMEOUT), + Some(WRITE_TIMEOUT), + )?, + }) } struct TcpBackend { @@ -250,58 +315,36 @@ struct TcpBackend { writer: BufWriter, } -impl TcpBackend { - fn read_data(&mut self, buffer: &mut [u8], block: bool) -> Result, 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, Error> { - let mut header = [0u8; 4]; - Ok(self.read_data(&mut header, block)?.map(|_| header)) - } -} - impl Backend for TcpBackend { - fn send_command(&mut self, command: &Command) -> Result<(), Error> { - let payload_data_type: u32 = DataType::Command.into(); - self.writer.write_all(&payload_data_type.to_be_bytes())?; + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + self.reader.read(buffer) + } - self.writer.write_all(&command.id.to_be_bytes())?; - self.writer.write_all(&command.args[0].to_be_bytes())?; - self.writer.write_all(&command.args[1].to_be_bytes())?; + fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> { + self.writer.write_all(buffer) + } + + 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; - self.writer.write_all(&command_data_length.to_be_bytes())?; - self.writer.write_all(&command.data)?; + self.write_all(&command_data_length.to_be_bytes())?; + self.write_all(&command.data)?; - self.writer.flush()?; + self.flush()?; Ok(()) } @@ -309,11 +352,13 @@ impl Backend for TcpBackend { fn process_incoming_data( &mut self, data_type: DataType, - packets: &mut VecDeque, - ) -> Result, Error> { + packets: &mut VecDeque, + ) -> std::io::Result> { let block = matches!(data_type, DataType::Response); - while let Some(header) = self.read_header(block)? { - let payload_data_type: DataType = u32::from_be_bytes(header).try_into()?; + while let Some(header) = self.try_read_header(block)? { + let payload_data_type: DataType = u32::from_be_bytes(header) + .try_into() + .map_err(|_| std::io::ErrorKind::InvalidData)?; let mut buffer = [0u8; 4]; match payload_data_type { DataType::Response => { @@ -342,7 +387,7 @@ impl Backend for TcpBackend { let mut data = vec![0u8; packet_data_length]; self.read_exact(&mut data)?; - packets.push_back(Packet { + packets.push_back(AsynchronousPacket { id: packet_info[0], data, }); @@ -351,31 +396,20 @@ impl Backend for TcpBackend { } } DataType::KeepAlive => {} - _ => return Err(Error::new("Unexpected payload data type received")), + _ => return Err(std::io::ErrorKind::InvalidData.into()), }; } Ok(None) } - - fn close(&self) { - self.stream.shutdown(std::net::Shutdown::Both).ok(); - } } fn new_tcp_backend(address: &str) -> Result { - let stream = match TcpStream::connect(address) { - Ok(stream) => { - stream.set_write_timeout(Some(Duration::from_secs(10)))?; - stream.set_read_timeout(Some(Duration::from_millis(10)))?; - stream - } - Err(error) => { - return Err(Error::new( - format!("Couldn't connect to [{address}]: {error}").as_str(), - )) - } - }; + let stream = TcpStream::connect(address).map_err(|error| { + Error::new(format!("Couldn't connect to [{address}]: {error}").as_str()) + })?; + stream.set_read_timeout(Some(POLL_TIMEOUT))?; + stream.set_write_timeout(Some(WRITE_TIMEOUT))?; let reader = BufReader::new(stream.try_clone()?); let writer = BufWriter::new(stream.try_clone()?); Ok(TcpBackend { @@ -385,9 +419,29 @@ fn new_tcp_backend(address: &str) -> Result { }) } +fn new_local_backend(port: &str) -> Result, Error> { + let mut backend: Box = 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, Error> { + Ok(Box::new(new_tcp_backend(address)?)) +} + pub struct Link { backend: Box, - packets: VecDeque, + packets: VecDeque, } impl Link { @@ -415,7 +469,7 @@ impl Link { Ok(response.data) } - fn receive_response(&mut self) -> Result { + pub fn receive_response(&mut self) -> Result { match self .backend .process_incoming_data(DataType::Response, &mut self.packets) @@ -430,7 +484,7 @@ impl Link { } } - pub fn receive_packet(&mut self) -> Result, Error> { + pub fn receive_packet(&mut self) -> Result, Error> { if self.packets.len() == 0 { let response = self .backend @@ -441,6 +495,19 @@ impl Link { } Ok(self.packets.pop_front()) } + + pub fn receive_response_or_packet(&mut self) -> Result, 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 { @@ -451,45 +518,75 @@ impl Drop for Link { pub fn new_local(port: &str) -> Result { Ok(Link { - backend: Box::new(new_serial_backend(port)?), + backend: new_local_backend(port)?, packets: VecDeque::new(), }) } pub fn new_remote(address: &str) -> Result { Ok(Link { - backend: Box::new(new_tcp_backend(address)?), + backend: new_remote_backend(address)?, packets: VecDeque::new(), }) } -pub struct LocalDevice { - pub port: String, - pub serial_number: String, +pub enum BackendType { + Serial, + Ftdi, } -pub fn list_local_devices() -> Result, 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, Error> { const SC64_VID: u16 = 0x0403; 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 = Vec::new(); + let mut devices: Vec = Vec::new(); - for device in serialport::available_ports()?.into_iter() { - if let serialport::SerialPortType::UsbPort(info) = device.port_type { - let serial_number = info.serial_number.unwrap_or("".to_string()); - if info.vid == SC64_VID && info.pid == SC64_PID && serial_number.starts_with(SC64_SID) { - serial_devices.push(LocalDevice { - port: device.port_name, - serial_number, + if let Ok(list) = FtdiDevice::list(SC64_VID, SC64_PID) { + for device in list.into_iter() { + if device.description == SC64_DESCRIPTION { + devices.push(DeviceInfo { + backend: BackendType::Ftdi, + port: format!("{FTDI_PREFIX}{}", device.port), + 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 Ok(serial_devices); + return Ok(devices); } diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index a1ac764..9a8064c 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -1,7 +1,9 @@ mod cic; mod error; pub mod firmware; +mod ftdi; mod link; +mod serial; pub mod server; mod time; mod types; @@ -12,8 +14,8 @@ pub use self::{ server::ServerEvent, types::{ BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, - DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, MemoryTestPattern, - MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType, + DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, ISViewer, + MemoryTestPattern, MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType, }, }; @@ -43,7 +45,7 @@ pub struct DeviceState { pub rom_write_enable: Switch, pub rom_shadow_enable: Switch, pub dd_mode: DdMode, - pub isv_address: u32, + pub isviewer: ISViewer, pub boot_mode: BootMode, pub save_type: SaveType, pub cic_seed: CicSeed, @@ -547,7 +549,7 @@ impl SC64 { rom_write_enable: get_config!(self, RomWriteEnable)?, rom_shadow_enable: get_config!(self, RomShadowEnable)?, dd_mode: get_config!(self, DdMode)?, - isv_address: get_config!(self, IsvAddress)?, + isviewer: get_config!(self, ISViewer)?, boot_mode: get_config!(self, BootMode)?, save_type: get_config!(self, SaveType)?, 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::IsvAddress(offset))?; + self.command_config_set(Config::ISViewer(ISViewer::Enabled(offset)))?; } else { self.command_config_set(Config::RomWriteEnable(Switch::Off))?; - self.command_config_set(Config::IsvAddress(0))?; + self.command_config_set(Config::ISViewer(ISViewer::Disabled))?; } Ok(()) } @@ -875,13 +877,8 @@ impl SC64 { impl SC64 { pub fn open_local(port: Option) -> Result { - let port = if let Some(port) = port { - port - } else { - list_local_devices()?[0].port.clone() - }; 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()?; Ok(sc64) diff --git a/sw/deployer/src/sc64/serial.rs b/sw/deployer/src/sc64/serial.rs new file mode 100644 index 0000000..0c040ac --- /dev/null +++ b/sw/deployer/src/sc64/serial.rs @@ -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, + 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, + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result { + 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> { + 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 { + 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 { + 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 { + 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()); + } + } + } +} diff --git a/sw/deployer/src/sc64/server.rs b/sw/deployer/src/sc64/server.rs index ae52a15..735d70f 100644 --- a/sw/deployer/src/sc64/server.rs +++ b/sw/deployer/src/sc64/server.rs @@ -1,19 +1,10 @@ use super::{ error::Error, - link::{list_local_devices, new_serial, Command, DataType, Packet, Response, Serial}, -}; -use std::{ - collections::VecDeque, - io::{BufReader, BufWriter, ErrorKind, Read, Write}, - net::{TcpListener, TcpStream}, - sync::{ - atomic::{AtomicBool, Ordering}, - mpsc::{channel, Receiver, Sender}, - Arc, + link::{ + list_local_devices, new_local, AsynchronousPacket, Command, DataType, Response, UsbPacket, }, - thread, - time::{Duration, Instant}, }; +use std::io::{Read, Write}; pub enum ServerEvent { Listening(String), @@ -22,237 +13,160 @@ pub enum ServerEvent { Err(String), } +struct StreamHandler { + stream: std::net::TcpStream, + reader: std::io::BufReader, + writer: std::io::BufWriter, +} + +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 { + 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> { + 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> { + if let Some(header) = self.try_read_header()? { + if let Ok(data_type) = TryInto::::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( port: Option, address: String, event_callback: fn(ServerEvent), ) -> Result<(), Error> { - let port = if let Some(port) = port { - port - } else { - list_local_devices()?[0].port.clone() - }; - let listener = TcpListener::bind(address)?; + let port = port.unwrap_or(list_local_devices()?[0].port.clone()); + let listener = std::net::TcpListener::bind(address)?; let listening_address = listener.local_addr()?; + event_callback(ServerEvent::Listening(listening_address.to_string())); - for stream in listener.incoming() { - match stream { - Ok(mut stream) => { - let peer = stream.peer_addr()?.to_string(); - event_callback(ServerEvent::Connected(peer.clone())); - match server_accept_connection(port.clone(), &mut stream) { - 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()), - }, + for incoming in listener.incoming() { + let stream = incoming?; + let peer = stream.peer_addr()?.to_string(); + + event_callback(ServerEvent::Connected(peer.clone())); + + match server_accept_connection(port.clone(), &mut StreamHandler::new(stream)?) { + Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())), + Err(error) => event_callback(ServerEvent::Err(error.to_string())), } } Ok(()) } - -enum Event { - Command(Command), - Response(Response), - Packet(Packet), - KeepAlive, - Closed(Option), -} - -fn server_accept_connection(port: String, stream: &mut TcpStream) -> Result<(), Error> { - let (event_sender, event_receiver) = channel::(); - 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, - serial_writer: Arc, - event_receiver: Receiver, -) -> 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, - exit_flag: Arc, -) -> 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, - event_sender: Sender, - exit_flag: Arc, -) -> Result<(), Error> { - let mut packets: VecDeque = 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, exit_flag: Arc) { - 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)); - } - } -} diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 8699207..d9e5e09 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -1,4 +1,4 @@ -use super::{link::Packet, Error}; +use super::{link::AsynchronousPacket, Error}; use std::fmt::Display; #[derive(Clone, Copy)] @@ -7,7 +7,7 @@ pub enum ConfigId { RomWriteEnable, RomShadowEnable, DdMode, - IsvAddress, + ISViewer, BootMode, SaveType, CicSeed, @@ -25,7 +25,7 @@ pub enum Config { RomWriteEnable(Switch), RomShadowEnable(Switch), DdMode(DdMode), - IsvAddress(u32), + ISViewer(ISViewer), BootMode(BootMode), SaveType(SaveType), CicSeed(CicSeed), @@ -45,7 +45,7 @@ impl From for u32 { ConfigId::RomWriteEnable => 1, ConfigId::RomShadowEnable => 2, ConfigId::DdMode => 3, - ConfigId::IsvAddress => 4, + ConfigId::ISViewer => 4, ConfigId::BootMode => 5, ConfigId::SaveType => 6, ConfigId::CicSeed => 7, @@ -69,7 +69,7 @@ impl TryFrom<(ConfigId, u32)> for Config { ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?), ConfigId::RomShadowEnable => Self::RomShadowEnable(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::SaveType => Self::SaveType(config.try_into()?), ConfigId::CicSeed => Self::CicSeed(config.try_into()?), @@ -91,7 +91,7 @@ impl From for [u32; 2] { Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()], Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.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::SaveType(val) => [ConfigId::SaveType.into(), val.into()], Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()], @@ -199,6 +199,41 @@ impl From 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 for ISViewer { + type Error = Error; + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => Self::Disabled, + offset => Self::Enabled(offset), + }) + } +} + +impl From for u32 { + fn from(value: ISViewer) -> Self { + match value { + ISViewer::Disabled => 0, + ISViewer::Enabled(offset) => offset, + } + } +} + pub enum BootMode { Menu, Rom, @@ -588,9 +623,9 @@ pub enum DataPacket { UpdateStatus(UpdateStatus), } -impl TryFrom for DataPacket { +impl TryFrom for DataPacket { type Error = Error; - fn try_from(value: Packet) -> Result { + fn try_from(value: AsynchronousPacket) -> Result { Ok(match value.id { b'B' => Self::Button, b'G' => Self::DataFlushed,