mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2025-01-08 10:40:41 +01:00
Merge branch 'main' into new-irq
This commit is contained in:
commit
401e07322f
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -71,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
- version: linux
|
- version: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
linux-packages: libudev-dev
|
apt-packages: libudev-dev
|
||||||
executable: target/release/sc64deployer
|
executable: target/release/sc64deployer
|
||||||
package-name: sc64-deployer-linux
|
package-name: sc64-deployer-linux
|
||||||
package-params: -czf
|
package-params: -czf
|
||||||
@ -98,11 +98,11 @@ jobs:
|
|||||||
string: '${{ github.ref_name }}'
|
string: '${{ github.ref_name }}'
|
||||||
replace-with: '-'
|
replace-with: '-'
|
||||||
|
|
||||||
- name: Install linux packages
|
- name: Install apt packages
|
||||||
if: matrix.linux-packages
|
if: matrix.apt-packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install ${{ matrix.linux-packages }}
|
sudo apt-get -y install ${{ matrix.apt-packages }}
|
||||||
|
|
||||||
- name: Build deployer
|
- name: Build deployer
|
||||||
run: cargo b -r ${{ matrix.build-params }}
|
run: cargo b -r ${{ matrix.build-params }}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
<Options/>
|
<Options/>
|
||||||
<Implementation title="impl1" dir="impl1" description="impl1" synthesis="synplify" default_strategy="release">
|
<Implementation title="impl1" dir="impl1" description="impl1" synthesis="synplify" default_strategy="release">
|
||||||
<Options VerilogStandard="System Verilog" def_top="top" top="top"/>
|
<Options VerilogStandard="System Verilog" def_top="top" top="top"/>
|
||||||
|
<Source name="../../rtl/memory/dma_scb.sv" type="Verilog" type_short="Verilog">
|
||||||
|
<Options VerilogStandard="System Verilog"/>
|
||||||
|
</Source>
|
||||||
<Source name="../../rtl/memory/mem_bus.sv" type="Verilog" type_short="Verilog">
|
<Source name="../../rtl/memory/mem_bus.sv" type="Verilog" type_short="Verilog">
|
||||||
<Options VerilogStandard="System Verilog"/>
|
<Options VerilogStandard="System Verilog"/>
|
||||||
</Source>
|
</Source>
|
||||||
@ -12,6 +15,9 @@
|
|||||||
<Source name="../../rtl/sd/sd_scb.sv" type="Verilog" type_short="Verilog">
|
<Source name="../../rtl/sd/sd_scb.sv" type="Verilog" type_short="Verilog">
|
||||||
<Options VerilogStandard="System Verilog"/>
|
<Options VerilogStandard="System Verilog"/>
|
||||||
</Source>
|
</Source>
|
||||||
|
<Source name="../../rtl/usb/usb_scb.sv" type="Verilog" type_short="Verilog">
|
||||||
|
<Options VerilogStandard="System Verilog"/>
|
||||||
|
</Source>
|
||||||
<Source name="../../rtl/fifo/fifo_bus.sv" type="Verilog" type_short="Verilog">
|
<Source name="../../rtl/fifo/fifo_bus.sv" type="Verilog" type_short="Verilog">
|
||||||
<Options VerilogStandard="System Verilog"/>
|
<Options VerilogStandard="System Verilog"/>
|
||||||
</Source>
|
</Source>
|
||||||
|
@ -214,7 +214,7 @@ LOCATE COMP "usb_miso" SITE "10" ;
|
|||||||
LOCATE COMP "usb_pwrsav" SITE "2" ;
|
LOCATE COMP "usb_pwrsav" SITE "2" ;
|
||||||
SYSCONFIG SDM_PORT=DISABLE I2C_PORT=ENABLE ;
|
SYSCONFIG SDM_PORT=DISABLE I2C_PORT=ENABLE ;
|
||||||
VOLTAGE 3.300 V;
|
VOLTAGE 3.300 V;
|
||||||
FREQUENCY NET "clk" 100.000000 MHz PAR_ADJ 10.000000 ;
|
FREQUENCY NET "clk" 100.000000 MHz ;
|
||||||
BLOCK PATH TO PORT "mcu_int" ;
|
BLOCK PATH TO PORT "mcu_int" ;
|
||||||
BLOCK PATH TO PORT "n64_irq" ;
|
BLOCK PATH TO PORT "n64_irq" ;
|
||||||
BLOCK PATH FROM PORT "usb_pwrsav" ;
|
BLOCK PATH FROM PORT "usb_pwrsav" ;
|
||||||
|
@ -1,35 +1,29 @@
|
|||||||
interface fifo_bus ();
|
interface fifo_bus ();
|
||||||
|
|
||||||
logic rx_empty;
|
logic rx_empty;
|
||||||
logic rx_almost_empty;
|
|
||||||
logic rx_read;
|
logic rx_read;
|
||||||
logic [7:0] rx_rdata;
|
logic [7:0] rx_rdata;
|
||||||
|
|
||||||
logic tx_full;
|
logic tx_full;
|
||||||
logic tx_almost_full;
|
|
||||||
logic tx_write;
|
logic tx_write;
|
||||||
logic [7:0] tx_wdata;
|
logic [7:0] tx_wdata;
|
||||||
|
|
||||||
modport controller (
|
modport controller (
|
||||||
input rx_empty,
|
input rx_empty,
|
||||||
input rx_almost_empty,
|
|
||||||
output rx_read,
|
output rx_read,
|
||||||
input rx_rdata,
|
input rx_rdata,
|
||||||
|
|
||||||
input tx_full,
|
input tx_full,
|
||||||
input tx_almost_full,
|
|
||||||
output tx_write,
|
output tx_write,
|
||||||
output tx_wdata
|
output tx_wdata
|
||||||
);
|
);
|
||||||
|
|
||||||
modport fifo (
|
modport fifo (
|
||||||
output rx_empty,
|
output rx_empty,
|
||||||
output rx_almost_empty,
|
|
||||||
input rx_read,
|
input rx_read,
|
||||||
output rx_rdata,
|
output rx_rdata,
|
||||||
|
|
||||||
output tx_full,
|
output tx_full,
|
||||||
output tx_almost_full,
|
|
||||||
input tx_write,
|
input tx_write,
|
||||||
input tx_wdata
|
input tx_wdata
|
||||||
);
|
);
|
||||||
|
@ -11,16 +11,12 @@ module fifo_junction (
|
|||||||
dev_bus.tx_wdata = cfg_bus.tx_write ? cfg_bus.tx_wdata : dma_bus.tx_wdata;
|
dev_bus.tx_wdata = cfg_bus.tx_write ? cfg_bus.tx_wdata : dma_bus.tx_wdata;
|
||||||
|
|
||||||
cfg_bus.rx_empty = dev_bus.rx_empty;
|
cfg_bus.rx_empty = dev_bus.rx_empty;
|
||||||
cfg_bus.rx_almost_empty = dev_bus.rx_almost_empty;
|
|
||||||
cfg_bus.rx_rdata = dev_bus.rx_rdata;
|
cfg_bus.rx_rdata = dev_bus.rx_rdata;
|
||||||
cfg_bus.tx_full = dev_bus.tx_full;
|
cfg_bus.tx_full = dev_bus.tx_full;
|
||||||
cfg_bus.tx_almost_full = dev_bus.tx_almost_full;
|
|
||||||
|
|
||||||
dma_bus.rx_empty = dev_bus.rx_empty;
|
dma_bus.rx_empty = dev_bus.rx_empty;
|
||||||
dma_bus.rx_almost_empty = dev_bus.rx_almost_empty;
|
|
||||||
dma_bus.rx_rdata = dev_bus.rx_rdata;
|
dma_bus.rx_rdata = dev_bus.rx_rdata;
|
||||||
dma_bus.tx_full = dev_bus.tx_full;
|
dma_bus.tx_full = dev_bus.tx_full;
|
||||||
dma_bus.tx_almost_full = dev_bus.tx_almost_full;
|
|
||||||
end
|
end
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
@ -394,13 +394,13 @@ module mcu_top (
|
|||||||
|
|
||||||
REG_USB_SCR: begin
|
REG_USB_SCR: begin
|
||||||
reg_rdata <= {
|
reg_rdata <= {
|
||||||
2'd0,
|
1'd0,
|
||||||
|
usb_scb.fifo_flush_busy,
|
||||||
usb_scb.pwrsav,
|
usb_scb.pwrsav,
|
||||||
usb_scb.reset_state,
|
usb_scb.reset_state,
|
||||||
usb_scb.tx_count,
|
usb_scb.tx_count,
|
||||||
usb_scb.rx_count,
|
usb_scb.rx_count,
|
||||||
2'b00,
|
3'b000,
|
||||||
usb_scb.reset_pending,
|
|
||||||
~fifo_bus.tx_full,
|
~fifo_bus.tx_full,
|
||||||
~fifo_bus.rx_empty,
|
~fifo_bus.rx_empty,
|
||||||
1'b0
|
1'b0
|
||||||
@ -681,9 +681,10 @@ module mcu_top (
|
|||||||
mem_start <= 1'b0;
|
mem_start <= 1'b0;
|
||||||
mem_stop <= 1'b0;
|
mem_stop <= 1'b0;
|
||||||
|
|
||||||
usb_scb.write_buffer_flush <= 1'b0;
|
|
||||||
usb_scb.reset_ack <= 1'b0;
|
|
||||||
usb_scb.fifo_flush <= 1'b0;
|
usb_scb.fifo_flush <= 1'b0;
|
||||||
|
usb_scb.write_buffer_flush <= 1'b0;
|
||||||
|
usb_scb.reset_on_ack <= 1'b0;
|
||||||
|
usb_scb.reset_off_ack <= 1'b0;
|
||||||
|
|
||||||
usb_dma_scb.start <= 1'b0;
|
usb_dma_scb.start <= 1'b0;
|
||||||
usb_dma_scb.stop <= 1'b0;
|
usb_dma_scb.stop <= 1'b0;
|
||||||
@ -770,11 +771,10 @@ module mcu_top (
|
|||||||
end
|
end
|
||||||
|
|
||||||
REG_USB_SCR: begin
|
REG_USB_SCR: begin
|
||||||
{
|
usb_scb.write_buffer_flush <= reg_wdata[5];
|
||||||
usb_scb.write_buffer_flush,
|
usb_scb.reset_off_ack <= reg_wdata[4];
|
||||||
usb_scb.reset_ack,
|
usb_scb.reset_on_ack <= reg_wdata[3];
|
||||||
usb_scb.fifo_flush
|
usb_scb.fifo_flush <= reg_wdata[0];
|
||||||
} <= {reg_wdata[5:4], reg_wdata[0]};
|
|
||||||
end
|
end
|
||||||
|
|
||||||
REG_USB_DMA_ADDRESS: begin
|
REG_USB_DMA_ADDRESS: begin
|
||||||
|
31
fw/rtl/memory/dma_scb.sv
Normal file
31
fw/rtl/memory/dma_scb.sv
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
interface dma_scb ();
|
||||||
|
|
||||||
|
logic start;
|
||||||
|
logic stop;
|
||||||
|
logic busy;
|
||||||
|
logic direction;
|
||||||
|
logic byte_swap;
|
||||||
|
logic [26:0] starting_address;
|
||||||
|
logic [26:0] transfer_length;
|
||||||
|
|
||||||
|
modport controller (
|
||||||
|
output start,
|
||||||
|
output stop,
|
||||||
|
input busy,
|
||||||
|
output direction,
|
||||||
|
output byte_swap,
|
||||||
|
output starting_address,
|
||||||
|
output transfer_length
|
||||||
|
);
|
||||||
|
|
||||||
|
modport dma (
|
||||||
|
input start,
|
||||||
|
input stop,
|
||||||
|
output busy,
|
||||||
|
input direction,
|
||||||
|
input byte_swap,
|
||||||
|
input starting_address,
|
||||||
|
input transfer_length
|
||||||
|
);
|
||||||
|
|
||||||
|
endinterface
|
@ -1,36 +1,3 @@
|
|||||||
interface dma_scb ();
|
|
||||||
|
|
||||||
logic start;
|
|
||||||
logic stop;
|
|
||||||
logic busy;
|
|
||||||
logic direction;
|
|
||||||
logic byte_swap;
|
|
||||||
logic [26:0] starting_address;
|
|
||||||
logic [26:0] transfer_length;
|
|
||||||
|
|
||||||
modport controller (
|
|
||||||
output start,
|
|
||||||
output stop,
|
|
||||||
input busy,
|
|
||||||
output direction,
|
|
||||||
output byte_swap,
|
|
||||||
output starting_address,
|
|
||||||
output transfer_length
|
|
||||||
);
|
|
||||||
|
|
||||||
modport dma (
|
|
||||||
input start,
|
|
||||||
input stop,
|
|
||||||
output busy,
|
|
||||||
input direction,
|
|
||||||
input byte_swap,
|
|
||||||
input starting_address,
|
|
||||||
input transfer_length
|
|
||||||
);
|
|
||||||
|
|
||||||
endinterface
|
|
||||||
|
|
||||||
|
|
||||||
module memory_dma (
|
module memory_dma (
|
||||||
input clk,
|
input clk,
|
||||||
input reset,
|
input reset,
|
||||||
@ -41,213 +8,403 @@ module memory_dma (
|
|||||||
mem_bus.controller mem_bus
|
mem_bus.controller mem_bus
|
||||||
);
|
);
|
||||||
|
|
||||||
// DMA start/stop control
|
// Start/stop logic
|
||||||
|
|
||||||
logic dma_start;
|
logic dma_start;
|
||||||
logic dma_stop;
|
logic dma_stop;
|
||||||
|
|
||||||
always_comb begin
|
always_ff @(posedge clk) begin
|
||||||
dma_start = dma_scb.start && !dma_scb.stop && !dma_scb.busy;
|
dma_start <= 1'b0;
|
||||||
dma_stop = dma_scb.stop;
|
if (dma_scb.stop) begin
|
||||||
|
dma_stop <= 1'b1;
|
||||||
|
end else if (dma_scb.start) begin
|
||||||
|
dma_start <= 1'b1;
|
||||||
|
dma_stop <= 1'b0;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
// Remaining counter and FIFO enable
|
// Memory bus controller
|
||||||
|
|
||||||
logic [26:0] remaining;
|
typedef enum bit [1:0] {
|
||||||
logic trx_enabled;
|
MEM_BUS_STATE_IDLE,
|
||||||
|
MEM_BUS_STATE_WAIT,
|
||||||
|
MEM_BUS_STATE_TRANSFER
|
||||||
|
} e_mem_bus_state;
|
||||||
|
|
||||||
|
e_mem_bus_state mem_bus_state;
|
||||||
|
e_mem_bus_state next_mem_bus_state;
|
||||||
|
|
||||||
|
logic mem_bus_wdata_ready;
|
||||||
|
logic mem_bus_wdata_empty;
|
||||||
|
logic mem_bus_wdata_end;
|
||||||
|
logic [1:0] mem_bus_wdata_valid;
|
||||||
|
logic [15:0] mem_bus_wdata_buffer;
|
||||||
|
|
||||||
|
logic mem_bus_rdata_ready;
|
||||||
|
logic mem_bus_rdata_ack;
|
||||||
|
logic mem_bus_rdata_end;
|
||||||
|
logic [1:0] mem_bus_rdata_valid;
|
||||||
|
logic [15:0] mem_bus_rdata_buffer;
|
||||||
|
|
||||||
|
logic [26:0] mem_bus_remaining_bytes;
|
||||||
|
logic mem_bus_last_transfer;
|
||||||
|
logic mem_bus_almost_last_transfer;
|
||||||
|
logic mem_bus_unaligned_start;
|
||||||
|
logic mem_bus_unaligned_end;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (reset) begin
|
||||||
|
mem_bus_state <= MEM_BUS_STATE_IDLE;
|
||||||
|
end else begin
|
||||||
|
mem_bus_state <= next_mem_bus_state;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
always_comb begin
|
always_comb begin
|
||||||
trx_enabled = remaining > 27'd0;
|
next_mem_bus_state = mem_bus_state;
|
||||||
|
|
||||||
|
mem_bus_last_transfer = (mem_bus_remaining_bytes == 27'd0);
|
||||||
|
mem_bus_almost_last_transfer = (mem_bus_remaining_bytes <= 27'd2);
|
||||||
|
|
||||||
|
mem_bus_wdata_end = mem_bus_last_transfer;
|
||||||
|
mem_bus_wdata_valid = (mem_bus_unaligned_end && mem_bus_almost_last_transfer) ? 2'b10 :
|
||||||
|
mem_bus_unaligned_start ? 2'b01 :
|
||||||
|
2'b11;
|
||||||
|
|
||||||
|
case (mem_bus_state)
|
||||||
|
MEM_BUS_STATE_IDLE: begin
|
||||||
|
if (dma_start) begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_WAIT;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MEM_BUS_STATE_WAIT: begin
|
||||||
|
if (dma_stop || mem_bus_last_transfer) begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_IDLE;
|
||||||
|
end else if (mem_bus_wdata_ready || !mem_bus_wdata_empty || mem_bus_rdata_ready) begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_TRANSFER;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MEM_BUS_STATE_TRANSFER: begin
|
||||||
|
if (mem_bus.ack) begin
|
||||||
|
if (dma_stop || mem_bus_last_transfer) begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_IDLE;
|
||||||
|
end else if (!(mem_bus_wdata_ready || !mem_bus_wdata_empty || mem_bus_rdata_ready)) begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_WAIT;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: begin
|
||||||
|
next_mem_bus_state = MEM_BUS_STATE_IDLE;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
mem_bus_rdata_ack <= 1'b0;
|
||||||
|
|
||||||
|
if (mem_bus.ack) begin
|
||||||
|
mem_bus.request <= 1'b0;
|
||||||
|
mem_bus.address <= (mem_bus.address + 26'd2);
|
||||||
|
mem_bus_rdata_ack <= 1'b1;
|
||||||
|
mem_bus_rdata_end <= mem_bus_last_transfer;
|
||||||
|
mem_bus_rdata_valid <= mem_bus.wmask;
|
||||||
|
mem_bus_rdata_buffer <= mem_bus.rdata;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (mem_bus_wdata_ready) begin
|
||||||
|
mem_bus_wdata_empty <= 1'b0;
|
||||||
|
end
|
||||||
|
|
||||||
|
case (mem_bus_state)
|
||||||
|
MEM_BUS_STATE_IDLE: begin
|
||||||
|
mem_bus.request <= 1'b0;
|
||||||
|
mem_bus_rdata_end <= 1'b1;
|
||||||
|
if (dma_start) begin
|
||||||
|
mem_bus.write <= dma_scb.direction;
|
||||||
|
mem_bus.address <= {dma_scb.starting_address[26:1], 1'b0};
|
||||||
|
mem_bus_remaining_bytes <= dma_scb.transfer_length;
|
||||||
|
mem_bus_unaligned_start <= dma_scb.starting_address[0];
|
||||||
|
mem_bus_unaligned_end <= (dma_scb.starting_address[0] ^ dma_scb.transfer_length[0]);
|
||||||
|
mem_bus_rdata_end <= 1'b0;
|
||||||
|
mem_bus_wdata_empty <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MEM_BUS_STATE_WAIT: begin
|
||||||
|
if (!dma_stop && !mem_bus_last_transfer) begin
|
||||||
|
if (mem_bus_wdata_ready || !mem_bus_wdata_empty || mem_bus_rdata_ready) begin
|
||||||
|
mem_bus.request <= 1'b1;
|
||||||
|
mem_bus_unaligned_start <= 1'b0;
|
||||||
|
mem_bus.wdata <= (dma_scb.byte_swap ? {mem_bus_wdata_buffer[7:0], mem_bus_wdata_buffer[15:8]} : mem_bus_wdata_buffer);
|
||||||
|
mem_bus.wmask <= 2'b11;
|
||||||
|
mem_bus_remaining_bytes <= (mem_bus_remaining_bytes - 27'd2);
|
||||||
|
if (mem_bus_unaligned_end && mem_bus_almost_last_transfer) begin
|
||||||
|
mem_bus.wmask <= 2'b10;
|
||||||
|
mem_bus_remaining_bytes <= (mem_bus_remaining_bytes - 27'd1);
|
||||||
|
end
|
||||||
|
if (mem_bus_unaligned_start) begin
|
||||||
|
mem_bus.wmask <= 2'b01;
|
||||||
|
mem_bus_remaining_bytes <= (mem_bus_remaining_bytes - 27'd1);
|
||||||
|
end
|
||||||
|
mem_bus_wdata_empty <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MEM_BUS_STATE_TRANSFER: begin
|
||||||
|
if (!dma_stop && !mem_bus_last_transfer) begin
|
||||||
|
if (mem_bus.ack && (mem_bus_wdata_ready || !mem_bus_wdata_empty || mem_bus_rdata_ready)) begin
|
||||||
|
mem_bus.request <= 1'b1;
|
||||||
|
mem_bus.wdata <= (dma_scb.byte_swap ? {mem_bus_wdata_buffer[7:0], mem_bus_wdata_buffer[15:8]} : mem_bus_wdata_buffer);
|
||||||
|
mem_bus.wmask <= 2'b11;
|
||||||
|
mem_bus_remaining_bytes <= (mem_bus_remaining_bytes - 27'd2);
|
||||||
|
if (mem_bus_unaligned_end && mem_bus_almost_last_transfer) begin
|
||||||
|
mem_bus.wmask <= 2'b10;
|
||||||
|
mem_bus_remaining_bytes <= (mem_bus_remaining_bytes - 27'd1);
|
||||||
|
end
|
||||||
|
mem_bus_wdata_empty <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: begin end
|
||||||
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
// RX FIFO controller
|
// RX FIFO controller
|
||||||
|
|
||||||
logic [1:0] rx_wmask;
|
typedef enum bit [2:0] {
|
||||||
logic rx_rdata_pop;
|
RX_FIFO_BUS_STATE_IDLE,
|
||||||
logic rx_rdata_shift;
|
RX_FIFO_BUS_STATE_WAIT,
|
||||||
logic rx_rdata_valid;
|
RX_FIFO_BUS_STATE_TRANSFER_1,
|
||||||
logic [15:0] rx_buffer;
|
RX_FIFO_BUS_STATE_TRANSFER_2,
|
||||||
logic rx_buffer_valid;
|
RX_FIFO_BUS_STATE_ACK
|
||||||
logic [1:0] rx_buffer_counter;
|
} e_rx_fifo_bus_state;
|
||||||
logic [1:0] rx_buffer_valid_counter;
|
|
||||||
|
e_rx_fifo_bus_state rx_fifo_bus_state;
|
||||||
|
e_rx_fifo_bus_state next_rx_fifo_bus_state;
|
||||||
|
|
||||||
|
logic rx_fifo_shift;
|
||||||
|
logic rx_fifo_shift_delayed;
|
||||||
|
logic [1:0] rx_fifo_valid;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (reset || dma_stop) begin
|
||||||
|
rx_fifo_bus_state <= RX_FIFO_BUS_STATE_IDLE;
|
||||||
|
end else begin
|
||||||
|
rx_fifo_bus_state <= next_rx_fifo_bus_state;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
always_comb begin
|
always_comb begin
|
||||||
rx_buffer_valid = rx_buffer_valid_counter == 2'd2;
|
next_rx_fifo_bus_state = rx_fifo_bus_state;
|
||||||
|
|
||||||
|
rx_fifo_shift = 1'b0;
|
||||||
|
|
||||||
|
fifo_bus.rx_read = 1'b0;
|
||||||
|
|
||||||
|
case (rx_fifo_bus_state)
|
||||||
|
RX_FIFO_BUS_STATE_IDLE: begin
|
||||||
|
if (dma_start && dma_scb.direction) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_WAIT;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RX_FIFO_BUS_STATE_WAIT: begin
|
||||||
|
if (mem_bus_wdata_end) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_IDLE;
|
||||||
|
end else if (mem_bus_wdata_empty) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_TRANSFER_1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RX_FIFO_BUS_STATE_TRANSFER_1: begin
|
||||||
|
fifo_bus.rx_read = (!fifo_bus.rx_empty && rx_fifo_valid[1]);
|
||||||
|
if (!fifo_bus.rx_empty || !rx_fifo_valid[1]) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_TRANSFER_2;
|
||||||
|
rx_fifo_shift = 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RX_FIFO_BUS_STATE_TRANSFER_2: begin
|
||||||
|
fifo_bus.rx_read = (!fifo_bus.rx_empty && rx_fifo_valid[1]);
|
||||||
|
if (!fifo_bus.rx_empty || !rx_fifo_valid[1]) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_ACK;
|
||||||
|
rx_fifo_shift = 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RX_FIFO_BUS_STATE_ACK: begin
|
||||||
|
if (mem_bus_wdata_ready) begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_WAIT;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: begin
|
||||||
|
next_rx_fifo_bus_state = RX_FIFO_BUS_STATE_IDLE;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
rx_rdata_pop <= (
|
mem_bus_wdata_ready <= 1'b0;
|
||||||
!rx_rdata_pop &&
|
rx_fifo_shift_delayed <= rx_fifo_shift;
|
||||||
!fifo_bus.rx_read &&
|
|
||||||
trx_enabled &&
|
|
||||||
rx_buffer_counter < 2'd2 &&
|
|
||||||
!fifo_bus.rx_empty &&
|
|
||||||
mem_bus.write
|
|
||||||
);
|
|
||||||
rx_rdata_shift <= 1'b0;
|
|
||||||
fifo_bus.rx_read <= rx_rdata_pop;
|
|
||||||
rx_rdata_valid <= fifo_bus.rx_read;
|
|
||||||
|
|
||||||
if (dma_start) begin
|
if (rx_fifo_shift) begin
|
||||||
if (dma_scb.starting_address[0]) begin
|
rx_fifo_valid <= {rx_fifo_valid[0], 1'bX};
|
||||||
rx_wmask <= 2'b01;
|
end
|
||||||
rx_buffer_counter <= 2'd1;
|
|
||||||
rx_buffer_valid_counter <= 2'd1;
|
if (rx_fifo_shift_delayed) begin
|
||||||
end else begin
|
if (rx_fifo_bus_state == RX_FIFO_BUS_STATE_ACK) begin
|
||||||
rx_wmask <= 2'b11;
|
mem_bus_wdata_ready <= 1'b1;
|
||||||
rx_buffer_counter <= 2'd0;
|
|
||||||
rx_buffer_valid_counter <= 2'd0;
|
|
||||||
end
|
end
|
||||||
|
mem_bus_wdata_buffer <= {mem_bus_wdata_buffer[7:0], fifo_bus.rx_rdata};
|
||||||
end
|
end
|
||||||
|
|
||||||
if (rx_rdata_pop) begin
|
case (rx_fifo_bus_state)
|
||||||
rx_buffer_counter <= rx_buffer_counter + 1'd1;
|
RX_FIFO_BUS_STATE_WAIT: begin
|
||||||
end
|
if (mem_bus_wdata_empty) begin
|
||||||
|
rx_fifo_valid <= mem_bus_wdata_valid;
|
||||||
if (rx_rdata_shift || rx_rdata_valid) begin
|
end
|
||||||
if (dma_scb.byte_swap) begin
|
|
||||||
rx_buffer <= {fifo_bus.rx_rdata, rx_buffer[15:8]};
|
|
||||||
end else begin
|
|
||||||
rx_buffer <= {rx_buffer[7:0], fifo_bus.rx_rdata};
|
|
||||||
end
|
end
|
||||||
rx_buffer_valid_counter <= rx_buffer_valid_counter + 1'd1;
|
|
||||||
if (remaining == 27'd0 && rx_buffer_counter == 2'd1) begin
|
|
||||||
rx_wmask <= 2'b10;
|
|
||||||
rx_rdata_shift <= 1'b1;
|
|
||||||
rx_buffer_counter <= rx_buffer_counter + 1'd1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (rx_buffer_valid && !mem_bus.request) begin
|
default: begin end
|
||||||
rx_wmask <= 2'b11;
|
endcase
|
||||||
rx_buffer_counter <= 2'd0;
|
|
||||||
rx_buffer_valid_counter <= 2'd0;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
// TX FIFO controller
|
// TX FIFO controller
|
||||||
|
|
||||||
logic tx_wdata_push;
|
typedef enum bit [1:0] {
|
||||||
logic tx_wdata_first_push;
|
TX_FIFO_BUS_STATE_IDLE,
|
||||||
logic [7:0] tx_buffer;
|
TX_FIFO_BUS_STATE_WAIT,
|
||||||
logic tx_buffer_counter;
|
TX_FIFO_BUS_STATE_TRANSFER_1,
|
||||||
logic tx_buffer_ready;
|
TX_FIFO_BUS_STATE_TRANSFER_2
|
||||||
logic tx_buffer_valid;
|
} e_tx_fifo_bus_state;
|
||||||
|
|
||||||
|
e_tx_fifo_bus_state tx_fifo_bus_state;
|
||||||
|
e_tx_fifo_bus_state next_tx_fifo_bus_state;
|
||||||
|
|
||||||
|
logic tx_fifo_shift;
|
||||||
|
logic tx_fifo_waiting;
|
||||||
|
logic [1:0] tx_fifo_valid;
|
||||||
|
logic [15:0] tx_fifo_buffer;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (reset || dma_stop) begin
|
||||||
|
tx_fifo_bus_state <= TX_FIFO_BUS_STATE_IDLE;
|
||||||
|
end else begin
|
||||||
|
tx_fifo_bus_state <= next_tx_fifo_bus_state;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
always_comb begin
|
always_comb begin
|
||||||
fifo_bus.tx_write = tx_wdata_push;
|
next_tx_fifo_bus_state = tx_fifo_bus_state;
|
||||||
end
|
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
tx_fifo_shift = 1'b0;
|
||||||
tx_wdata_push <= (
|
|
||||||
!tx_wdata_push &&
|
|
||||||
trx_enabled &&
|
|
||||||
tx_buffer_valid &&
|
|
||||||
!fifo_bus.tx_full &&
|
|
||||||
!mem_bus.write
|
|
||||||
);
|
|
||||||
|
|
||||||
if (reset || dma_stop) begin
|
fifo_bus.tx_write = 1'b0;
|
||||||
tx_buffer_ready <= 1'b0;
|
fifo_bus.tx_wdata = tx_fifo_buffer[15:8];
|
||||||
tx_buffer_valid <= 1'b0;
|
|
||||||
end
|
|
||||||
|
|
||||||
if (dma_start) begin
|
case (tx_fifo_bus_state)
|
||||||
tx_wdata_first_push <= 1'b1;
|
TX_FIFO_BUS_STATE_IDLE: begin
|
||||||
tx_buffer_ready <= 1'b1;
|
if (dma_start && !dma_scb.direction) begin
|
||||||
tx_buffer_valid <= 1'b0;
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_WAIT;
|
||||||
end
|
end
|
||||||
|
|
||||||
if (tx_buffer_ready && mem_bus.request) begin
|
|
||||||
tx_buffer_ready <= 1'b0;
|
|
||||||
end
|
|
||||||
|
|
||||||
if (mem_bus.ack) begin
|
|
||||||
tx_wdata_first_push <= 1'b0;
|
|
||||||
tx_buffer_counter <= 1'd1;
|
|
||||||
tx_buffer_valid <= 1'b1;
|
|
||||||
{fifo_bus.tx_wdata, tx_buffer} <= mem_bus.rdata;
|
|
||||||
if (tx_wdata_first_push && dma_scb.starting_address[0]) begin
|
|
||||||
fifo_bus.tx_wdata <= mem_bus.rdata[7:0];
|
|
||||||
tx_buffer_counter <= 1'd0;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (tx_wdata_push) begin
|
|
||||||
tx_buffer_counter <= tx_buffer_counter - 1'd1;
|
|
||||||
fifo_bus.tx_wdata <= tx_buffer;
|
|
||||||
if (tx_buffer_counter == 1'd0) begin
|
|
||||||
tx_buffer_ready <= 1'b1;
|
|
||||||
tx_buffer_valid <= 1'b0;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
// Remaining counter controller
|
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
|
||||||
if (reset || dma_stop) begin
|
|
||||||
remaining <= 27'd0;
|
|
||||||
end else begin
|
|
||||||
if (dma_start) begin
|
|
||||||
remaining <= dma_scb.transfer_length;
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if ((mem_bus.write && rx_rdata_pop) || (!mem_bus.write && tx_wdata_push)) begin
|
TX_FIFO_BUS_STATE_WAIT: begin
|
||||||
remaining <= remaining - 1'd1;
|
if (mem_bus_rdata_ack || tx_fifo_waiting) begin
|
||||||
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_TRANSFER_1;
|
||||||
|
end else if (mem_bus_rdata_end) begin
|
||||||
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_IDLE;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
TX_FIFO_BUS_STATE_TRANSFER_1: begin
|
||||||
|
fifo_bus.tx_write = (!fifo_bus.tx_full && tx_fifo_valid[1]);
|
||||||
|
if (!fifo_bus.tx_full || !tx_fifo_valid[1]) begin
|
||||||
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_TRANSFER_2;
|
||||||
|
tx_fifo_shift = 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
// Mem bus controller
|
TX_FIFO_BUS_STATE_TRANSFER_2: begin
|
||||||
|
fifo_bus.tx_write = (!fifo_bus.tx_full && tx_fifo_valid[1]);
|
||||||
always_ff @(posedge clk) begin
|
if (!fifo_bus.tx_full || !tx_fifo_valid[1]) begin
|
||||||
dma_scb.busy <= mem_bus.request || trx_enabled;
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_WAIT;
|
||||||
end
|
tx_fifo_shift = 1'b1;
|
||||||
|
if (!mem_bus_rdata_ack && !tx_fifo_waiting && mem_bus_rdata_end) begin
|
||||||
always_ff @(posedge clk) begin
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_IDLE;
|
||||||
if (reset) begin
|
|
||||||
mem_bus.request <= 1'b0;
|
|
||||||
end else begin
|
|
||||||
if (!mem_bus.request) begin
|
|
||||||
if (mem_bus.write) begin
|
|
||||||
if (rx_buffer_valid) begin
|
|
||||||
mem_bus.request <= 1'b1;
|
|
||||||
mem_bus.wmask <= rx_wmask;
|
|
||||||
mem_bus.wdata <= rx_buffer;
|
|
||||||
end
|
|
||||||
end else begin
|
|
||||||
if (tx_buffer_ready) begin
|
|
||||||
mem_bus.request <= 1'b1;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if (mem_bus.ack) begin
|
default: begin
|
||||||
mem_bus.request <= 1'b0;
|
next_tx_fifo_bus_state = TX_FIFO_BUS_STATE_IDLE;
|
||||||
end
|
end
|
||||||
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
if (dma_start) begin
|
if (tx_fifo_shift) begin
|
||||||
mem_bus.write <= dma_scb.direction;
|
tx_fifo_valid <= {tx_fifo_valid[0], 1'bX};
|
||||||
|
tx_fifo_buffer <= {tx_fifo_buffer[7:0], 8'hXX};
|
||||||
end
|
end
|
||||||
|
|
||||||
|
case (tx_fifo_bus_state)
|
||||||
|
TX_FIFO_BUS_STATE_IDLE: begin
|
||||||
|
mem_bus_rdata_ready <= 1'b0;
|
||||||
|
tx_fifo_waiting <= 1'b0;
|
||||||
|
if (dma_start) begin
|
||||||
|
mem_bus_rdata_ready <= !dma_scb.direction;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TX_FIFO_BUS_STATE_WAIT: begin
|
||||||
|
if (mem_bus_rdata_ack || tx_fifo_waiting) begin
|
||||||
|
mem_bus_rdata_ready <= 1'b0;
|
||||||
|
tx_fifo_waiting <= 1'b0;
|
||||||
|
tx_fifo_valid <= mem_bus_rdata_valid;
|
||||||
|
tx_fifo_buffer <= mem_bus_rdata_buffer;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TX_FIFO_BUS_STATE_TRANSFER_1: begin
|
||||||
|
if (mem_bus_rdata_ack) begin
|
||||||
|
tx_fifo_waiting <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TX_FIFO_BUS_STATE_TRANSFER_2: begin
|
||||||
|
if (mem_bus_rdata_ack) begin
|
||||||
|
tx_fifo_waiting <= 1'b1;
|
||||||
|
end
|
||||||
|
if (fifo_bus.tx_write || !tx_fifo_valid[1]) begin
|
||||||
|
mem_bus_rdata_ready <= !mem_bus_rdata_end;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
default: begin end
|
||||||
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
|
||||||
if (dma_start) begin
|
|
||||||
mem_bus.address <= {dma_scb.starting_address[26:1], 1'b0};
|
|
||||||
end
|
|
||||||
|
|
||||||
if (mem_bus.ack) begin
|
// DMA busy indicator
|
||||||
mem_bus.address <= mem_bus.address + 2'd2;
|
|
||||||
end
|
always_ff @(posedge clk) begin
|
||||||
|
dma_scb.busy <= (
|
||||||
|
(dma_scb.start && !dma_scb.stop) ||
|
||||||
|
dma_start ||
|
||||||
|
(mem_bus_state != MEM_BUS_STATE_IDLE) ||
|
||||||
|
(rx_fifo_bus_state != RX_FIFO_BUS_STATE_IDLE) ||
|
||||||
|
(tx_fifo_bus_state != TX_FIFO_BUS_STATE_IDLE)
|
||||||
|
);
|
||||||
end
|
end
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
@ -14,29 +14,38 @@ module memory_sdram (
|
|||||||
inout [15:0] sdram_dq
|
inout [15:0] sdram_dq
|
||||||
);
|
);
|
||||||
|
|
||||||
localparam [2:0] CAS_LATENCY = 3'd2;
|
// in Hz
|
||||||
|
localparam real FREQUENCY = 100_000_000.0;
|
||||||
|
|
||||||
localparam real T_INIT = 100_000.0;
|
// in clocks
|
||||||
localparam real T_RC = 60.0;
|
localparam bit [2:0] CAS_LATENCY = 3'd2;
|
||||||
localparam real T_RP = 15.0;
|
|
||||||
localparam real T_RCD = 15.0;
|
|
||||||
localparam real T_MRD = 14.0;
|
|
||||||
localparam real T_REF = 7_800.0;
|
|
||||||
|
|
||||||
localparam real T_CLK = (1.0 / 100_000_000) * 1_000_000_000.0;
|
// in nanoseconds
|
||||||
localparam int C_INIT = int'((T_INIT + T_CLK - 1) / T_CLK);
|
localparam real T_INIT = 100_000.0;
|
||||||
localparam int C_RC = int'((T_RC + T_CLK - 1) / T_CLK);
|
localparam real T_MRD = 14.0;
|
||||||
localparam int C_RP = int'((T_RP + T_CLK - 1) / T_CLK);
|
localparam real T_RAS = 37.0;
|
||||||
localparam int C_RCD = int'((T_RCD + T_CLK - 1) / T_CLK);
|
localparam real T_RC = 60.0;
|
||||||
localparam int C_MRD = int'((T_MRD + T_CLK - 1) / T_CLK);
|
localparam real T_RCD = 15.0;
|
||||||
localparam int C_REF = int'((T_REF + T_CLK - 1) / T_CLK);
|
localparam real T_REF = 7_812.5;
|
||||||
|
localparam real T_RP = 15.0;
|
||||||
|
|
||||||
localparam INIT_PRECHARGE = 4'd0;
|
localparam real T_CLK = (1.0 / FREQUENCY) * 1_000_000_000.0;
|
||||||
localparam INIT_REFRESH_1 = C_RP;
|
|
||||||
localparam INIT_REFRESH_2 = C_RP + C_RC;
|
|
||||||
localparam INIT_MODE_REG = C_RP + (2 * C_RC);
|
|
||||||
localparam INIT_DONE = C_RP + (2 * C_RC) + C_MRD;
|
|
||||||
|
|
||||||
|
const bit [13:0] C_INIT = 14'(int'($ceil(T_INIT / T_CLK)));
|
||||||
|
const bit [4:0] C_MRD = 5'(int'($ceil(T_MRD / T_CLK)));
|
||||||
|
const bit [2:0] C_RAS = 3'(int'($ceil(T_RAS / T_CLK)));
|
||||||
|
const bit [4:0] C_RC = 5'(int'($ceil(T_RC / T_CLK)));
|
||||||
|
const bit [4:0] C_RCD = 5'(int'($ceil(T_RCD / T_CLK)));
|
||||||
|
const bit [13:0] C_REF = 14'(int'($ceil(T_REF / T_CLK)));
|
||||||
|
const bit [4:0] C_RP = 5'(int'($ceil(T_RP / T_CLK)));
|
||||||
|
|
||||||
|
const bit [4:0] INIT_PRECHARGE = 5'd0;
|
||||||
|
const bit [4:0] INIT_REFRESH_1 = INIT_PRECHARGE + C_RP;
|
||||||
|
const bit [4:0] INIT_REFRESH_2 = INIT_REFRESH_1 + C_RC;
|
||||||
|
const bit [4:0] INIT_MODE_REG = INIT_REFRESH_2 + C_RC;
|
||||||
|
const bit [4:0] INIT_DONE = INIT_MODE_REG + C_MRD;
|
||||||
|
|
||||||
|
// /CS, /RAS, /CAS, /WE
|
||||||
typedef enum bit [3:0] {
|
typedef enum bit [3:0] {
|
||||||
CMD_DESL = 4'b1111,
|
CMD_DESL = 4'b1111,
|
||||||
CMD_NOP = 4'b0111,
|
CMD_NOP = 4'b0111,
|
||||||
@ -58,13 +67,10 @@ module memory_sdram (
|
|||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
{sdram_cs, sdram_ras, sdram_cas, sdram_we} <= 4'(sdram_next_cmd);
|
{sdram_cs, sdram_ras, sdram_cas, sdram_we} <= 4'(sdram_next_cmd);
|
||||||
|
|
||||||
{sdram_ba, sdram_a} <= 15'd0;
|
{sdram_ba, sdram_a} <= 15'd0;
|
||||||
sdram_dqm <= 2'b00;
|
sdram_dqm <= 2'b00;
|
||||||
|
|
||||||
sdram_dq_input <= sdram_dq;
|
sdram_dq_input <= sdram_dq;
|
||||||
sdram_dq_output <= mem_bus.wdata;
|
sdram_dq_output <= mem_bus.wdata;
|
||||||
|
|
||||||
sdram_dq_output_enable <= 1'b0;
|
sdram_dq_output_enable <= 1'b0;
|
||||||
|
|
||||||
case (sdram_next_cmd)
|
case (sdram_next_cmd)
|
||||||
@ -76,19 +82,31 @@ module memory_sdram (
|
|||||||
|
|
||||||
CMD_ACT: begin
|
CMD_ACT: begin
|
||||||
{sdram_ba, sdram_a} <= mem_bus.address[25:11];
|
{sdram_ba, sdram_a} <= mem_bus.address[25:11];
|
||||||
sdram_dqm <= 2'b00;
|
|
||||||
current_active_bank_row <= mem_bus.address[25:11];
|
current_active_bank_row <= mem_bus.address[25:11];
|
||||||
end
|
end
|
||||||
|
|
||||||
CMD_PRE: begin
|
CMD_PRE: begin
|
||||||
{sdram_ba, sdram_a} <= {2'b00, 2'b00, 1'b1, 10'd0};
|
{sdram_ba, sdram_a} <= {
|
||||||
sdram_dqm <= 2'b00;
|
2'b00, // [BA1:BA0] Don't care
|
||||||
|
2'b00, // [A12:A11] Don't care
|
||||||
|
1'b1, // [A10] Precharge all banks
|
||||||
|
10'd0 // [A9:A0] Don't care
|
||||||
|
};
|
||||||
end
|
end
|
||||||
|
|
||||||
CMD_MRS: begin
|
CMD_MRS: begin
|
||||||
{sdram_ba, sdram_a} <= {2'b00, 1'b0, 1'b0, 2'b00, CAS_LATENCY, 1'b0, 3'b000};
|
{sdram_ba, sdram_a} <= {
|
||||||
sdram_dqm <= 2'b00;
|
2'b00, // [BA1:BA0] Reserved = 0
|
||||||
|
3'b000, // [A12:A10] Reserved = 0
|
||||||
|
1'b0, // [A9] Write Burst Mode = Programmed Burst Length
|
||||||
|
2'b00, // [A8:A7] Operating Mode = Standard Operation
|
||||||
|
CAS_LATENCY, // [A6:A4] Latency Mode = 2
|
||||||
|
1'b0, // [A3] Burst Type = Sequential
|
||||||
|
3'b000 // [A2:A0] Burst Length = 1
|
||||||
|
};
|
||||||
end
|
end
|
||||||
|
|
||||||
|
default: begin end
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -121,35 +139,51 @@ module memory_sdram (
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
logic [13:0] powerup_coutner;
|
logic [13:0] refresh_counter;
|
||||||
logic powerup_done;
|
|
||||||
logic [4:0] wait_counter;
|
logic [4:0] wait_counter;
|
||||||
logic [9:0] refresh_counter;
|
logic [2:0] precharge_counter;
|
||||||
|
logic powerup_done;
|
||||||
logic pending_refresh;
|
logic pending_refresh;
|
||||||
|
logic precharge_valid;
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
if (reset) begin
|
refresh_counter <= refresh_counter + 1'd1;
|
||||||
powerup_coutner <= 14'd0;
|
|
||||||
powerup_done <= 1'b0;
|
if (refresh_counter == C_INIT) begin
|
||||||
end else if (powerup_coutner < C_INIT) begin
|
refresh_counter <= 14'd0;
|
||||||
powerup_coutner <= powerup_coutner + 1'd1;
|
|
||||||
end else begin
|
|
||||||
powerup_done <= 1'b1;
|
powerup_done <= 1'b1;
|
||||||
end
|
end
|
||||||
|
|
||||||
if (reset || state != next_state) begin
|
if (powerup_done && refresh_counter == C_REF - 14'd1) begin
|
||||||
wait_counter <= 5'd0;
|
refresh_counter <= 14'd0;
|
||||||
end else begin
|
pending_refresh <= 1'b1;
|
||||||
wait_counter <= wait_counter + 1'd1;
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if (sdram_next_cmd == CMD_REF) begin
|
if (sdram_next_cmd == CMD_REF) begin
|
||||||
refresh_counter <= 10'd0;
|
|
||||||
pending_refresh <= 1'b0;
|
pending_refresh <= 1'b0;
|
||||||
end else if (refresh_counter < C_REF) begin
|
end
|
||||||
refresh_counter <= refresh_counter + 1'd1;
|
|
||||||
end else begin
|
if (reset) begin
|
||||||
pending_refresh <= 1'b1;
|
refresh_counter <= 14'd0;
|
||||||
|
powerup_done <= 1'b0;
|
||||||
|
pending_refresh <= 1'b0;
|
||||||
|
end
|
||||||
|
|
||||||
|
wait_counter <= wait_counter + 1'd1;
|
||||||
|
|
||||||
|
if (state != next_state) begin
|
||||||
|
wait_counter <= 5'd0;
|
||||||
|
end
|
||||||
|
|
||||||
|
precharge_counter <= precharge_counter + 1'd1;
|
||||||
|
|
||||||
|
if (precharge_counter >= C_RAS - 2'd2) begin
|
||||||
|
precharge_valid <= 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (sdram_next_cmd == CMD_ACT) begin
|
||||||
|
precharge_counter <= 3'd0;
|
||||||
|
precharge_valid <= 1'b0;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -157,6 +191,7 @@ module memory_sdram (
|
|||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
mem_bus.ack <= 1'b0;
|
mem_bus.ack <= 1'b0;
|
||||||
|
|
||||||
read_cmd_ack_delay <= {sdram_next_cmd == CMD_READ, read_cmd_ack_delay[(CAS_LATENCY):1]};
|
read_cmd_ack_delay <= {sdram_next_cmd == CMD_READ, read_cmd_ack_delay[(CAS_LATENCY):1]};
|
||||||
|
|
||||||
if (sdram_next_cmd == CMD_WRITE || read_cmd_ack_delay[0]) begin
|
if (sdram_next_cmd == CMD_WRITE || read_cmd_ack_delay[0]) begin
|
||||||
@ -202,20 +237,20 @@ module memory_sdram (
|
|||||||
end
|
end
|
||||||
|
|
||||||
S_ACTIVATING: begin
|
S_ACTIVATING: begin
|
||||||
if (wait_counter == C_RCD) begin
|
if (wait_counter == C_RCD - 5'd2) begin
|
||||||
next_state = S_ACTIVE;
|
next_state = S_ACTIVE;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
S_ACTIVE: begin
|
S_ACTIVE: begin
|
||||||
if (pending_refresh) begin
|
if (pending_refresh && precharge_valid) begin
|
||||||
next_state = S_PRECHARGE;
|
next_state = S_PRECHARGE;
|
||||||
sdram_next_cmd = CMD_PRE;
|
sdram_next_cmd = CMD_PRE;
|
||||||
end else if (mem_bus.request) begin
|
end else if (mem_bus.request) begin
|
||||||
if (request_in_current_active_bank_row) begin
|
if (request_in_current_active_bank_row) begin
|
||||||
next_state = S_BUSY;
|
next_state = S_BUSY;
|
||||||
sdram_next_cmd = mem_bus.write ? CMD_WRITE : CMD_READ;
|
sdram_next_cmd = mem_bus.write ? CMD_WRITE : CMD_READ;
|
||||||
end else begin
|
end else if (precharge_valid) begin
|
||||||
next_state = S_PRECHARGE;
|
next_state = S_PRECHARGE;
|
||||||
sdram_next_cmd = CMD_PRE;
|
sdram_next_cmd = CMD_PRE;
|
||||||
end
|
end
|
||||||
@ -229,18 +264,13 @@ module memory_sdram (
|
|||||||
end
|
end
|
||||||
|
|
||||||
S_PRECHARGE: begin
|
S_PRECHARGE: begin
|
||||||
if (wait_counter == C_RP) begin
|
if (wait_counter == C_RP - 5'd2) begin
|
||||||
if (pending_refresh) begin
|
next_state = S_IDLE;
|
||||||
next_state = S_REFRESH;
|
|
||||||
sdram_next_cmd = CMD_REF;
|
|
||||||
end else begin
|
|
||||||
next_state = S_IDLE;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
S_REFRESH: begin
|
S_REFRESH: begin
|
||||||
if (wait_counter == C_RC) begin
|
if (wait_counter == C_RC - 5'd2) begin
|
||||||
next_state = S_IDLE;
|
next_state = S_IDLE;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -36,12 +36,10 @@ module sd_dat (
|
|||||||
// FIFO
|
// FIFO
|
||||||
|
|
||||||
logic rx_full;
|
logic rx_full;
|
||||||
logic rx_almost_full;
|
|
||||||
logic rx_write;
|
logic rx_write;
|
||||||
logic [7:0] rx_wdata;
|
logic [7:0] rx_wdata;
|
||||||
|
|
||||||
logic tx_empty;
|
logic tx_empty;
|
||||||
logic tx_almost_empty;
|
|
||||||
logic tx_read;
|
logic tx_read;
|
||||||
logic [7:0] tx_rdata;
|
logic [7:0] tx_rdata;
|
||||||
|
|
||||||
@ -50,12 +48,10 @@ module sd_dat (
|
|||||||
.reset(reset || sd_scb.dat_fifo_flush),
|
.reset(reset || sd_scb.dat_fifo_flush),
|
||||||
|
|
||||||
.empty(fifo_bus.rx_empty),
|
.empty(fifo_bus.rx_empty),
|
||||||
.almost_empty(fifo_bus.rx_almost_empty),
|
|
||||||
.read(fifo_bus.rx_read),
|
.read(fifo_bus.rx_read),
|
||||||
.rdata(fifo_bus.rx_rdata),
|
.rdata(fifo_bus.rx_rdata),
|
||||||
|
|
||||||
.full(rx_full),
|
.full(rx_full),
|
||||||
.almost_full(rx_almost_full),
|
|
||||||
.write(rx_write),
|
.write(rx_write),
|
||||||
.wdata(rx_wdata),
|
.wdata(rx_wdata),
|
||||||
|
|
||||||
@ -67,12 +63,10 @@ module sd_dat (
|
|||||||
.reset(reset || sd_scb.dat_fifo_flush),
|
.reset(reset || sd_scb.dat_fifo_flush),
|
||||||
|
|
||||||
.empty(tx_empty),
|
.empty(tx_empty),
|
||||||
.almost_empty(tx_almost_empty),
|
|
||||||
.read(tx_read),
|
.read(tx_read),
|
||||||
.rdata(tx_rdata),
|
.rdata(tx_rdata),
|
||||||
|
|
||||||
.full(fifo_bus.tx_full),
|
.full(fifo_bus.tx_full),
|
||||||
.almost_full(fifo_bus.tx_almost_full),
|
|
||||||
.write(fifo_bus.tx_write),
|
.write(fifo_bus.tx_write),
|
||||||
.wdata(fifo_bus.tx_wdata),
|
.wdata(fifo_bus.tx_wdata),
|
||||||
|
|
||||||
|
@ -1,39 +1,3 @@
|
|||||||
interface usb_scb ();
|
|
||||||
|
|
||||||
logic fifo_flush;
|
|
||||||
logic reset_pending;
|
|
||||||
logic reset_ack;
|
|
||||||
logic write_buffer_flush;
|
|
||||||
logic [10:0] rx_count;
|
|
||||||
logic [10:0] tx_count;
|
|
||||||
logic pwrsav;
|
|
||||||
logic reset_state;
|
|
||||||
|
|
||||||
modport controller (
|
|
||||||
output fifo_flush,
|
|
||||||
input reset_pending,
|
|
||||||
output reset_ack,
|
|
||||||
output write_buffer_flush,
|
|
||||||
input rx_count,
|
|
||||||
input tx_count,
|
|
||||||
input pwrsav,
|
|
||||||
input reset_state
|
|
||||||
);
|
|
||||||
|
|
||||||
modport usb (
|
|
||||||
input fifo_flush,
|
|
||||||
output reset_pending,
|
|
||||||
input reset_ack,
|
|
||||||
input write_buffer_flush,
|
|
||||||
output rx_count,
|
|
||||||
output tx_count,
|
|
||||||
output pwrsav,
|
|
||||||
output reset_state
|
|
||||||
);
|
|
||||||
|
|
||||||
endinterface
|
|
||||||
|
|
||||||
|
|
||||||
module usb_ft1248 (
|
module usb_ft1248 (
|
||||||
input clk,
|
input clk,
|
||||||
input reset,
|
input reset,
|
||||||
@ -50,27 +14,25 @@ module usb_ft1248 (
|
|||||||
);
|
);
|
||||||
|
|
||||||
logic rx_full;
|
logic rx_full;
|
||||||
logic rx_almost_full;
|
logic rx_write_delayed;
|
||||||
logic rx_write;
|
|
||||||
logic [7:0] rx_wdata;
|
logic [7:0] rx_wdata;
|
||||||
|
|
||||||
logic tx_empty;
|
logic tx_empty;
|
||||||
logic tx_almost_empty;
|
|
||||||
logic tx_read;
|
logic tx_read;
|
||||||
logic [7:0] tx_rdata;
|
logic [7:0] tx_rdata;
|
||||||
|
|
||||||
|
logic fifo_flush;
|
||||||
|
|
||||||
fifo_8kb fifo_8kb_rx_inst (
|
fifo_8kb fifo_8kb_rx_inst (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
.reset(reset || usb_scb.fifo_flush),
|
.reset(fifo_flush),
|
||||||
|
|
||||||
.empty(fifo_bus.rx_empty),
|
.empty(fifo_bus.rx_empty),
|
||||||
.almost_empty(fifo_bus.rx_almost_empty),
|
|
||||||
.read(fifo_bus.rx_read),
|
.read(fifo_bus.rx_read),
|
||||||
.rdata(fifo_bus.rx_rdata),
|
.rdata(fifo_bus.rx_rdata),
|
||||||
|
|
||||||
.full(rx_full),
|
.full(rx_full),
|
||||||
.almost_full(rx_almost_full),
|
.write(rx_write_delayed),
|
||||||
.write(rx_write),
|
|
||||||
.wdata(rx_wdata),
|
.wdata(rx_wdata),
|
||||||
|
|
||||||
.count(usb_scb.rx_count)
|
.count(usb_scb.rx_count)
|
||||||
@ -78,15 +40,13 @@ module usb_ft1248 (
|
|||||||
|
|
||||||
fifo_8kb fifo_8kb_tx_inst (
|
fifo_8kb fifo_8kb_tx_inst (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
.reset(reset || usb_scb.fifo_flush),
|
.reset(fifo_flush),
|
||||||
|
|
||||||
.empty(tx_empty),
|
.empty(tx_empty),
|
||||||
.almost_empty(tx_almost_empty),
|
|
||||||
.read(tx_read),
|
.read(tx_read),
|
||||||
.rdata(tx_rdata),
|
.rdata(tx_rdata),
|
||||||
|
|
||||||
.full(fifo_bus.tx_full),
|
.full(fifo_bus.tx_full),
|
||||||
.almost_full(fifo_bus.tx_almost_full),
|
|
||||||
.write(fifo_bus.tx_write),
|
.write(fifo_bus.tx_write),
|
||||||
.wdata(fifo_bus.tx_wdata),
|
.wdata(fifo_bus.tx_wdata),
|
||||||
|
|
||||||
@ -140,9 +100,10 @@ module usb_ft1248 (
|
|||||||
e_cmd cmd;
|
e_cmd cmd;
|
||||||
e_cmd next_cmd;
|
e_cmd next_cmd;
|
||||||
logic [3:0] phase;
|
logic [3:0] phase;
|
||||||
|
logic rx_write;
|
||||||
|
logic last_rx_failed;
|
||||||
logic last_tx_failed;
|
logic last_tx_failed;
|
||||||
logic reset_reply;
|
logic reset_reply;
|
||||||
logic last_reset_status;
|
|
||||||
logic [4:0] modem_status_counter;
|
logic [4:0] modem_status_counter;
|
||||||
logic write_modem_status_pending;
|
logic write_modem_status_pending;
|
||||||
logic write_buffer_flush_pending;
|
logic write_buffer_flush_pending;
|
||||||
@ -152,7 +113,9 @@ module usb_ft1248 (
|
|||||||
cmd <= next_cmd;
|
cmd <= next_cmd;
|
||||||
|
|
||||||
usb_scb.pwrsav <= !ft_pwrsav;
|
usb_scb.pwrsav <= !ft_pwrsav;
|
||||||
usb_scb.reset_state <= last_reset_status;
|
fifo_flush <= 1'b0;
|
||||||
|
|
||||||
|
rx_write_delayed <= rx_write;
|
||||||
|
|
||||||
phase <= {phase[2:0], phase[3]};
|
phase <= {phase[2:0], phase[3]};
|
||||||
if (state == STATE_IDLE) begin
|
if (state == STATE_IDLE) begin
|
||||||
@ -160,25 +123,49 @@ module usb_ft1248 (
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (reset) begin
|
if (reset) begin
|
||||||
|
usb_scb.fifo_flush_busy <= 1'b0;
|
||||||
|
usb_scb.reset_state <= 1'b0;
|
||||||
|
last_rx_failed <= 1'b0;
|
||||||
last_tx_failed <= 1'b0;
|
last_tx_failed <= 1'b0;
|
||||||
usb_scb.reset_pending <= 1'b0;
|
reset_reply <= 1'b0;
|
||||||
last_reset_status <= 1'b0;
|
|
||||||
modem_status_counter <= 5'd0;
|
modem_status_counter <= 5'd0;
|
||||||
write_modem_status_pending <= 1'b0;
|
write_modem_status_pending <= 1'b1;
|
||||||
write_buffer_flush_pending <= 1'b0;
|
write_buffer_flush_pending <= 1'b0;
|
||||||
end else begin
|
end else begin
|
||||||
if (usb_scb.reset_ack) begin
|
if (usb_scb.fifo_flush) begin
|
||||||
usb_scb.reset_pending <= 1'b0;
|
usb_scb.fifo_flush_busy <= 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (usb_scb.reset_on_ack) begin
|
||||||
reset_reply <= 1'b1;
|
reset_reply <= 1'b1;
|
||||||
write_modem_status_pending <= 1'b1;
|
write_modem_status_pending <= 1'b1;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if (usb_scb.reset_off_ack) begin
|
||||||
|
reset_reply <= 1'b0;
|
||||||
|
write_modem_status_pending <= 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
if (usb_scb.write_buffer_flush) begin
|
if (usb_scb.write_buffer_flush) begin
|
||||||
write_buffer_flush_pending <= 1'b1;
|
write_buffer_flush_pending <= 1'b1;
|
||||||
end
|
end
|
||||||
|
|
||||||
if (state == STATE_IDLE) begin
|
if (state == STATE_IDLE) begin
|
||||||
modem_status_counter <= modem_status_counter + 1'd1;
|
modem_status_counter <= modem_status_counter + 1'd1;
|
||||||
|
if (usb_scb.fifo_flush_busy) begin
|
||||||
|
usb_scb.fifo_flush_busy <= 1'b0;
|
||||||
|
fifo_flush <= 1'b1;
|
||||||
|
last_rx_failed <= 1'b0;
|
||||||
|
last_tx_failed <= 1'b0;
|
||||||
|
end else if (last_rx_failed && !rx_full) begin
|
||||||
|
last_rx_failed <= 1'b0;
|
||||||
|
rx_write_delayed <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((state == STATE_DATA) && (cmd == CMD_READ) && phase[3]) begin
|
||||||
|
rx_wdata <= ft_miosi_in;
|
||||||
|
last_rx_failed <= !ft_miso && rx_full;
|
||||||
end
|
end
|
||||||
|
|
||||||
if ((state == STATE_DATA) && (cmd == CMD_WRITE) && phase[3]) begin
|
if ((state == STATE_DATA) && (cmd == CMD_WRITE) && phase[3]) begin
|
||||||
@ -187,14 +174,7 @@ module usb_ft1248 (
|
|||||||
|
|
||||||
if (!ft_miso && (state == STATE_DATA) && phase[3]) begin
|
if (!ft_miso && (state == STATE_DATA) && phase[3]) begin
|
||||||
if (cmd == CMD_READ_MODEM_STATUS) begin
|
if (cmd == CMD_READ_MODEM_STATUS) begin
|
||||||
last_reset_status <= ft_miosi_in[0];
|
usb_scb.reset_state <= ft_miosi_in[0];
|
||||||
if (!last_reset_status && ft_miosi_in[0]) begin
|
|
||||||
usb_scb.reset_pending <= 1'b1;
|
|
||||||
end
|
|
||||||
if (last_reset_status && !ft_miosi_in[0]) begin
|
|
||||||
reset_reply <= 1'b0;
|
|
||||||
write_modem_status_pending <= 1'b1;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if (cmd == CMD_WRITE_MODEM_STATUS) begin
|
if (cmd == CMD_WRITE_MODEM_STATUS) begin
|
||||||
write_modem_status_pending <= 1'b0;
|
write_modem_status_pending <= 1'b0;
|
||||||
@ -252,8 +232,6 @@ module usb_ft1248 (
|
|||||||
rx_write = 1'b0;
|
rx_write = 1'b0;
|
||||||
tx_read = 1'b0;
|
tx_read = 1'b0;
|
||||||
|
|
||||||
rx_wdata = ft_miosi_in;
|
|
||||||
|
|
||||||
if (!ft_miso && phase[3]) begin
|
if (!ft_miso && phase[3]) begin
|
||||||
case (state)
|
case (state)
|
||||||
STATE_STATUS: begin
|
STATE_STATUS: begin
|
||||||
@ -263,13 +241,15 @@ module usb_ft1248 (
|
|||||||
end
|
end
|
||||||
|
|
||||||
STATE_DATA: begin
|
STATE_DATA: begin
|
||||||
if (cmd == CMD_READ) begin
|
if (cmd == CMD_READ && !rx_full) begin
|
||||||
rx_write = 1'b1;
|
rx_write = 1'b1;
|
||||||
end
|
end
|
||||||
if (cmd == CMD_WRITE && !tx_empty) begin
|
if (cmd == CMD_WRITE && !tx_empty) begin
|
||||||
tx_read = 1'b1;
|
tx_read = 1'b1;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
default: begin end
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -283,7 +263,7 @@ module usb_ft1248 (
|
|||||||
end else begin
|
end else begin
|
||||||
case (state)
|
case (state)
|
||||||
STATE_IDLE: begin
|
STATE_IDLE: begin
|
||||||
if (ft_pwrsav) begin
|
if (ft_pwrsav && !(usb_scb.fifo_flush || usb_scb.fifo_flush_busy || fifo_flush)) begin
|
||||||
if (write_modem_status_pending) begin
|
if (write_modem_status_pending) begin
|
||||||
next_state = STATE_SELECT;
|
next_state = STATE_SELECT;
|
||||||
next_cmd = CMD_WRITE_MODEM_STATUS;
|
next_cmd = CMD_WRITE_MODEM_STATUS;
|
||||||
@ -330,7 +310,7 @@ module usb_ft1248 (
|
|||||||
if (ft_miso) begin
|
if (ft_miso) begin
|
||||||
next_state = STATE_DESELECT;
|
next_state = STATE_DESELECT;
|
||||||
end else if (cmd == CMD_READ) begin
|
end else if (cmd == CMD_READ) begin
|
||||||
if (rx_almost_full) begin
|
if (rx_full) begin
|
||||||
next_state = STATE_DESELECT;
|
next_state = STATE_DESELECT;
|
||||||
end
|
end
|
||||||
end else if (cmd == CMD_WRITE) begin
|
end else if (cmd == CMD_WRITE) begin
|
||||||
|
37
fw/rtl/usb/usb_scb.sv
Normal file
37
fw/rtl/usb/usb_scb.sv
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
interface usb_scb ();
|
||||||
|
|
||||||
|
logic fifo_flush;
|
||||||
|
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 fifo_flush_busy,
|
||||||
|
output write_buffer_flush,
|
||||||
|
input rx_count,
|
||||||
|
input tx_count,
|
||||||
|
input pwrsav,
|
||||||
|
input reset_state,
|
||||||
|
output reset_on_ack,
|
||||||
|
output reset_off_ack
|
||||||
|
);
|
||||||
|
|
||||||
|
modport usb (
|
||||||
|
input fifo_flush,
|
||||||
|
output fifo_flush_busy,
|
||||||
|
input write_buffer_flush,
|
||||||
|
output rx_count,
|
||||||
|
output tx_count,
|
||||||
|
output pwrsav,
|
||||||
|
output reset_state,
|
||||||
|
input reset_on_ack,
|
||||||
|
input reset_off_ack
|
||||||
|
);
|
||||||
|
|
||||||
|
endinterface
|
@ -3,18 +3,19 @@ module fifo_8kb (
|
|||||||
input reset,
|
input reset,
|
||||||
|
|
||||||
output empty,
|
output empty,
|
||||||
output almost_empty,
|
|
||||||
input read,
|
input read,
|
||||||
output [7:0] rdata,
|
output [7:0] rdata,
|
||||||
|
|
||||||
output full,
|
output full,
|
||||||
output almost_full,
|
|
||||||
input write,
|
input write,
|
||||||
input [7:0] wdata,
|
input [7:0] wdata,
|
||||||
|
|
||||||
output logic [10:0] count
|
output logic [10:0] count
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logic almost_empty;
|
||||||
|
logic almost_full;
|
||||||
|
|
||||||
fifo_8kb_lattice_generated fifo_8kb_lattice_generated_inst (
|
fifo_8kb_lattice_generated fifo_8kb_lattice_generated_inst (
|
||||||
.Data(wdata),
|
.Data(wdata),
|
||||||
.WrClock(clk),
|
.WrClock(clk),
|
||||||
@ -25,7 +26,7 @@ module fifo_8kb (
|
|||||||
.RPReset(reset),
|
.RPReset(reset),
|
||||||
.Q(rdata),
|
.Q(rdata),
|
||||||
.Empty(empty),
|
.Empty(empty),
|
||||||
.Full(full),
|
.Full(full),
|
||||||
.AlmostEmpty(almost_empty),
|
.AlmostEmpty(almost_empty),
|
||||||
.AlmostFull(almost_full)
|
.AlmostFull(almost_full)
|
||||||
);
|
);
|
||||||
|
1
fw/tests/.gitignore
vendored
Normal file
1
fw/tests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
35
fw/tests/Makefile
Normal file
35
fw/tests/Makefile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
RTL_DIR = ../rtl
|
||||||
|
BENCHES_DIR = benches
|
||||||
|
MOCKS_DIR = mocks
|
||||||
|
BUILD_DIR = build
|
||||||
|
SRC_DIRS = \
|
||||||
|
$(RTL_DIR)/fifo \
|
||||||
|
$(RTL_DIR)/mcu \
|
||||||
|
$(RTL_DIR)/memory \
|
||||||
|
$(RTL_DIR)/n64 \
|
||||||
|
$(RTL_DIR)/sd \
|
||||||
|
$(RTL_DIR)/serv \
|
||||||
|
$(RTL_DIR)/usb \
|
||||||
|
$(RTL_DIR)/vendor \
|
||||||
|
$(RTL_DIR) \
|
||||||
|
$(MOCKS_DIR)/vendor \
|
||||||
|
$(MOCKS_DIR)
|
||||||
|
|
||||||
|
INC_DIRS = $(addprefix -I, $(SRC_DIRS))
|
||||||
|
TEST_FILES = $(shell find "./$(BENCHES_DIR)" -not -path "$(BUILD_DIR)/*" -type f -name "*_tb.sv")
|
||||||
|
TESTS = $(addprefix $(BUILD_DIR)/, $(basename $(TEST_FILES)))
|
||||||
|
|
||||||
|
VERILATOR_FLAGS = --binary --trace --timescale 10ns/1ns -j --quiet $(INC_DIRS)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%: %.sv
|
||||||
|
@echo "[VERILATOR] $<"
|
||||||
|
@mkdir -p $@.obj
|
||||||
|
@verilator $(VERILATOR_FLAGS) -Mdir $@.obj $< > /dev/null
|
||||||
|
@$@.obj/V$(notdir $@)
|
||||||
|
|
||||||
|
tests: $(TESTS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf ./$(BUILD_DIR)
|
||||||
|
|
||||||
|
.PHONY: tests
|
126
fw/tests/benches/memory_dma_tb.sv
Normal file
126
fw/tests/benches/memory_dma_tb.sv
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
module memory_dma_tb;
|
||||||
|
|
||||||
|
logic clk;
|
||||||
|
logic reset;
|
||||||
|
|
||||||
|
dma_scb dma_scb ();
|
||||||
|
fifo_bus fifo_bus ();
|
||||||
|
mem_bus mem_bus ();
|
||||||
|
|
||||||
|
logic start;
|
||||||
|
logic stop;
|
||||||
|
logic direction;
|
||||||
|
logic byte_swap;
|
||||||
|
logic [26:0] starting_address;
|
||||||
|
logic [26:0] transfer_length;
|
||||||
|
|
||||||
|
logic flush;
|
||||||
|
logic rx_fill_enabled;
|
||||||
|
logic tx_drain_enabled;
|
||||||
|
|
||||||
|
memory_dma memory_dma (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.dma_scb(dma_scb),
|
||||||
|
.fifo_bus(fifo_bus),
|
||||||
|
.mem_bus(mem_bus)
|
||||||
|
);
|
||||||
|
|
||||||
|
dma_controller_mock dma_controller_mock (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.dma_scb(dma_scb),
|
||||||
|
|
||||||
|
.start(start),
|
||||||
|
.stop(stop),
|
||||||
|
.direction(direction),
|
||||||
|
.byte_swap(byte_swap),
|
||||||
|
.starting_address(starting_address),
|
||||||
|
.transfer_length(transfer_length)
|
||||||
|
);
|
||||||
|
|
||||||
|
fifo_bus_fifo_mock #(
|
||||||
|
.DEPTH(8),
|
||||||
|
.FILL_RATE(3),
|
||||||
|
.DRAIN_RATE(3)
|
||||||
|
) fifo_bus_fifo_mock (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.fifo_bus(fifo_bus),
|
||||||
|
|
||||||
|
.flush(flush),
|
||||||
|
|
||||||
|
.rx_fill_enabled(rx_fill_enabled),
|
||||||
|
.tx_drain_enabled(tx_drain_enabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
memory_sdram_mock memory_sdram_mock (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.mem_bus(mem_bus)
|
||||||
|
);
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
clk = 1'b0;
|
||||||
|
forever begin
|
||||||
|
clk = ~clk; #0.5;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
reset = 1'b0;
|
||||||
|
#10;
|
||||||
|
reset = 1'b1;
|
||||||
|
#10;
|
||||||
|
reset = 1'b0;
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$dumpfile("traces/memory_dma_tb.vcd");
|
||||||
|
|
||||||
|
#10000;
|
||||||
|
|
||||||
|
$dumpvars();
|
||||||
|
|
||||||
|
#100;
|
||||||
|
start = 1'b1;
|
||||||
|
direction = 1'b0;
|
||||||
|
byte_swap = 1'b0;
|
||||||
|
starting_address = 27'hFFF1;
|
||||||
|
transfer_length = 27'd64;
|
||||||
|
#1;
|
||||||
|
start = 1'b0;
|
||||||
|
|
||||||
|
#9;
|
||||||
|
tx_drain_enabled = 1'b1;
|
||||||
|
|
||||||
|
#490;
|
||||||
|
stop = 1'b1;
|
||||||
|
#1;
|
||||||
|
stop = 1'b0;
|
||||||
|
|
||||||
|
#165;
|
||||||
|
|
||||||
|
start = 1'b1;
|
||||||
|
direction = 1'b1;
|
||||||
|
#1;
|
||||||
|
start = 1'b0;
|
||||||
|
|
||||||
|
#9;
|
||||||
|
rx_fill_enabled = 1'b1;
|
||||||
|
|
||||||
|
#490;
|
||||||
|
stop = 1'b1;
|
||||||
|
#1;
|
||||||
|
stop = 1'b0;
|
||||||
|
|
||||||
|
#99;
|
||||||
|
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
119
fw/tests/benches/usb_ft1248_tb.sv
Normal file
119
fw/tests/benches/usb_ft1248_tb.sv
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
module usb_ft1248_tb;
|
||||||
|
|
||||||
|
logic clk;
|
||||||
|
logic reset;
|
||||||
|
|
||||||
|
usb_scb usb_scb ();
|
||||||
|
fifo_bus fifo_bus ();
|
||||||
|
|
||||||
|
logic usb_pwrsav;
|
||||||
|
logic usb_clk;
|
||||||
|
logic usb_cs;
|
||||||
|
logic usb_miso;
|
||||||
|
logic [7:0] usb_miosi;
|
||||||
|
|
||||||
|
usb_ft1248 usb_ft1248 (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
.usb_scb(usb_scb),
|
||||||
|
.fifo_bus(fifo_bus),
|
||||||
|
.usb_pwrsav(usb_pwrsav),
|
||||||
|
.usb_clk(usb_clk),
|
||||||
|
.usb_cs(usb_cs),
|
||||||
|
.usb_miso(usb_miso),
|
||||||
|
.usb_miosi(usb_miosi)
|
||||||
|
);
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
clk = 1'b0;
|
||||||
|
forever begin
|
||||||
|
clk = ~clk; #0.5;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
reset = 1'b1;
|
||||||
|
#10;
|
||||||
|
reset = 1'b0;
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$dumpfile("traces/usb_ft1248_tb.vcd");
|
||||||
|
|
||||||
|
$dumpvars();
|
||||||
|
|
||||||
|
usb_pwrsav = 1'b1;
|
||||||
|
usb_miso = 1'b1;
|
||||||
|
|
||||||
|
#100;
|
||||||
|
|
||||||
|
fifo_bus.tx_write = 1'b1;
|
||||||
|
#100;
|
||||||
|
fifo_bus.tx_write = 1'b0;
|
||||||
|
|
||||||
|
#103;
|
||||||
|
|
||||||
|
usb_miso = 1'b0;
|
||||||
|
#80;
|
||||||
|
usb_scb.write_buffer_flush = 1'b1;
|
||||||
|
#1;
|
||||||
|
usb_scb.write_buffer_flush = 1'b0;
|
||||||
|
#20;
|
||||||
|
usb_miso = 1'b1;
|
||||||
|
#26;
|
||||||
|
usb_miso = 1'b0;
|
||||||
|
|
||||||
|
#4430;
|
||||||
|
|
||||||
|
usb_miso = 1'b1;
|
||||||
|
#13;
|
||||||
|
usb_miso = 1'b0;
|
||||||
|
|
||||||
|
#79;
|
||||||
|
|
||||||
|
fifo_bus.rx_read = 1'b1;
|
||||||
|
#1;
|
||||||
|
fifo_bus.rx_read = 1'b0;
|
||||||
|
|
||||||
|
#10;
|
||||||
|
|
||||||
|
fifo_bus.rx_read = 1'b1;
|
||||||
|
#1;
|
||||||
|
fifo_bus.rx_read = 1'b0;
|
||||||
|
|
||||||
|
#80;
|
||||||
|
|
||||||
|
fifo_bus.rx_read = 1'b1;
|
||||||
|
#1;
|
||||||
|
fifo_bus.rx_read = 1'b0;
|
||||||
|
|
||||||
|
#200;
|
||||||
|
|
||||||
|
usb_scb.reset_on_ack = 1'b1;
|
||||||
|
#1;
|
||||||
|
usb_scb.reset_on_ack = 1'b0;
|
||||||
|
|
||||||
|
#200;
|
||||||
|
|
||||||
|
usb_scb.reset_off_ack = 1'b1;
|
||||||
|
#1;
|
||||||
|
usb_scb.reset_off_ack = 1'b0;
|
||||||
|
|
||||||
|
#200;
|
||||||
|
|
||||||
|
usb_scb.fifo_flush = 1'b1;
|
||||||
|
#1;
|
||||||
|
usb_scb.fifo_flush = 1'b0;
|
||||||
|
|
||||||
|
#3000;
|
||||||
|
|
||||||
|
usb_scb.fifo_flush = 1'b1;
|
||||||
|
#1;
|
||||||
|
usb_scb.fifo_flush = 1'b0;
|
||||||
|
|
||||||
|
#6000;
|
||||||
|
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
22
fw/tests/docker_run.sh
Executable file
22
fw/tests/docker_run.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
pushd $(dirname $0) > /dev/null
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
-it \
|
||||||
|
--rm \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
-v "$(pwd)":/work \
|
||||||
|
-v "$(pwd)/../rtl":/rtl \
|
||||||
|
-e CCACHE_DIR=/tmp/ccache \
|
||||||
|
--entrypoint /bin/bash \
|
||||||
|
verilator/verilator:latest \
|
||||||
|
-c "make -j"
|
||||||
|
|
||||||
|
BUILD_ERROR=$?
|
||||||
|
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
if [ $BUILD_ERROR -ne 0 ]; then
|
||||||
|
exit -1
|
||||||
|
fi
|
39
fw/tests/mocks/dma_controller_mock.sv
Normal file
39
fw/tests/mocks/dma_controller_mock.sv
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
module dma_controller_mock (
|
||||||
|
input clk,
|
||||||
|
input reset,
|
||||||
|
|
||||||
|
dma_scb.controller dma_scb,
|
||||||
|
|
||||||
|
input start,
|
||||||
|
input stop,
|
||||||
|
input direction,
|
||||||
|
input byte_swap,
|
||||||
|
input [26:0] starting_address,
|
||||||
|
input [26:0] transfer_length
|
||||||
|
);
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
dma_scb.start <= 1'b0;
|
||||||
|
dma_scb.stop <= 1'b0;
|
||||||
|
|
||||||
|
if (reset) begin
|
||||||
|
dma_scb.direction <= 1'b0;
|
||||||
|
dma_scb.byte_swap <= 1'b0;
|
||||||
|
dma_scb.starting_address <= 27'd0;
|
||||||
|
dma_scb.transfer_length <= 27'd0;
|
||||||
|
end else begin
|
||||||
|
if (start) begin
|
||||||
|
dma_scb.start <= 1'b1;
|
||||||
|
dma_scb.direction <= direction;
|
||||||
|
dma_scb.byte_swap <= byte_swap;
|
||||||
|
dma_scb.starting_address <= starting_address;
|
||||||
|
dma_scb.transfer_length <= transfer_length;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (stop) begin
|
||||||
|
dma_scb.stop <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
145
fw/tests/mocks/fifo_bus_fifo_mock.sv
Normal file
145
fw/tests/mocks/fifo_bus_fifo_mock.sv
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
module fifo_bus_fifo_mock #(
|
||||||
|
parameter int DEPTH = 1024,
|
||||||
|
parameter int FILL_RATE = 3,
|
||||||
|
parameter int DRAIN_RATE = 3
|
||||||
|
) (
|
||||||
|
input clk,
|
||||||
|
input reset,
|
||||||
|
|
||||||
|
fifo_bus.fifo fifo_bus,
|
||||||
|
|
||||||
|
input flush,
|
||||||
|
|
||||||
|
input rx_fill_enabled,
|
||||||
|
input tx_drain_enabled
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam int PTR_BITS = $clog2(DEPTH);
|
||||||
|
|
||||||
|
|
||||||
|
// RX FIFO mock
|
||||||
|
|
||||||
|
logic rx_full;
|
||||||
|
logic rx_write;
|
||||||
|
logic [7:0] rx_wdata;
|
||||||
|
|
||||||
|
logic [PTR_BITS:0] rx_count;
|
||||||
|
|
||||||
|
fifo_mock #(
|
||||||
|
.DEPTH(DEPTH)
|
||||||
|
) fifo_rx (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.empty(fifo_bus.rx_empty),
|
||||||
|
.read(fifo_bus.rx_read),
|
||||||
|
.rdata(fifo_bus.rx_rdata),
|
||||||
|
|
||||||
|
.full(rx_full),
|
||||||
|
.write(rx_write),
|
||||||
|
.wdata(rx_wdata),
|
||||||
|
|
||||||
|
.count(rx_count)
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam int FILL_BITS = $clog2(FILL_RATE);
|
||||||
|
logic [FILL_BITS:0] fill_counter;
|
||||||
|
logic rx_fill;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
rx_fill <= rx_fill_enabled;
|
||||||
|
end
|
||||||
|
|
||||||
|
generate;
|
||||||
|
if (FILL_RATE == 0) begin
|
||||||
|
always_comb begin
|
||||||
|
rx_write = rx_fill && !rx_full;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
always_comb begin
|
||||||
|
rx_write = rx_fill && !rx_full && (fill_counter == (FILL_BITS + 1)'(FILL_RATE));
|
||||||
|
end
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (fill_counter < (FILL_BITS + 1)'(FILL_RATE)) begin
|
||||||
|
fill_counter <= fill_counter + (FILL_BITS + 1)'('d1);
|
||||||
|
end
|
||||||
|
if (reset) begin
|
||||||
|
fill_counter <= (FILL_BITS + 1)'('d0);
|
||||||
|
end else begin
|
||||||
|
if (rx_write) begin
|
||||||
|
fill_counter <= (FILL_BITS + 1)'('d0);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (reset) begin
|
||||||
|
rx_wdata <= 8'h01;
|
||||||
|
end else begin
|
||||||
|
if (rx_write) begin
|
||||||
|
rx_wdata <= rx_wdata + 8'h01;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
// TX FIFO mock
|
||||||
|
|
||||||
|
logic tx_empty;
|
||||||
|
logic tx_read;
|
||||||
|
logic [7:0] tx_rdata;
|
||||||
|
|
||||||
|
logic [PTR_BITS:0] tx_count;
|
||||||
|
|
||||||
|
fifo_mock #(
|
||||||
|
.DEPTH(DEPTH)
|
||||||
|
) fifo_tx (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.empty(tx_empty),
|
||||||
|
.read(tx_read),
|
||||||
|
.rdata(tx_rdata),
|
||||||
|
|
||||||
|
.full(fifo_bus.tx_full),
|
||||||
|
.write(fifo_bus.tx_write),
|
||||||
|
.wdata(fifo_bus.tx_wdata),
|
||||||
|
|
||||||
|
.count(tx_count)
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam int DRAIN_BITS = $clog2(DRAIN_RATE);
|
||||||
|
logic [DRAIN_BITS:0] drain_counter;
|
||||||
|
logic tx_drain;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
tx_drain <= tx_drain_enabled;
|
||||||
|
end
|
||||||
|
|
||||||
|
generate;
|
||||||
|
if (DRAIN_RATE == 0) begin
|
||||||
|
always_comb begin
|
||||||
|
tx_read = tx_drain && !tx_empty;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
always_comb begin
|
||||||
|
tx_read = tx_drain && !tx_empty && (drain_counter == (DRAIN_BITS + 1)'(DRAIN_RATE));
|
||||||
|
end
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (drain_counter < (DRAIN_BITS + 1)'(DRAIN_RATE)) begin
|
||||||
|
drain_counter <= drain_counter + (DRAIN_BITS + 1)'('d1);
|
||||||
|
end
|
||||||
|
if (reset) begin
|
||||||
|
drain_counter <= (DRAIN_BITS + 1)'('d0);
|
||||||
|
end else begin
|
||||||
|
if (tx_read) begin
|
||||||
|
drain_counter <= (DRAIN_BITS + 1)'('d0);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
endmodule
|
49
fw/tests/mocks/fifo_mock.sv
Normal file
49
fw/tests/mocks/fifo_mock.sv
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
module fifo_mock #(
|
||||||
|
parameter int DEPTH = 1024,
|
||||||
|
localparam int PTR_BITS = $clog2(DEPTH)
|
||||||
|
) (
|
||||||
|
input clk,
|
||||||
|
input reset,
|
||||||
|
|
||||||
|
output logic empty,
|
||||||
|
input read,
|
||||||
|
output [7:0] rdata,
|
||||||
|
|
||||||
|
output logic full,
|
||||||
|
input write,
|
||||||
|
input [7:0] wdata,
|
||||||
|
|
||||||
|
output logic [PTR_BITS:0] count
|
||||||
|
);
|
||||||
|
|
||||||
|
logic [7:0] fifo_mem [0:(DEPTH - 1)];
|
||||||
|
logic [(PTR_BITS - 1):0] fifo_rptr;
|
||||||
|
logic [(PTR_BITS - 1):0] fifo_wptr;
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
full = count >= (PTR_BITS + 1)'(DEPTH);
|
||||||
|
empty = count == (PTR_BITS + 1)'('d0);
|
||||||
|
end
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (read) begin
|
||||||
|
rdata <= fifo_mem[fifo_rptr];
|
||||||
|
fifo_rptr <= fifo_rptr + PTR_BITS'('d1);
|
||||||
|
count <= count - (PTR_BITS + 1)'('d1);
|
||||||
|
end
|
||||||
|
if (write) begin
|
||||||
|
fifo_mem[fifo_wptr] <= wdata;
|
||||||
|
fifo_wptr <= fifo_wptr + PTR_BITS'('d1);
|
||||||
|
count <= count + (PTR_BITS + 1)'('d1);
|
||||||
|
end
|
||||||
|
if (read && write) begin
|
||||||
|
count <= count;
|
||||||
|
end
|
||||||
|
if (reset) begin
|
||||||
|
count <= (PTR_BITS + 1)'('d0);
|
||||||
|
fifo_rptr <= PTR_BITS'('d0);
|
||||||
|
fifo_wptr <= PTR_BITS'('d0);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
70
fw/tests/mocks/memory_sdram_mock.sv
Normal file
70
fw/tests/mocks/memory_sdram_mock.sv
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
module memory_sdram_mock (
|
||||||
|
input clk,
|
||||||
|
input reset,
|
||||||
|
|
||||||
|
mem_bus.memory mem_bus
|
||||||
|
);
|
||||||
|
|
||||||
|
logic sdram_cs;
|
||||||
|
logic sdram_ras;
|
||||||
|
logic sdram_cas;
|
||||||
|
logic sdram_we;
|
||||||
|
logic [1:0] sdram_ba;
|
||||||
|
logic [12:0] sdram_a;
|
||||||
|
logic [1:0] sdram_dqm;
|
||||||
|
logic [15:0] sdram_dq;
|
||||||
|
|
||||||
|
memory_sdram memory_sdram_inst (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.mem_bus(mem_bus),
|
||||||
|
|
||||||
|
.sdram_cs(sdram_cs),
|
||||||
|
.sdram_ras(sdram_ras),
|
||||||
|
.sdram_cas(sdram_cas),
|
||||||
|
.sdram_we(sdram_we),
|
||||||
|
.sdram_ba(sdram_ba),
|
||||||
|
.sdram_a(sdram_a),
|
||||||
|
.sdram_dqm(sdram_dqm),
|
||||||
|
.sdram_dq(sdram_dq)
|
||||||
|
);
|
||||||
|
|
||||||
|
logic [1:0] cas_delay;
|
||||||
|
logic [15:0] data_from_sdram;
|
||||||
|
logic [15:0] data_to_sdram;
|
||||||
|
logic [15:0] sdram_dq_driven;
|
||||||
|
|
||||||
|
assign sdram_dq = sdram_dq_driven;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (reset) begin
|
||||||
|
cas_delay <= 2'b00;
|
||||||
|
data_from_sdram <= 16'h0102;
|
||||||
|
data_to_sdram <= 16'hFFFF;
|
||||||
|
end else begin
|
||||||
|
cas_delay <= {cas_delay[0], 1'b0};
|
||||||
|
|
||||||
|
if ({sdram_cs, sdram_ras, sdram_cas, sdram_we} == 4'b0101) begin
|
||||||
|
cas_delay[0] <= 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (cas_delay[1]) begin
|
||||||
|
data_from_sdram <= data_from_sdram + 16'h0202;
|
||||||
|
end
|
||||||
|
|
||||||
|
if ({sdram_cs, sdram_ras, sdram_cas, sdram_we} == 4'b0100) begin
|
||||||
|
if (!sdram_dqm[0]) data_to_sdram[7:0] <= sdram_dq[7:0];
|
||||||
|
if (!sdram_dqm[1]) data_to_sdram[15:8] <= sdram_dq[15:8];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
sdram_dq_driven = 16'hXXXX;
|
||||||
|
if (cas_delay[1]) begin
|
||||||
|
sdram_dq_driven = data_from_sdram;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
33
fw/tests/mocks/vendor/fifo_8kb.sv
vendored
Normal file
33
fw/tests/mocks/vendor/fifo_8kb.sv
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module fifo_8kb (
|
||||||
|
input clk,
|
||||||
|
input reset,
|
||||||
|
|
||||||
|
output empty,
|
||||||
|
input read,
|
||||||
|
output [7:0] rdata,
|
||||||
|
|
||||||
|
output full,
|
||||||
|
input write,
|
||||||
|
input [7:0] wdata,
|
||||||
|
|
||||||
|
output logic [10:0] count
|
||||||
|
);
|
||||||
|
|
||||||
|
fifo_mock #(
|
||||||
|
.DEPTH(1024)
|
||||||
|
) fifo_8kb (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset),
|
||||||
|
|
||||||
|
.empty(empty),
|
||||||
|
.read(read),
|
||||||
|
.rdata(rdata),
|
||||||
|
|
||||||
|
.full(full),
|
||||||
|
.write(write),
|
||||||
|
.wdata(wdata),
|
||||||
|
|
||||||
|
.count(count)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
2
fw/tests/traces/.gitignore
vendored
Normal file
2
fw/tests/traces/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.vcd
|
||||||
|
*.gtkw
|
File diff suppressed because it is too large
Load Diff
@ -79,8 +79,8 @@ typedef enum {
|
|||||||
#define USB_SCR_FIFO_FLUSH (1 << 0)
|
#define USB_SCR_FIFO_FLUSH (1 << 0)
|
||||||
#define USB_SCR_RXNE (1 << 1)
|
#define USB_SCR_RXNE (1 << 1)
|
||||||
#define USB_SCR_TXE (1 << 2)
|
#define USB_SCR_TXE (1 << 2)
|
||||||
#define USB_SCR_RESET_PENDING (1 << 3)
|
#define USB_SCR_RESET_ON_ACK (1 << 3)
|
||||||
#define USB_SCR_RESET_ACK (1 << 4)
|
#define USB_SCR_RESET_OFF_ACK (1 << 4)
|
||||||
#define USB_SCR_WRITE_FLUSH (1 << 5)
|
#define USB_SCR_WRITE_FLUSH (1 << 5)
|
||||||
#define USB_SCR_RX_COUNT_BIT (6)
|
#define USB_SCR_RX_COUNT_BIT (6)
|
||||||
#define USB_SCR_RX_COUNT_MASK (0x7FF << USB_SCR_RX_COUNT_BIT)
|
#define USB_SCR_RX_COUNT_MASK (0x7FF << USB_SCR_RX_COUNT_BIT)
|
||||||
@ -88,6 +88,7 @@ typedef enum {
|
|||||||
#define USB_SCR_TX_COUNT_MASK (0x7FF << USB_SCR_TX_COUNT_BIT)
|
#define USB_SCR_TX_COUNT_MASK (0x7FF << USB_SCR_TX_COUNT_BIT)
|
||||||
#define USB_SCR_RESET_STATE (1 << 28)
|
#define USB_SCR_RESET_STATE (1 << 28)
|
||||||
#define USB_SCR_PWRSAV (1 << 29)
|
#define USB_SCR_PWRSAV (1 << 29)
|
||||||
|
#define USB_SCR_FIFO_FLUSH_BUSY (1 << 30)
|
||||||
|
|
||||||
#define DMA_SCR_START (1 << 0)
|
#define DMA_SCR_START (1 << 0)
|
||||||
#define DMA_SCR_STOP (1 << 1)
|
#define DMA_SCR_STOP (1 << 1)
|
||||||
|
@ -43,6 +43,8 @@ enum tx_state {
|
|||||||
|
|
||||||
|
|
||||||
struct process {
|
struct process {
|
||||||
|
bool last_reset_state;
|
||||||
|
|
||||||
enum rx_state rx_state;
|
enum rx_state rx_state;
|
||||||
uint8_t rx_counter;
|
uint8_t rx_counter;
|
||||||
uint8_t rx_cmd;
|
uint8_t rx_cmd;
|
||||||
@ -80,10 +82,6 @@ static const uint32_t ERR_TOKEN = (0x45525200UL);
|
|||||||
static const uint32_t PKT_TOKEN = (0x504B5400UL);
|
static const uint32_t PKT_TOKEN = (0x504B5400UL);
|
||||||
|
|
||||||
|
|
||||||
static bool usb_dma_ready (void) {
|
|
||||||
return !((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool usb_rx_byte (uint8_t *data) {
|
static bool usb_rx_byte (uint8_t *data) {
|
||||||
if (fpga_usb_status_get() & USB_STATUS_RXNE) {
|
if (fpga_usb_status_get() & USB_STATUS_RXNE) {
|
||||||
*data = fpga_usb_pop();
|
*data = fpga_usb_pop();
|
||||||
@ -149,7 +147,63 @@ static bool usb_rx_cmd (uint8_t *cmd) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void usb_reset (void) {
|
||||||
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP);
|
||||||
|
while (fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY);
|
||||||
|
fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH);
|
||||||
|
while (fpga_reg_get(REG_USB_SCR) & USB_SCR_FIFO_FLUSH_BUSY);
|
||||||
|
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.tx_state = TX_STATE_IDLE;
|
||||||
|
|
||||||
|
p.response_pending = false;
|
||||||
|
p.packet_pending = false;
|
||||||
|
|
||||||
|
p.read_ready = true;
|
||||||
|
p.read_length = 0;
|
||||||
|
p.read_address = 0;
|
||||||
|
|
||||||
|
usb_rx_word_counter = 0;
|
||||||
|
usb_rx_word_buffer = 0;
|
||||||
|
usb_tx_word_counter = 0;
|
||||||
|
usb_rx_cmd_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_flush_packet (void) {
|
||||||
|
if (p.packet_pending && p.packet_info.done_callback) {
|
||||||
|
p.packet_pending = false;
|
||||||
|
p.packet_info.done_callback();
|
||||||
|
}
|
||||||
|
if (p.tx_state != TX_STATE_IDLE && p.tx_info.done_callback) {
|
||||||
|
p.tx_info.done_callback();
|
||||||
|
p.tx_info.done_callback = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_is_active (void) {
|
||||||
|
uint32_t scr = fpga_reg_get(REG_USB_SCR);
|
||||||
|
bool reset_state = (scr & USB_SCR_RESET_STATE);
|
||||||
|
if (p.last_reset_state != reset_state) {
|
||||||
|
p.last_reset_state = reset_state;
|
||||||
|
if (reset_state) {
|
||||||
|
usb_flush_packet();
|
||||||
|
usb_reset();
|
||||||
|
fpga_reg_set(REG_USB_SCR, USB_SCR_WRITE_FLUSH);
|
||||||
|
}
|
||||||
|
fpga_reg_set(REG_USB_SCR, reset_state ? USB_SCR_RESET_ON_ACK : USB_SCR_RESET_OFF_ACK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !(reset_state || (scr & USB_SCR_PWRSAV));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_dma_ready (void) {
|
||||||
|
return !((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY));
|
||||||
|
}
|
||||||
|
|
||||||
static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) {
|
static bool usb_validate_address_length (uint32_t address, uint32_t length, bool exclude_bootloader) {
|
||||||
|
if (length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) {
|
if ((address >= MEMORY_LENGTH) || (length > MEMORY_LENGTH)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -410,27 +464,34 @@ static void usb_rx_process (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (p.rx_state == RX_STATE_FLUSH) {
|
if (p.rx_state == RX_STATE_FLUSH) {
|
||||||
if (usb_dma_ready()) {
|
if (p.rx_args[1] > 0) {
|
||||||
if (p.rx_args[1] != 0) {
|
if (usb_dma_ready()) {
|
||||||
uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1];
|
uint32_t length = (p.rx_args[1] > RX_FLUSH_LENGTH) ? RX_FLUSH_LENGTH : p.rx_args[1];
|
||||||
fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS);
|
if (!p.rx_dma_running) {
|
||||||
fpga_reg_set(REG_USB_DMA_LENGTH, length);
|
fpga_reg_set(REG_USB_DMA_ADDRESS, RX_FLUSH_ADDRESS);
|
||||||
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
|
fpga_reg_set(REG_USB_DMA_LENGTH, length);
|
||||||
p.rx_args[1] -= length;
|
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START);
|
||||||
} else {
|
p.rx_dma_running = true;
|
||||||
if (p.flush_response) {
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
p.response_pending = true;
|
|
||||||
p.response_error = true;
|
|
||||||
} else if (p.flush_packet) {
|
|
||||||
usb_tx_info_t packet_info;
|
|
||||||
usb_create_packet(&packet_info, PACKET_CMD_DATA_FLUSHED);
|
|
||||||
if (usb_enqueue_packet(&packet_info)) {
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
p.rx_args[1] -= length;
|
||||||
|
p.rx_dma_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.rx_args[1] == 0) {
|
||||||
|
if (p.flush_response) {
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = true;
|
||||||
|
} else if (p.flush_packet) {
|
||||||
|
usb_tx_info_t packet_info;
|
||||||
|
usb_create_packet(&packet_info, PACKET_CMD_DATA_FLUSHED);
|
||||||
|
if (usb_enqueue_packet(&packet_info)) {
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -556,42 +617,16 @@ void usb_get_read_info (uint32_t *args) {
|
|||||||
|
|
||||||
|
|
||||||
void usb_init (void) {
|
void usb_init (void) {
|
||||||
fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP);
|
p.last_reset_state = false;
|
||||||
fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH);
|
usb_reset();
|
||||||
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
p.tx_state = TX_STATE_IDLE;
|
|
||||||
|
|
||||||
p.response_pending = false;
|
|
||||||
p.packet_pending = false;
|
|
||||||
|
|
||||||
p.read_ready = true;
|
|
||||||
p.read_length = 0;
|
|
||||||
p.read_address = 0;
|
|
||||||
|
|
||||||
usb_rx_word_counter = 0;
|
|
||||||
usb_rx_word_buffer = 0;
|
|
||||||
usb_tx_word_counter = 0;
|
|
||||||
usb_rx_cmd_counter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void usb_process (void) {
|
void usb_process (void) {
|
||||||
uint32_t scr = fpga_reg_get(REG_USB_SCR);
|
if (usb_is_active()) {
|
||||||
if (scr & (USB_SCR_PWRSAV | USB_SCR_RESET_STATE | USB_SCR_RESET_PENDING)) {
|
|
||||||
if (p.packet_pending && p.packet_info.done_callback) {
|
|
||||||
p.packet_pending = false;
|
|
||||||
p.packet_info.done_callback();
|
|
||||||
}
|
|
||||||
if (scr & USB_SCR_RESET_PENDING) {
|
|
||||||
if (p.tx_state != TX_STATE_IDLE && p.tx_info.done_callback) {
|
|
||||||
p.tx_info.done_callback();
|
|
||||||
}
|
|
||||||
usb_init();
|
|
||||||
fpga_reg_set(REG_USB_SCR, USB_SCR_RESET_ACK);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
usb_rx_process();
|
usb_rx_process();
|
||||||
usb_tx_process();
|
usb_tx_process();
|
||||||
|
} else {
|
||||||
|
usb_flush_packet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
828
sw/deployer/Cargo.lock
generated
828
sw/deployer/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,22 +7,24 @@ description = "SummerCart64 loader and control software"
|
|||||||
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.1.6", features = ["derive"] }
|
clap = { version = "4.5.8", features = ["derive"] }
|
||||||
clap-num = "1.0.2"
|
clap-num = "1.1.1"
|
||||||
colored = "2.0.0"
|
colored = "2.1.0"
|
||||||
crc32fast = "1.3.2"
|
crc32fast = "1.4.2"
|
||||||
ctrlc = "3.2.5"
|
ctrlc = "3.4.4"
|
||||||
encoding_rs = "0.8.32"
|
encoding_rs = "0.8.34"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
image = "0.24.5"
|
image = "0.25.1"
|
||||||
include-flate = { version = "0.2.0", features = ["stable"] }
|
include-flate = { version = "0.2.0", features = ["stable"] }
|
||||||
|
libftdi1-sys = { version = "1.1.3", features = ["libusb1-sys", "vendored"] }
|
||||||
|
libusb1-sys = { version = "0.6.5", features = ["vendored"] }
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
panic-message = "0.3.0"
|
panic-message = "0.3.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rust-ini = "0.18.0"
|
rust-ini = "0.18.0"
|
||||||
serial2 = "0.2.20"
|
serial2 = "0.2.26"
|
||||||
serialport = "4.3.0"
|
serialport = "4.4.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -291,17 +291,7 @@ impl Handler {
|
|||||||
let filename = &if let Some(path) = path {
|
let filename = &if let Some(path) = path {
|
||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
} else {
|
} else {
|
||||||
generate_filename(
|
generate_filename("save", "sav")
|
||||||
"save",
|
|
||||||
match save_writeback.save {
|
|
||||||
sc64::SaveType::Eeprom4k | sc64::SaveType::Eeprom16k => "eep",
|
|
||||||
sc64::SaveType::Sram | sc64::SaveType::SramBanked | sc64::SaveType::Sram1m => {
|
|
||||||
"srm"
|
|
||||||
}
|
|
||||||
sc64::SaveType::Flashram => "fla",
|
|
||||||
_ => "sav",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
match File::create(filename) {
|
match File::create(filename) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
|
@ -25,7 +25,7 @@ struct Cli {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Commands,
|
command: Commands,
|
||||||
|
|
||||||
/// Connect to SC64 device on provided serial port
|
/// Connect to SC64 device on provided local port
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
port: Option<String>,
|
port: Option<String>,
|
||||||
|
|
||||||
@ -350,7 +350,13 @@ fn handle_list_command() -> Result<(), sc64::Error> {
|
|||||||
|
|
||||||
println!("{}", "Found devices:".bold());
|
println!("{}", "Found devices:".bold());
|
||||||
for (i, d) in devices.iter().enumerate() {
|
for (i, d) in devices.iter().enumerate() {
|
||||||
println!(" {i}: [{}] at port [{}]", d.serial_number, d.port);
|
let index = i + 1;
|
||||||
|
println!(
|
||||||
|
" {index}: [{}] at port [{}] (using \"{}\" backend)",
|
||||||
|
d.serial.bold(),
|
||||||
|
d.port.bold(),
|
||||||
|
d.backend.to_string().bold()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -733,35 +739,35 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
|||||||
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
|
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
println!("{}", "SummerCart64 state information:".bold());
|
println!("{}", "SummerCart64 state information:".bold());
|
||||||
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
||||||
println!(" RTC datetime: {}", datetime);
|
println!(" RTC datetime: {}", datetime);
|
||||||
println!(" Boot mode: {}", state.boot_mode);
|
println!(" Boot mode: {}", state.boot_mode);
|
||||||
println!(" Save type: {}", state.save_type);
|
println!(" Save type: {}", state.save_type);
|
||||||
println!(" CIC seed: {}", state.cic_seed);
|
println!(" CIC seed: {}", state.cic_seed);
|
||||||
println!(" TV type: {}", state.tv_type);
|
println!(" TV type: {}", state.tv_type);
|
||||||
println!(" Bootloader switch: {}", state.bootloader_switch);
|
println!(" Bootloader switch: {}", state.bootloader_switch);
|
||||||
println!(" ROM write: {}", state.rom_write_enable);
|
println!(" ROM write: {}", state.rom_write_enable);
|
||||||
println!(" ROM shadow: {}", state.rom_shadow_enable);
|
println!(" ROM shadow: {}", state.rom_shadow_enable);
|
||||||
println!(" ROM extended: {}", state.rom_extended_enable);
|
println!(" ROM extended: {}", state.rom_extended_enable);
|
||||||
println!(" 64DD mode: {}", state.dd_mode);
|
println!(" 64DD mode: {}", state.dd_mode);
|
||||||
println!(" 64DD SD card mode: {}", state.dd_sd_enable);
|
println!(" 64DD SD card mode: {}", state.dd_sd_enable);
|
||||||
println!(" 64DD drive type: {}", state.dd_drive_type);
|
println!(" 64DD drive type: {}", state.dd_drive_type);
|
||||||
println!(" 64DD disk state: {}", state.dd_disk_state);
|
println!(" 64DD disk state: {}", state.dd_disk_state);
|
||||||
println!(" Button mode: {}", state.button_mode);
|
println!(" Button mode: {}", state.button_mode);
|
||||||
println!(" Button state: {}", state.button_state);
|
println!(" Button state: {}", state.button_state);
|
||||||
println!(" LED blink: {}", state.led_enable);
|
println!(" LED blink: {}", state.led_enable);
|
||||||
println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address);
|
println!(" IS-Viewer 64: {}", state.isviewer);
|
||||||
println!("{}", "SummerCart64 diagnostic information:".bold());
|
println!("{}", "SummerCart64 diagnostic information:".bold());
|
||||||
println!(
|
println!(
|
||||||
" Last PI address: 0x{:08X}",
|
" Last PI address: 0x{:08X}",
|
||||||
state.fpga_debug_data.last_pi_address
|
state.fpga_debug_data.last_pi_address
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" PI FIFO flags: {}",
|
" PI FIFO flags: {}",
|
||||||
state.fpga_debug_data.pi_fifo_flags
|
state.fpga_debug_data.pi_fifo_flags
|
||||||
);
|
);
|
||||||
println!(" Current CIC step: {}", state.fpga_debug_data.cic_step);
|
println!(" Current CIC step: {}", state.fpga_debug_data.cic_step);
|
||||||
println!(" Diagnostic data: {}", state.diagnostic_data);
|
println!(" Diagnostic data: {}", state.diagnostic_data);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,3 @@ impl From<std::io::Error> for Error {
|
|||||||
Error::new(format!("IO error: {}", value).as_str())
|
Error::new(format!("IO error: {}", value).as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serialport::Error> for Error {
|
|
||||||
fn from(value: serialport::Error) -> Self {
|
|
||||||
Error::new(format!("SerialPort error: {}", value.description).as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
540
sw/deployer/src/sc64/ftdi.rs
Normal file
540
sw/deployer/src/sc64/ftdi.rs
Normal file
@ -0,0 +1,540 @@
|
|||||||
|
pub struct DeviceInfo {
|
||||||
|
pub description: String,
|
||||||
|
pub serial: String,
|
||||||
|
pub port: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum InterfaceIndex {
|
||||||
|
Any,
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum ModuleDetachMode {
|
||||||
|
AutoDetach,
|
||||||
|
DontDetach,
|
||||||
|
AutoDetachReattach,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ModemStatus {
|
||||||
|
dsr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Wrapper {
|
||||||
|
context: *mut libftdi1_sys::ftdi_context,
|
||||||
|
unclog_buffer: std::collections::VecDeque<u8>,
|
||||||
|
write_buffer: Vec<u8>,
|
||||||
|
read_timeout: std::time::Duration,
|
||||||
|
write_timeout: std::time::Duration,
|
||||||
|
read_chunksize: usize,
|
||||||
|
write_chunksize: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wrapper {
|
||||||
|
const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16);
|
||||||
|
const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
read_timeout: Option<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> std::io::Result<Self> {
|
||||||
|
let context = unsafe { libftdi1_sys::ftdi_new() };
|
||||||
|
if context.is_null() {
|
||||||
|
return Err(std::io::ErrorKind::OutOfMemory.into());
|
||||||
|
}
|
||||||
|
let mut wrapper = Self {
|
||||||
|
context,
|
||||||
|
unclog_buffer: std::collections::VecDeque::new(),
|
||||||
|
write_buffer: vec![],
|
||||||
|
read_timeout: Self::DEFAULT_RW_TIMEOUT,
|
||||||
|
write_timeout: Self::DEFAULT_RW_TIMEOUT,
|
||||||
|
read_chunksize: 4096,
|
||||||
|
write_chunksize: 4096,
|
||||||
|
};
|
||||||
|
wrapper.set_timeouts(read_timeout, write_timeout)?;
|
||||||
|
wrapper.read_data_set_chunksize(wrapper.read_chunksize)?;
|
||||||
|
wrapper.write_data_set_chunksize(wrapper.write_chunksize)?;
|
||||||
|
Ok(wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_devices(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
|
||||||
|
let wrapper = Self::new(None, None)?;
|
||||||
|
|
||||||
|
let mut device_list: *mut libftdi1_sys::ftdi_device_list = std::ptr::null_mut();
|
||||||
|
let devices = unsafe {
|
||||||
|
libftdi1_sys::ftdi_usb_find_all(
|
||||||
|
wrapper.context,
|
||||||
|
&mut device_list,
|
||||||
|
vendor as i32,
|
||||||
|
product as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if devices > 0 {
|
||||||
|
let mut list: Vec<DeviceInfo> = vec![];
|
||||||
|
|
||||||
|
let mut description = [0i8; 128];
|
||||||
|
let mut serial = [0i8; 128];
|
||||||
|
|
||||||
|
let mut device = device_list;
|
||||||
|
let mut index = 0;
|
||||||
|
while !device.is_null() {
|
||||||
|
let result = unsafe {
|
||||||
|
libftdi1_sys::ftdi_usb_get_strings(
|
||||||
|
wrapper.context,
|
||||||
|
(*device).dev,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
description.as_mut_ptr(),
|
||||||
|
description.len() as i32,
|
||||||
|
serial.as_mut_ptr(),
|
||||||
|
serial.len() as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = unsafe { std::ffi::CStr::from_ptr(description.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
let serial = unsafe { std::ffi::CStr::from_ptr(serial.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
let port = if list.binary_search_by(|d| d.serial.cmp(&serial)).is_ok() {
|
||||||
|
format!("i:0x{vendor:04X}:0x{product:04X}:{index}")
|
||||||
|
} else {
|
||||||
|
format!("s:0x{vendor:04X}:0x{product:04X}:{serial}")
|
||||||
|
};
|
||||||
|
|
||||||
|
if result == 0 {
|
||||||
|
list.push(DeviceInfo {
|
||||||
|
description,
|
||||||
|
serial,
|
||||||
|
port,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
device = unsafe { (*device).next };
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort_by(|a, b| a.serial.cmp(&b.serial));
|
||||||
|
|
||||||
|
Ok(list)
|
||||||
|
} else {
|
||||||
|
match devices {
|
||||||
|
0 => Ok(vec![]),
|
||||||
|
-3 => Err(std::io::ErrorKind::OutOfMemory.into()),
|
||||||
|
-5 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-6 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_usb_find_all: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { libftdi1_sys::ftdi_list_free(&mut device_list) }
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn libusb_convert_result(&self, result: i32) -> std::io::Error {
|
||||||
|
if result == libusb1_sys::constants::LIBUSB_ERROR_OVERFLOW {
|
||||||
|
return std::io::Error::other("libusb overflow");
|
||||||
|
}
|
||||||
|
match result {
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_IO => std::io::ErrorKind::UnexpectedEof,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_INVALID_PARAM => std::io::ErrorKind::InvalidInput,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_ACCESS => std::io::ErrorKind::PermissionDenied,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_NO_DEVICE => std::io::ErrorKind::NotConnected,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_NOT_FOUND => std::io::ErrorKind::NotFound,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_BUSY => std::io::ErrorKind::WouldBlock,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_TIMEOUT => std::io::ErrorKind::TimedOut,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_PIPE => std::io::ErrorKind::BrokenPipe,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_INTERRUPTED => std::io::ErrorKind::Interrupted,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_NO_MEM => std::io::ErrorKind::OutOfMemory,
|
||||||
|
libusb1_sys::constants::LIBUSB_ERROR_NOT_SUPPORTED => std::io::ErrorKind::Unsupported,
|
||||||
|
_ => std::io::ErrorKind::Other,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_timeouts(
|
||||||
|
&mut self,
|
||||||
|
read_timeout: Option<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let read_timeout = read_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT);
|
||||||
|
let write_timeout = write_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT);
|
||||||
|
unsafe {
|
||||||
|
(*self.context).usb_read_timeout = i32::try_from(read_timeout.as_millis())
|
||||||
|
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
|
||||||
|
(*self.context).usb_write_timeout = i32::try_from(write_timeout.as_millis())
|
||||||
|
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
|
||||||
|
}
|
||||||
|
self.read_timeout = read_timeout;
|
||||||
|
self.write_timeout = write_timeout;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_module_detach_mode(&mut self, mode: ModuleDetachMode) {
|
||||||
|
let mode = match mode {
|
||||||
|
ModuleDetachMode::AutoDetach => {
|
||||||
|
libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_SIO_MODULE
|
||||||
|
}
|
||||||
|
ModuleDetachMode::DontDetach => {
|
||||||
|
libftdi1_sys::ftdi_module_detach_mode::DONT_DETACH_SIO_MODULE
|
||||||
|
}
|
||||||
|
ModuleDetachMode::AutoDetachReattach => {
|
||||||
|
libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_REATACH_SIO_MODULE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
(*self.context).module_detach_mode = mode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interface(&mut self, interface: InterfaceIndex) -> std::io::Result<()> {
|
||||||
|
let interface = match interface {
|
||||||
|
InterfaceIndex::Any => libftdi1_sys::ftdi_interface::INTERFACE_ANY,
|
||||||
|
InterfaceIndex::A => libftdi1_sys::ftdi_interface::INTERFACE_A,
|
||||||
|
InterfaceIndex::B => libftdi1_sys::ftdi_interface::INTERFACE_B,
|
||||||
|
InterfaceIndex::C => libftdi1_sys::ftdi_interface::INTERFACE_C,
|
||||||
|
InterfaceIndex::D => libftdi1_sys::ftdi_interface::INTERFACE_D,
|
||||||
|
};
|
||||||
|
match unsafe { libftdi1_sys::ftdi_set_interface(self.context, interface) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => Err(std::io::ErrorKind::InvalidInput.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
-3 => Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_set_interface: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usb_open_string(&mut self, description: &str) -> std::io::Result<()> {
|
||||||
|
let description = std::ffi::CString::new(description)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_raw();
|
||||||
|
match unsafe { libftdi1_sys::ftdi_usb_open_string(self.context, description) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-2 => Err(std::io::ErrorKind::ConnectionRefused.into()),
|
||||||
|
-3 => Err(std::io::ErrorKind::NotFound.into()),
|
||||||
|
-4 => Err(std::io::ErrorKind::PermissionDenied.into()),
|
||||||
|
-5 => Err(std::io::ErrorKind::PermissionDenied.into()),
|
||||||
|
-6 => Err(std::io::ErrorKind::ConnectionRefused.into()),
|
||||||
|
-7 => Err(std::io::ErrorKind::ConnectionRefused.into()),
|
||||||
|
-8 => Err(std::io::ErrorKind::ConnectionRefused.into()),
|
||||||
|
-9 => Err(std::io::ErrorKind::ConnectionRefused.into()),
|
||||||
|
-10 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-11 => Err(std::io::ErrorKind::InvalidInput.into()),
|
||||||
|
-12 => Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_usb_open_string: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usb_reset(&mut self) -> std::io::Result<()> {
|
||||||
|
match unsafe { libftdi1_sys::ftdi_usb_reset(self.context) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_usb_reset: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_latency_timer(&mut self, latency: Option<std::time::Duration>) -> std::io::Result<()> {
|
||||||
|
let latency = u8::try_from(latency.unwrap_or(Self::DEFAULT_POLL_TIMEOUT).as_millis())
|
||||||
|
.map_err(|_| std::io::ErrorKind::InvalidInput)?;
|
||||||
|
match unsafe { libftdi1_sys::ftdi_set_latency_timer(self.context, latency) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => Err(std::io::ErrorKind::InvalidInput.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-3 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_set_latency_timer: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> {
|
||||||
|
match unsafe {
|
||||||
|
libftdi1_sys::ftdi_read_data_set_chunksize(
|
||||||
|
self.context,
|
||||||
|
u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
0 => {
|
||||||
|
self.read_chunksize = chunksize;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
-1 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_read_data_set_chunksize: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> {
|
||||||
|
match unsafe {
|
||||||
|
libftdi1_sys::ftdi_write_data_set_chunksize(
|
||||||
|
self.context,
|
||||||
|
u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
0 => {
|
||||||
|
self.write_chunksize = chunksize;
|
||||||
|
self.commit_write()
|
||||||
|
}
|
||||||
|
-1 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_write_data_set_chunksize: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
|
||||||
|
let state = if value { 1 } else { 0 };
|
||||||
|
match unsafe { libftdi1_sys::ftdi_setdtr(self.context, state) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_setdtr: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_modem_status(&mut self) -> std::io::Result<ModemStatus> {
|
||||||
|
const DSR_BIT: u16 = 1 << 5;
|
||||||
|
|
||||||
|
let mut status = 0;
|
||||||
|
|
||||||
|
match unsafe { libftdi1_sys::ftdi_poll_modem_status(self.context, &mut status) } {
|
||||||
|
0 => Ok(ModemStatus {
|
||||||
|
dsr: (status & DSR_BIT) != 0,
|
||||||
|
}),
|
||||||
|
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_poll_modem_status: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tciflush(&mut self) -> std::io::Result<()> {
|
||||||
|
let timeout = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
match self.read(&mut vec![0u8; self.read_chunksize]) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::Interrupted
|
||||||
|
| std::io::ErrorKind::TimedOut
|
||||||
|
| std::io::ErrorKind::WouldBlock => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if timeout.elapsed() > self.read_timeout {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tcoflush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.write_buffer.clear();
|
||||||
|
match unsafe { libftdi1_sys::ftdi_tcoflush(self.context) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-2 => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
-3 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(std::io::Error::other(format!(
|
||||||
|
"Unexpected response from ftdi_tcoflush: {result}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_data(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let length = i32::try_from(buffer.len()).map_err(|_| std::io::ErrorKind::InvalidInput)?;
|
||||||
|
let result =
|
||||||
|
unsafe { libftdi1_sys::ftdi_read_data(self.context, buffer.as_mut_ptr(), length) };
|
||||||
|
match result {
|
||||||
|
1.. => Ok(result as usize),
|
||||||
|
0 => Err(std::io::ErrorKind::WouldBlock.into()),
|
||||||
|
-666 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
|
result => Err(self.libusb_convert_result(result)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_data(&mut self, buffer: &[u8], written: &mut usize) -> std::io::Result<()> {
|
||||||
|
let mut transferred = 0;
|
||||||
|
let result = unsafe {
|
||||||
|
// NOTE: Nasty hack to overcome libftdi1 API limitation.
|
||||||
|
// Write can partially succeed, but the default ftdi_write_data
|
||||||
|
// function doesn't report number of transferred bytes in that case.
|
||||||
|
libusb1_sys::libusb_bulk_transfer(
|
||||||
|
(*self.context).usb_dev,
|
||||||
|
(*self.context).in_ep as u8,
|
||||||
|
Vec::from(buffer).as_mut_ptr(),
|
||||||
|
buffer.len() as i32,
|
||||||
|
&mut transferred,
|
||||||
|
Self::WRITE_CHUNK_TIMEOUT.as_millis() as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
*written = transferred as usize;
|
||||||
|
if result < 0 {
|
||||||
|
return Err(self.libusb_convert_result(result));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unclog_pipe(&mut self) -> std::io::Result<()> {
|
||||||
|
let mut buffer = vec![0u8; self.read_chunksize];
|
||||||
|
let read = match self.read_data(&mut buffer) {
|
||||||
|
Ok(read) => read,
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock => 0,
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.unclog_buffer.extend(buffer[0..read].iter());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_write(&mut self) -> std::io::Result<()> {
|
||||||
|
let timeout = std::time::Instant::now();
|
||||||
|
while !self.write_buffer.is_empty() {
|
||||||
|
let mut written = 0;
|
||||||
|
let result = self.write_data(&self.write_buffer.clone(), &mut written);
|
||||||
|
self.write_buffer.drain(..written);
|
||||||
|
if let Err(error) = result {
|
||||||
|
match error.kind() {
|
||||||
|
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
|
||||||
|
_ => return Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timeout.elapsed() > self.write_timeout {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
if buffer.is_empty() {
|
||||||
|
Err(std::io::ErrorKind::InvalidInput.into())
|
||||||
|
} else if self.unclog_buffer.is_empty() {
|
||||||
|
self.read_data(buffer)
|
||||||
|
} else {
|
||||||
|
for (index, item) in buffer.iter_mut().enumerate() {
|
||||||
|
if let Some(byte) = self.unclog_buffer.pop_front() {
|
||||||
|
*item = byte;
|
||||||
|
} else {
|
||||||
|
return Ok(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(buffer.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let remaining_space = self.write_chunksize - self.write_buffer.len();
|
||||||
|
let length = buffer.len().min(remaining_space);
|
||||||
|
self.write_buffer.extend(&buffer[..length]);
|
||||||
|
if self.write_buffer.len() >= self.write_chunksize {
|
||||||
|
self.commit_write()?
|
||||||
|
}
|
||||||
|
Ok(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.commit_write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Wrapper {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libftdi1_sys::ftdi_free(self.context) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FtdiDevice {
|
||||||
|
wrapper: Wrapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FtdiDevice {
|
||||||
|
pub fn list(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
|
||||||
|
Wrapper::list_devices(vendor, product)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(
|
||||||
|
description: &str,
|
||||||
|
poll_timeout: Option<std::time::Duration>,
|
||||||
|
read_timeout: Option<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> std::io::Result<FtdiDevice> {
|
||||||
|
let mut wrapper = Wrapper::new(read_timeout, write_timeout)?;
|
||||||
|
|
||||||
|
wrapper.set_module_detach_mode(ModuleDetachMode::AutoDetachReattach);
|
||||||
|
wrapper.set_interface(InterfaceIndex::A)?;
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
wrapper.read_data_set_chunksize(CHUNK_SIZE)?;
|
||||||
|
wrapper.write_data_set_chunksize(CHUNK_SIZE)?;
|
||||||
|
|
||||||
|
wrapper.usb_open_string(description)?;
|
||||||
|
|
||||||
|
wrapper.usb_reset()?;
|
||||||
|
|
||||||
|
wrapper.set_latency_timer(poll_timeout)?;
|
||||||
|
|
||||||
|
Ok(FtdiDevice { wrapper })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
|
||||||
|
self.wrapper.set_dtr(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_dsr(&mut self) -> std::io::Result<bool> {
|
||||||
|
Ok(self.wrapper.poll_modem_status()?.dsr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_input(&mut self) -> std::io::Result<()> {
|
||||||
|
self.wrapper.tciflush()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_output(&mut self) -> std::io::Result<()> {
|
||||||
|
self.wrapper.tcoflush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Read for FtdiDevice {
|
||||||
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.wrapper.read(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for FtdiDevice {
|
||||||
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.wrapper.write(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.wrapper.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FtdiDevice {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libftdi1_sys::ftdi_usb_close(self.wrapper.context) };
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
use super::error::Error;
|
use super::{error::Error, ftdi::FtdiDevice, serial::SerialDevice};
|
||||||
use serial2::SerialPort;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::{BufReader, BufWriter, ErrorKind, Read, Write},
|
fmt::Display,
|
||||||
|
io::{BufReader, BufWriter, Read, Write},
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,82 +50,97 @@ pub struct Response {
|
|||||||
pub error: bool,
|
pub error: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Packet {
|
pub struct AsynchronousPacket {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Serial {
|
pub enum UsbPacket {
|
||||||
serial: SerialPort,
|
Response(Response),
|
||||||
|
AsynchronousPacket(AsynchronousPacket),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serial {
|
const SERIAL_PREFIX: &str = "serial://";
|
||||||
fn reset(&self) -> Result<(), Error> {
|
const FTDI_PREFIX: &str = "ftdi://";
|
||||||
const RESET_WAIT_DURATION: Duration = Duration::from_millis(10);
|
|
||||||
const RESET_RETRY_COUNT: i32 = 100;
|
|
||||||
const FLUSH_TIMEOUT: Duration = Duration::from_secs(1);
|
|
||||||
|
|
||||||
self.serial.set_dtr(true)?;
|
const RESET_TIMEOUT: Duration = Duration::from_secs(1);
|
||||||
for n in 0..=RESET_RETRY_COUNT {
|
const POLL_TIMEOUT: Duration = Duration::from_millis(5);
|
||||||
self.serial.discard_buffers()?;
|
const READ_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
thread::sleep(RESET_WAIT_DURATION);
|
const WRITE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
if self.serial.read_dsr()? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if n == RESET_RETRY_COUNT {
|
|
||||||
return Err(Error::new("Couldn't reset SC64 device (on)"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let flush_timeout = Instant::now();
|
pub trait Backend {
|
||||||
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize>;
|
||||||
|
|
||||||
|
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()>;
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()>;
|
||||||
|
|
||||||
|
fn discard_input(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_output(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dtr(&mut self, _value: bool) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dsr(&mut self) -> std::io::Result<bool> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {}
|
||||||
|
|
||||||
|
fn reset(&mut self) -> std::io::Result<()> {
|
||||||
|
self.discard_output()?;
|
||||||
|
|
||||||
|
let timeout = Instant::now();
|
||||||
|
self.set_dtr(true)?;
|
||||||
loop {
|
loop {
|
||||||
match self.serial.read(&mut vec![0; 1]) {
|
if self.read_dsr()? {
|
||||||
Ok(length) => match length {
|
break;
|
||||||
0 => break,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
ErrorKind::TimedOut => break,
|
|
||||||
_ => {
|
|
||||||
return Err(Error::new(
|
|
||||||
format!("Couldn't flush SC64 serial buffer: {error}").as_str(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if flush_timeout.elapsed() >= FLUSH_TIMEOUT {
|
if timeout.elapsed() > RESET_TIMEOUT {
|
||||||
return Err(Error::new("SC64 serial buffer flush took too long"));
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::TimedOut,
|
||||||
|
"Couldn't reset SC64 device (on)",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.serial.set_dtr(false)?;
|
self.discard_input()?;
|
||||||
for n in 0..=RESET_RETRY_COUNT {
|
|
||||||
thread::sleep(RESET_WAIT_DURATION);
|
let timeout = Instant::now();
|
||||||
if !self.serial.read_dsr()? {
|
self.set_dtr(false)?;
|
||||||
|
loop {
|
||||||
|
if !self.read_dsr()? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if n == RESET_RETRY_COUNT {
|
if timeout.elapsed() > RESET_TIMEOUT {
|
||||||
return Err(Error::new("Couldn't reset SC64 device (off)"));
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::TimedOut,
|
||||||
|
"Couldn't reset SC64 device (off)",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_data(&self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
|
fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result<Option<()>> {
|
||||||
let timeout = Instant::now();
|
|
||||||
let mut position = 0;
|
let mut position = 0;
|
||||||
let length = buffer.len();
|
let length = buffer.len();
|
||||||
|
let timeout = Instant::now();
|
||||||
while position < length {
|
while position < length {
|
||||||
if timeout.elapsed() > Duration::from_secs(10) {
|
match self.read(&mut buffer[position..length]) {
|
||||||
return Err(Error::new("Serial read timeout"));
|
Ok(0) => return Err(std::io::ErrorKind::UnexpectedEof.into()),
|
||||||
}
|
|
||||||
match self.serial.read(&mut buffer[position..length]) {
|
|
||||||
Ok(0) => return Err(Error::new("Unexpected end of serial data")),
|
|
||||||
Ok(bytes) => position += bytes,
|
Ok(bytes) => position += bytes,
|
||||||
Err(error) => match error.kind() {
|
Err(error) => match error.kind() {
|
||||||
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {
|
std::io::ErrorKind::Interrupted
|
||||||
|
| std::io::ErrorKind::TimedOut
|
||||||
|
| std::io::ErrorKind::WouldBlock => {
|
||||||
if !block && position == 0 {
|
if !block && position == 0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -134,48 +148,53 @@ impl Serial {
|
|||||||
_ => return Err(error.into()),
|
_ => return Err(error.into()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if timeout.elapsed() > READ_TIMEOUT {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(()))
|
Ok(Some(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_exact(&self, buffer: &mut [u8]) -> Result<(), Error> {
|
fn try_read_header(&mut self, block: bool) -> std::io::Result<Option<[u8; 4]>> {
|
||||||
match self.read_data(buffer, true)? {
|
let mut header = [0u8; 4];
|
||||||
|
Ok(self.try_read_exact(&mut header, block)?.map(|_| header))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_exact(&mut self, buffer: &mut [u8]) -> std::io::Result<()> {
|
||||||
|
match self.try_read_exact(buffer, true)? {
|
||||||
Some(()) => Ok(()),
|
Some(()) => Ok(()),
|
||||||
None => Err(Error::new("Unexpected end of serial data")),
|
None => Err(std::io::ErrorKind::UnexpectedEof.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_header(&self, block: bool) -> Result<Option<[u8; 4]>, Error> {
|
fn send_command(&mut self, command: &Command) -> std::io::Result<()> {
|
||||||
let mut header = [0u8; 4];
|
self.write_all(b"CMD")?;
|
||||||
Ok(self.read_data(&mut header, block)?.map(|_| header))
|
self.write_all(&command.id.to_be_bytes())?;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_command(&self, command: &Command) -> Result<(), Error> {
|
self.write_all(&command.args[0].to_be_bytes())?;
|
||||||
self.serial.write_all(b"CMD")?;
|
self.write_all(&command.args[1].to_be_bytes())?;
|
||||||
self.serial.write_all(&command.id.to_be_bytes())?;
|
|
||||||
self.serial.write_all(&command.args[0].to_be_bytes())?;
|
|
||||||
self.serial.write_all(&command.args[1].to_be_bytes())?;
|
|
||||||
|
|
||||||
self.serial.write_all(&command.data)?;
|
self.write_all(&command.data)?;
|
||||||
|
|
||||||
self.serial.flush()?;
|
self.flush()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_incoming_data(
|
fn process_incoming_data(
|
||||||
&self,
|
&mut self,
|
||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
packets: &mut VecDeque<Packet>,
|
packets: &mut VecDeque<AsynchronousPacket>,
|
||||||
) -> Result<Option<Response>, Error> {
|
) -> std::io::Result<Option<Response>> {
|
||||||
let block = matches!(data_type, DataType::Response);
|
let block = matches!(data_type, DataType::Response);
|
||||||
while let Some(header) = self.read_header(block)? {
|
|
||||||
let (packet_token, error) = (match &header[0..3] {
|
while let Some(header) = self.try_read_header(block)? {
|
||||||
b"CMP" => Ok((false, false)),
|
let (packet_token, error) = match &header[0..3] {
|
||||||
b"PKT" => Ok((true, false)),
|
b"CMP" => (false, false),
|
||||||
b"ERR" => Ok((false, true)),
|
b"PKT" => (true, false),
|
||||||
_ => Err(Error::new("Unknown response token")),
|
b"ERR" => (false, true),
|
||||||
})?;
|
_ => return Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
|
};
|
||||||
let id = header[3];
|
let id = header[3];
|
||||||
|
|
||||||
let mut buffer = [0u8; 4];
|
let mut buffer = [0u8; 4];
|
||||||
@ -187,7 +206,7 @@ impl Serial {
|
|||||||
self.read_exact(&mut data)?;
|
self.read_exact(&mut data)?;
|
||||||
|
|
||||||
if packet_token {
|
if packet_token {
|
||||||
packets.push_back(Packet { id, data });
|
packets.push_back(AsynchronousPacket { id, data });
|
||||||
if matches!(data_type, DataType::Packet) {
|
if matches!(data_type, DataType::Packet) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -200,48 +219,94 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_serial(port: &str) -> Result<Serial, Error> {
|
pub struct SerialBackend {
|
||||||
let mut serial = SerialPort::open(port, 115_200)?;
|
device: SerialDevice,
|
||||||
serial.set_write_timeout(Duration::from_secs(10))?;
|
|
||||||
serial.set_read_timeout(Duration::from_millis(10))?;
|
|
||||||
let backend = Serial { serial };
|
|
||||||
backend.reset()?;
|
|
||||||
Ok(backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Backend {
|
|
||||||
fn send_command(&mut self, command: &Command) -> Result<(), Error>;
|
|
||||||
fn process_incoming_data(
|
|
||||||
&mut self,
|
|
||||||
data_type: DataType,
|
|
||||||
packets: &mut VecDeque<Packet>,
|
|
||||||
) -> Result<Option<Response>, Error>;
|
|
||||||
fn close(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SerialBackend {
|
|
||||||
inner: Serial,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for SerialBackend {
|
impl Backend for SerialBackend {
|
||||||
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
self.inner.send_command(command)
|
self.device.read(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_incoming_data(
|
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
|
||||||
&mut self,
|
self.device.write_all(buffer)
|
||||||
data_type: DataType,
|
}
|
||||||
packets: &mut VecDeque<Packet>,
|
|
||||||
) -> Result<Option<Response>, Error> {
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
self.inner.process_incoming_data(data_type, packets)
|
self.device.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_input(&mut self) -> std::io::Result<()> {
|
||||||
|
self.device.discard_input()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_output(&mut self) -> std::io::Result<()> {
|
||||||
|
self.device.discard_output()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
|
||||||
|
self.device.set_dtr(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dsr(&mut self) -> std::io::Result<bool> {
|
||||||
|
self.device.read_dsr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_serial_backend(port: &str) -> Result<SerialBackend, Error> {
|
fn new_serial_backend(port: &str) -> std::io::Result<SerialBackend> {
|
||||||
let backend = SerialBackend {
|
Ok(SerialBackend {
|
||||||
inner: new_serial(port)?,
|
device: SerialDevice::new(
|
||||||
};
|
port,
|
||||||
Ok(backend)
|
Some(POLL_TIMEOUT),
|
||||||
|
Some(READ_TIMEOUT),
|
||||||
|
Some(WRITE_TIMEOUT),
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FtdiBackend {
|
||||||
|
device: FtdiDevice,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for FtdiBackend {
|
||||||
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.device.read(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
|
||||||
|
self.device.write_all(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.device.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_input(&mut self) -> std::io::Result<()> {
|
||||||
|
self.device.discard_input()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_output(&mut self) -> std::io::Result<()> {
|
||||||
|
self.device.discard_output()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
|
||||||
|
self.device.set_dtr(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dsr(&mut self) -> std::io::Result<bool> {
|
||||||
|
self.device.read_dsr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_ftdi_backend(port: &str) -> std::io::Result<FtdiBackend> {
|
||||||
|
Ok(FtdiBackend {
|
||||||
|
device: FtdiDevice::open(
|
||||||
|
port,
|
||||||
|
Some(POLL_TIMEOUT),
|
||||||
|
Some(READ_TIMEOUT),
|
||||||
|
Some(WRITE_TIMEOUT),
|
||||||
|
)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TcpBackend {
|
struct TcpBackend {
|
||||||
@ -250,58 +315,36 @@ struct TcpBackend {
|
|||||||
writer: BufWriter<TcpStream>,
|
writer: BufWriter<TcpStream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcpBackend {
|
|
||||||
fn read_data(&mut self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
|
|
||||||
let timeout = Instant::now();
|
|
||||||
let mut position = 0;
|
|
||||||
let length = buffer.len();
|
|
||||||
while position < length {
|
|
||||||
if timeout.elapsed() > Duration::from_secs(10) {
|
|
||||||
return Err(Error::new("Stream read timeout"));
|
|
||||||
}
|
|
||||||
match self.reader.read(&mut buffer[position..length]) {
|
|
||||||
Ok(0) => return Err(Error::new("Unexpected end of stream data")),
|
|
||||||
Ok(bytes) => position += bytes,
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {
|
|
||||||
if !block && position == 0 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err(error.into()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_exact(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
|
||||||
match self.read_data(buffer, true)? {
|
|
||||||
Some(()) => Ok(()),
|
|
||||||
None => Err(Error::new("Unexpected end of stream data")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_header(&mut self, block: bool) -> Result<Option<[u8; 4]>, Error> {
|
|
||||||
let mut header = [0u8; 4];
|
|
||||||
Ok(self.read_data(&mut header, block)?.map(|_| header))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Backend for TcpBackend {
|
impl Backend for TcpBackend {
|
||||||
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
let payload_data_type: u32 = DataType::Command.into();
|
self.reader.read(buffer)
|
||||||
self.writer.write_all(&payload_data_type.to_be_bytes())?;
|
}
|
||||||
|
|
||||||
self.writer.write_all(&command.id.to_be_bytes())?;
|
fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> {
|
||||||
self.writer.write_all(&command.args[0].to_be_bytes())?;
|
self.writer.write_all(buffer)
|
||||||
self.writer.write_all(&command.args[1].to_be_bytes())?;
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.writer.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
self.stream.shutdown(std::net::Shutdown::Both).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_command(&mut self, command: &Command) -> std::io::Result<()> {
|
||||||
|
let payload_data_type: u32 = DataType::Command.into();
|
||||||
|
self.write_all(&payload_data_type.to_be_bytes())?;
|
||||||
|
|
||||||
|
self.write_all(&command.id.to_be_bytes())?;
|
||||||
|
self.write_all(&command.args[0].to_be_bytes())?;
|
||||||
|
self.write_all(&command.args[1].to_be_bytes())?;
|
||||||
|
|
||||||
let command_data_length = command.data.len() as u32;
|
let command_data_length = command.data.len() as u32;
|
||||||
self.writer.write_all(&command_data_length.to_be_bytes())?;
|
self.write_all(&command_data_length.to_be_bytes())?;
|
||||||
self.writer.write_all(&command.data)?;
|
self.write_all(&command.data)?;
|
||||||
|
|
||||||
self.writer.flush()?;
|
self.flush()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -309,11 +352,13 @@ impl Backend for TcpBackend {
|
|||||||
fn process_incoming_data(
|
fn process_incoming_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
packets: &mut VecDeque<Packet>,
|
packets: &mut VecDeque<AsynchronousPacket>,
|
||||||
) -> Result<Option<Response>, Error> {
|
) -> std::io::Result<Option<Response>> {
|
||||||
let block = matches!(data_type, DataType::Response);
|
let block = matches!(data_type, DataType::Response);
|
||||||
while let Some(header) = self.read_header(block)? {
|
while let Some(header) = self.try_read_header(block)? {
|
||||||
let payload_data_type: DataType = u32::from_be_bytes(header).try_into()?;
|
let payload_data_type: DataType = u32::from_be_bytes(header)
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| std::io::ErrorKind::InvalidData)?;
|
||||||
let mut buffer = [0u8; 4];
|
let mut buffer = [0u8; 4];
|
||||||
match payload_data_type {
|
match payload_data_type {
|
||||||
DataType::Response => {
|
DataType::Response => {
|
||||||
@ -342,7 +387,7 @@ impl Backend for TcpBackend {
|
|||||||
let mut data = vec![0u8; packet_data_length];
|
let mut data = vec![0u8; packet_data_length];
|
||||||
self.read_exact(&mut data)?;
|
self.read_exact(&mut data)?;
|
||||||
|
|
||||||
packets.push_back(Packet {
|
packets.push_back(AsynchronousPacket {
|
||||||
id: packet_info[0],
|
id: packet_info[0],
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
@ -351,31 +396,20 @@ impl Backend for TcpBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::KeepAlive => {}
|
DataType::KeepAlive => {}
|
||||||
_ => return Err(Error::new("Unexpected payload data type received")),
|
_ => return Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&self) {
|
|
||||||
self.stream.shutdown(std::net::Shutdown::Both).ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
|
fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
|
||||||
let stream = match TcpStream::connect(address) {
|
let stream = TcpStream::connect(address).map_err(|error| {
|
||||||
Ok(stream) => {
|
Error::new(format!("Couldn't connect to [{address}]: {error}").as_str())
|
||||||
stream.set_write_timeout(Some(Duration::from_secs(10)))?;
|
})?;
|
||||||
stream.set_read_timeout(Some(Duration::from_millis(10)))?;
|
stream.set_read_timeout(Some(POLL_TIMEOUT))?;
|
||||||
stream
|
stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
format!("Couldn't connect to [{address}]: {error}").as_str(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let reader = BufReader::new(stream.try_clone()?);
|
let reader = BufReader::new(stream.try_clone()?);
|
||||||
let writer = BufWriter::new(stream.try_clone()?);
|
let writer = BufWriter::new(stream.try_clone()?);
|
||||||
Ok(TcpBackend {
|
Ok(TcpBackend {
|
||||||
@ -385,9 +419,29 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_local_backend(port: &str) -> Result<Box<dyn Backend>, Error> {
|
||||||
|
let mut backend: Box<dyn Backend> = if port.starts_with(SERIAL_PREFIX) {
|
||||||
|
Box::new(new_serial_backend(
|
||||||
|
port.strip_prefix(SERIAL_PREFIX).unwrap_or_default(),
|
||||||
|
)?)
|
||||||
|
} else if port.starts_with(FTDI_PREFIX) {
|
||||||
|
Box::new(new_ftdi_backend(
|
||||||
|
port.strip_prefix(FTDI_PREFIX).unwrap_or_default(),
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
return Err(Error::new("Invalid port prefix provided"));
|
||||||
|
};
|
||||||
|
backend.reset()?;
|
||||||
|
Ok(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_remote_backend(address: &str) -> Result<Box<dyn Backend>, Error> {
|
||||||
|
Ok(Box::new(new_tcp_backend(address)?))
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
backend: Box<dyn Backend>,
|
backend: Box<dyn Backend>,
|
||||||
packets: VecDeque<Packet>,
|
packets: VecDeque<AsynchronousPacket>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Link {
|
impl Link {
|
||||||
@ -415,7 +469,7 @@ impl Link {
|
|||||||
Ok(response.data)
|
Ok(response.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_response(&mut self) -> Result<Response, Error> {
|
pub fn receive_response(&mut self) -> Result<Response, Error> {
|
||||||
match self
|
match self
|
||||||
.backend
|
.backend
|
||||||
.process_incoming_data(DataType::Response, &mut self.packets)
|
.process_incoming_data(DataType::Response, &mut self.packets)
|
||||||
@ -430,7 +484,7 @@ impl Link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive_packet(&mut self) -> Result<Option<Packet>, Error> {
|
pub fn receive_packet(&mut self) -> Result<Option<AsynchronousPacket>, Error> {
|
||||||
if self.packets.len() == 0 {
|
if self.packets.len() == 0 {
|
||||||
let response = self
|
let response = self
|
||||||
.backend
|
.backend
|
||||||
@ -441,6 +495,19 @@ impl Link {
|
|||||||
}
|
}
|
||||||
Ok(self.packets.pop_front())
|
Ok(self.packets.pop_front())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn receive_response_or_packet(&mut self) -> Result<Option<UsbPacket>, Error> {
|
||||||
|
let response = self
|
||||||
|
.backend
|
||||||
|
.process_incoming_data(DataType::Packet, &mut self.packets)?;
|
||||||
|
if let Some(response) = response {
|
||||||
|
return Ok(Some(UsbPacket::Response(response)));
|
||||||
|
}
|
||||||
|
if let Some(packet) = self.packets.pop_front() {
|
||||||
|
return Ok(Some(UsbPacket::AsynchronousPacket(packet)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Link {
|
impl Drop for Link {
|
||||||
@ -451,45 +518,75 @@ impl Drop for Link {
|
|||||||
|
|
||||||
pub fn new_local(port: &str) -> Result<Link, Error> {
|
pub fn new_local(port: &str) -> Result<Link, Error> {
|
||||||
Ok(Link {
|
Ok(Link {
|
||||||
backend: Box::new(new_serial_backend(port)?),
|
backend: new_local_backend(port)?,
|
||||||
packets: VecDeque::new(),
|
packets: VecDeque::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_remote(address: &str) -> Result<Link, Error> {
|
pub fn new_remote(address: &str) -> Result<Link, Error> {
|
||||||
Ok(Link {
|
Ok(Link {
|
||||||
backend: Box::new(new_tcp_backend(address)?),
|
backend: new_remote_backend(address)?,
|
||||||
packets: VecDeque::new(),
|
packets: VecDeque::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalDevice {
|
pub enum BackendType {
|
||||||
pub port: String,
|
Serial,
|
||||||
pub serial_number: String,
|
Ftdi,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_local_devices() -> Result<Vec<LocalDevice>, Error> {
|
impl Display for BackendType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::Serial => "serial",
|
||||||
|
Self::Ftdi => "libftdi",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceInfo {
|
||||||
|
pub backend: BackendType,
|
||||||
|
pub port: String,
|
||||||
|
pub serial: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_local_devices() -> Result<Vec<DeviceInfo>, Error> {
|
||||||
const SC64_VID: u16 = 0x0403;
|
const SC64_VID: u16 = 0x0403;
|
||||||
const SC64_PID: u16 = 0x6014;
|
const SC64_PID: u16 = 0x6014;
|
||||||
const SC64_SID: &str = "SC64";
|
const SC64_SERIAL_PREFIX: &str = "SC64";
|
||||||
|
const SC64_DESCRIPTION: &str = "SC64";
|
||||||
|
|
||||||
let mut serial_devices: Vec<LocalDevice> = Vec::new();
|
let mut devices: Vec<DeviceInfo> = Vec::new();
|
||||||
|
|
||||||
for device in serialport::available_ports()?.into_iter() {
|
if let Ok(list) = FtdiDevice::list(SC64_VID, SC64_PID) {
|
||||||
if let serialport::SerialPortType::UsbPort(info) = device.port_type {
|
for device in list.into_iter() {
|
||||||
let serial_number = info.serial_number.unwrap_or("".to_string());
|
if device.description == SC64_DESCRIPTION {
|
||||||
if info.vid == SC64_VID && info.pid == SC64_PID && serial_number.starts_with(SC64_SID) {
|
devices.push(DeviceInfo {
|
||||||
serial_devices.push(LocalDevice {
|
backend: BackendType::Ftdi,
|
||||||
port: device.port_name,
|
port: format!("{FTDI_PREFIX}{}", device.port),
|
||||||
serial_number,
|
serial: device.serial,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(list) = SerialDevice::list(SC64_VID, SC64_PID) {
|
||||||
|
for device in list.into_iter() {
|
||||||
|
let is_sc64_device = device.description == SC64_DESCRIPTION
|
||||||
|
|| device.serial.starts_with(SC64_SERIAL_PREFIX);
|
||||||
|
if is_sc64_device {
|
||||||
|
devices.push(DeviceInfo {
|
||||||
|
backend: BackendType::Serial,
|
||||||
|
port: format!("{SERIAL_PREFIX}{}", device.port),
|
||||||
|
serial: device.serial,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if serial_devices.len() == 0 {
|
if devices.len() == 0 {
|
||||||
return Err(Error::new("No SC64 devices found"));
|
return Err(Error::new("No SC64 devices found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(serial_devices);
|
return Ok(devices);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
mod cic;
|
mod cic;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod firmware;
|
pub mod firmware;
|
||||||
|
mod ftdi;
|
||||||
mod link;
|
mod link;
|
||||||
|
mod serial;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
mod time;
|
mod time;
|
||||||
mod types;
|
mod types;
|
||||||
@ -12,8 +14,8 @@ pub use self::{
|
|||||||
server::ServerEvent,
|
server::ServerEvent,
|
||||||
types::{
|
types::{
|
||||||
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode,
|
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode,
|
||||||
DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, MemoryTestPattern,
|
DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, ISViewer,
|
||||||
MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType,
|
MemoryTestPattern, MemoryTestPatternResult, SaveType, SaveWriteback, Switch, TvType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ pub struct DeviceState {
|
|||||||
pub rom_write_enable: Switch,
|
pub rom_write_enable: Switch,
|
||||||
pub rom_shadow_enable: Switch,
|
pub rom_shadow_enable: Switch,
|
||||||
pub dd_mode: DdMode,
|
pub dd_mode: DdMode,
|
||||||
pub isv_address: u32,
|
pub isviewer: ISViewer,
|
||||||
pub boot_mode: BootMode,
|
pub boot_mode: BootMode,
|
||||||
pub save_type: SaveType,
|
pub save_type: SaveType,
|
||||||
pub cic_seed: CicSeed,
|
pub cic_seed: CicSeed,
|
||||||
@ -547,7 +549,7 @@ impl SC64 {
|
|||||||
rom_write_enable: get_config!(self, RomWriteEnable)?,
|
rom_write_enable: get_config!(self, RomWriteEnable)?,
|
||||||
rom_shadow_enable: get_config!(self, RomShadowEnable)?,
|
rom_shadow_enable: get_config!(self, RomShadowEnable)?,
|
||||||
dd_mode: get_config!(self, DdMode)?,
|
dd_mode: get_config!(self, DdMode)?,
|
||||||
isv_address: get_config!(self, IsvAddress)?,
|
isviewer: get_config!(self, ISViewer)?,
|
||||||
boot_mode: get_config!(self, BootMode)?,
|
boot_mode: get_config!(self, BootMode)?,
|
||||||
save_type: get_config!(self, SaveType)?,
|
save_type: get_config!(self, SaveType)?,
|
||||||
cic_seed: get_config!(self, CicSeed)?,
|
cic_seed: get_config!(self, CicSeed)?,
|
||||||
@ -597,10 +599,10 @@ impl SC64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.command_config_set(Config::RomWriteEnable(Switch::On))?;
|
self.command_config_set(Config::RomWriteEnable(Switch::On))?;
|
||||||
self.command_config_set(Config::IsvAddress(offset))?;
|
self.command_config_set(Config::ISViewer(ISViewer::Enabled(offset)))?;
|
||||||
} else {
|
} else {
|
||||||
self.command_config_set(Config::RomWriteEnable(Switch::Off))?;
|
self.command_config_set(Config::RomWriteEnable(Switch::Off))?;
|
||||||
self.command_config_set(Config::IsvAddress(0))?;
|
self.command_config_set(Config::ISViewer(ISViewer::Disabled))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -875,13 +877,8 @@ impl SC64 {
|
|||||||
|
|
||||||
impl SC64 {
|
impl SC64 {
|
||||||
pub fn open_local(port: Option<String>) -> Result<Self, Error> {
|
pub fn open_local(port: Option<String>) -> Result<Self, Error> {
|
||||||
let port = if let Some(port) = port {
|
|
||||||
port
|
|
||||||
} else {
|
|
||||||
list_local_devices()?[0].port.clone()
|
|
||||||
};
|
|
||||||
let mut sc64 = SC64 {
|
let mut sc64 = SC64 {
|
||||||
link: link::new_local(&port)?,
|
link: link::new_local(&port.unwrap_or(list_local_devices()?[0].port.clone()))?,
|
||||||
};
|
};
|
||||||
sc64.check_device()?;
|
sc64.check_device()?;
|
||||||
Ok(sc64)
|
Ok(sc64)
|
||||||
|
159
sw/deployer/src/sc64/serial.rs
Normal file
159
sw/deployer/src/sc64/serial.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
pub struct DeviceInfo {
|
||||||
|
pub description: String,
|
||||||
|
pub serial: String,
|
||||||
|
pub port: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerialDevice {
|
||||||
|
serial: serial2::SerialPort,
|
||||||
|
unclog_buffer: std::collections::VecDeque<u8>,
|
||||||
|
poll_timeout: std::time::Duration,
|
||||||
|
read_timeout: std::time::Duration,
|
||||||
|
write_timeout: std::time::Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialDevice {
|
||||||
|
const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16);
|
||||||
|
const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
|
||||||
|
const BUFFER_SIZE: usize = 16 * 1024;
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
port: &str,
|
||||||
|
poll_timeout: Option<std::time::Duration>,
|
||||||
|
read_timeout: Option<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> std::io::Result<Self> {
|
||||||
|
let mut device = Self {
|
||||||
|
serial: serial2::SerialPort::open(port, 115_200)?,
|
||||||
|
unclog_buffer: std::collections::VecDeque::new(),
|
||||||
|
poll_timeout: poll_timeout.unwrap_or(Self::DEFAULT_POLL_TIMEOUT),
|
||||||
|
read_timeout: read_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT),
|
||||||
|
write_timeout: write_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT),
|
||||||
|
};
|
||||||
|
device.serial.set_read_timeout(device.poll_timeout)?;
|
||||||
|
device.serial.set_write_timeout(Self::WRITE_CHUNK_TIMEOUT)?;
|
||||||
|
Ok(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
|
||||||
|
let mut devices = vec![];
|
||||||
|
|
||||||
|
for port in serialport::available_ports()? {
|
||||||
|
if let serialport::SerialPortType::UsbPort(info) = port.port_type {
|
||||||
|
if info.vid == vendor && info.pid == product {
|
||||||
|
devices.push(DeviceInfo {
|
||||||
|
description: info.product.unwrap_or_default(),
|
||||||
|
serial: info.serial_number.unwrap_or_default(),
|
||||||
|
port: port.port_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.sort_by(|a, b| a.serial.cmp(&b.serial));
|
||||||
|
|
||||||
|
Ok(devices)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> {
|
||||||
|
self.serial.set_dtr(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_dsr(&mut self) -> std::io::Result<bool> {
|
||||||
|
self.serial.read_dsr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_input(&mut self) -> std::io::Result<()> {
|
||||||
|
let timeout = std::time::Instant::now();
|
||||||
|
self.serial.discard_input_buffer()?;
|
||||||
|
loop {
|
||||||
|
match self.serial.read(&mut vec![0u8; Self::BUFFER_SIZE]) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::Interrupted
|
||||||
|
| std::io::ErrorKind::TimedOut
|
||||||
|
| std::io::ErrorKind::WouldBlock => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if timeout.elapsed() > self.read_timeout {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_output(&mut self) -> std::io::Result<()> {
|
||||||
|
self.serial.discard_output_buffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unclog_pipe(&mut self) -> std::io::Result<()> {
|
||||||
|
let mut buffer = vec![0u8; Self::BUFFER_SIZE];
|
||||||
|
let read = match self.serial.read(&mut buffer) {
|
||||||
|
Ok(read) => read,
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::Interrupted
|
||||||
|
| std::io::ErrorKind::TimedOut
|
||||||
|
| std::io::ErrorKind::WouldBlock => 0,
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.unclog_buffer.extend(buffer[0..read].iter());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Read for SerialDevice {
|
||||||
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
if buffer.is_empty() {
|
||||||
|
Err(std::io::ErrorKind::InvalidInput.into())
|
||||||
|
} else if self.unclog_buffer.is_empty() {
|
||||||
|
self.serial.read(buffer)
|
||||||
|
} else {
|
||||||
|
for (index, item) in buffer.iter_mut().enumerate() {
|
||||||
|
if let Some(byte) = self.unclog_buffer.pop_front() {
|
||||||
|
*item = byte;
|
||||||
|
} else {
|
||||||
|
return Ok(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(buffer.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for SerialDevice {
|
||||||
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let timeout = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
match self.serial.write(buffer) {
|
||||||
|
Ok(bytes) => return Ok(bytes),
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if timeout.elapsed() > self.write_timeout {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
let timeout = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
match self.serial.flush() {
|
||||||
|
Ok(()) => return Ok(()),
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::TimedOut => self.unclog_pipe()?,
|
||||||
|
_ => return Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if timeout.elapsed() > self.write_timeout {
|
||||||
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,10 @@
|
|||||||
use super::{
|
use super::{
|
||||||
error::Error,
|
error::Error,
|
||||||
link::{list_local_devices, new_serial, Command, DataType, Packet, Response, Serial},
|
link::{
|
||||||
};
|
list_local_devices, new_local, AsynchronousPacket, Command, DataType, Response, UsbPacket,
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
io::{BufReader, BufWriter, ErrorKind, Read, Write},
|
|
||||||
net::{TcpListener, TcpStream},
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc::{channel, Receiver, Sender},
|
|
||||||
Arc,
|
|
||||||
},
|
},
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
pub enum ServerEvent {
|
pub enum ServerEvent {
|
||||||
Listening(String),
|
Listening(String),
|
||||||
@ -22,237 +13,160 @@ pub enum ServerEvent {
|
|||||||
Err(String),
|
Err(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct StreamHandler {
|
||||||
|
stream: std::net::TcpStream,
|
||||||
|
reader: std::io::BufReader<std::net::TcpStream>,
|
||||||
|
writer: std::io::BufWriter<std::net::TcpStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
const WRITE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
const KEEPALIVE_PERIOD: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
|
||||||
|
impl StreamHandler {
|
||||||
|
fn new(stream: std::net::TcpStream) -> std::io::Result<StreamHandler> {
|
||||||
|
let reader = std::io::BufReader::new(stream.try_clone()?);
|
||||||
|
let writer = std::io::BufWriter::new(stream.try_clone()?);
|
||||||
|
stream.set_read_timeout(Some(READ_TIMEOUT))?;
|
||||||
|
stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
|
||||||
|
Ok(StreamHandler {
|
||||||
|
stream,
|
||||||
|
reader,
|
||||||
|
writer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_header(&mut self) -> std::io::Result<Option<[u8; 4]>> {
|
||||||
|
self.stream.set_nonblocking(true)?;
|
||||||
|
let mut header = [0u8; 4];
|
||||||
|
let result = match self.reader.read_exact(&mut header) {
|
||||||
|
Ok(()) => Ok(Some(header)),
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::WouldBlock => Ok(None),
|
||||||
|
_ => Err(error),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.stream.set_nonblocking(false)?;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_command(&mut self) -> std::io::Result<Option<Command>> {
|
||||||
|
if let Some(header) = self.try_read_header()? {
|
||||||
|
if let Ok(data_type) = TryInto::<DataType>::try_into(u32::from_be_bytes(header)) {
|
||||||
|
if !matches!(data_type, DataType::Command) {
|
||||||
|
return Err(std::io::Error::other(
|
||||||
|
"Received data type was not a command data type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = [0u8; 4];
|
||||||
|
let mut id_buffer = [0u8; 1];
|
||||||
|
let mut args = [0u32; 2];
|
||||||
|
|
||||||
|
self.reader.read_exact(&mut id_buffer)?;
|
||||||
|
let id = id_buffer[0];
|
||||||
|
|
||||||
|
self.reader.read_exact(&mut buffer)?;
|
||||||
|
args[0] = u32::from_be_bytes(buffer);
|
||||||
|
self.reader.read_exact(&mut buffer)?;
|
||||||
|
args[1] = u32::from_be_bytes(buffer);
|
||||||
|
|
||||||
|
self.reader.read_exact(&mut buffer)?;
|
||||||
|
let command_data_length = u32::from_be_bytes(buffer) as usize;
|
||||||
|
let mut data = vec![0u8; command_data_length];
|
||||||
|
self.reader.read_exact(&mut data)?;
|
||||||
|
|
||||||
|
Ok(Some(Command { id, args, data }))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_response(&mut self, response: Response) -> std::io::Result<()> {
|
||||||
|
self.writer
|
||||||
|
.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
|
||||||
|
self.writer.write_all(&[response.id])?;
|
||||||
|
self.writer.write_all(&[response.error as u8])?;
|
||||||
|
self.writer
|
||||||
|
.write_all(&(response.data.len() as u32).to_be_bytes())?;
|
||||||
|
self.writer.write_all(&response.data)?;
|
||||||
|
self.writer.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_packet(&mut self, packet: AsynchronousPacket) -> std::io::Result<()> {
|
||||||
|
self.writer
|
||||||
|
.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
|
||||||
|
self.writer.write_all(&[packet.id])?;
|
||||||
|
self.writer
|
||||||
|
.write_all(&(packet.data.len() as u32).to_be_bytes())?;
|
||||||
|
self.writer.write_all(&packet.data)?;
|
||||||
|
self.writer.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_keepalive(&mut self) -> std::io::Result<()> {
|
||||||
|
self.writer
|
||||||
|
.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
|
||||||
|
self.writer.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_accept_connection(port: String, connection: &mut StreamHandler) -> Result<(), Error> {
|
||||||
|
let mut link = new_local(&port)?;
|
||||||
|
|
||||||
|
let mut keepalive = std::time::Instant::now();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match connection.receive_command() {
|
||||||
|
Ok(Some(command)) => {
|
||||||
|
link.execute_command_raw(&command, true, true)?;
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
std::io::ErrorKind::UnexpectedEof => return Ok(()),
|
||||||
|
_ => return Err(error.into()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(usb_packet) = link.receive_response_or_packet()? {
|
||||||
|
match usb_packet {
|
||||||
|
UsbPacket::Response(response) => connection.send_response(response)?,
|
||||||
|
UsbPacket::AsynchronousPacket(packet) => connection.send_packet(packet)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keepalive.elapsed() > KEEPALIVE_PERIOD {
|
||||||
|
keepalive = std::time::Instant::now();
|
||||||
|
connection.send_keepalive().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
port: Option<String>,
|
port: Option<String>,
|
||||||
address: String,
|
address: String,
|
||||||
event_callback: fn(ServerEvent),
|
event_callback: fn(ServerEvent),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let port = if let Some(port) = port {
|
let port = port.unwrap_or(list_local_devices()?[0].port.clone());
|
||||||
port
|
let listener = std::net::TcpListener::bind(address)?;
|
||||||
} else {
|
|
||||||
list_local_devices()?[0].port.clone()
|
|
||||||
};
|
|
||||||
let listener = TcpListener::bind(address)?;
|
|
||||||
let listening_address = listener.local_addr()?;
|
let listening_address = listener.local_addr()?;
|
||||||
|
|
||||||
event_callback(ServerEvent::Listening(listening_address.to_string()));
|
event_callback(ServerEvent::Listening(listening_address.to_string()));
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for incoming in listener.incoming() {
|
||||||
match stream {
|
let stream = incoming?;
|
||||||
Ok(mut stream) => {
|
let peer = stream.peer_addr()?.to_string();
|
||||||
let peer = stream.peer_addr()?.to_string();
|
|
||||||
event_callback(ServerEvent::Connected(peer.clone()));
|
event_callback(ServerEvent::Connected(peer.clone()));
|
||||||
match server_accept_connection(port.clone(), &mut stream) {
|
|
||||||
Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())),
|
match server_accept_connection(port.clone(), &mut StreamHandler::new(stream)?) {
|
||||||
Err(error) => event_callback(ServerEvent::Err(error.to_string())),
|
Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())),
|
||||||
}
|
Err(error) => event_callback(ServerEvent::Err(error.to_string())),
|
||||||
}
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
_ => return Err(error.into()),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Event {
|
|
||||||
Command(Command),
|
|
||||||
Response(Response),
|
|
||||||
Packet(Packet),
|
|
||||||
KeepAlive,
|
|
||||||
Closed(Option<Error>),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_accept_connection(port: String, stream: &mut TcpStream) -> Result<(), Error> {
|
|
||||||
let (event_sender, event_receiver) = channel::<Event>();
|
|
||||||
let exit_flag = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
let mut stream_writer = BufWriter::new(stream.try_clone()?);
|
|
||||||
let mut stream_reader = stream.try_clone()?;
|
|
||||||
|
|
||||||
let serial = Arc::new(new_serial(&port)?);
|
|
||||||
let serial_writer = serial.clone();
|
|
||||||
let serial_reader = serial.clone();
|
|
||||||
|
|
||||||
let stream_event_sender = event_sender.clone();
|
|
||||||
let stream_exit_flag = exit_flag.clone();
|
|
||||||
let stream_thread = thread::spawn(move || {
|
|
||||||
let closed_sender = stream_event_sender.clone();
|
|
||||||
match server_stream_thread(&mut stream_reader, stream_event_sender, stream_exit_flag) {
|
|
||||||
Ok(()) => closed_sender.send(Event::Closed(None)),
|
|
||||||
Err(error) => closed_sender.send(Event::Closed(Some(error))),
|
|
||||||
}
|
|
||||||
.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
let serial_event_sender = event_sender.clone();
|
|
||||||
let serial_exit_flag = exit_flag.clone();
|
|
||||||
let serial_thread = thread::spawn(move || {
|
|
||||||
let closed_sender = serial_event_sender.clone();
|
|
||||||
match server_serial_thread(serial_reader, serial_event_sender, serial_exit_flag) {
|
|
||||||
Ok(()) => closed_sender.send(Event::Closed(None)),
|
|
||||||
Err(error) => closed_sender.send(Event::Closed(Some(error))),
|
|
||||||
}
|
|
||||||
.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
let keepalive_event_sender = event_sender.clone();
|
|
||||||
let keepalive_exit_flag = exit_flag.clone();
|
|
||||||
let keepalive_thread = thread::spawn(move || {
|
|
||||||
server_keepalive_thread(keepalive_event_sender, keepalive_exit_flag);
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = server_process_events(&mut stream_writer, serial_writer, event_receiver);
|
|
||||||
|
|
||||||
exit_flag.store(true, Ordering::Relaxed);
|
|
||||||
stream_thread.join().ok();
|
|
||||||
serial_thread.join().ok();
|
|
||||||
keepalive_thread.join().ok();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_process_events(
|
|
||||||
stream_writer: &mut BufWriter<TcpStream>,
|
|
||||||
serial_writer: Arc<Serial>,
|
|
||||||
event_receiver: Receiver<Event>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
for event in event_receiver.into_iter() {
|
|
||||||
match event {
|
|
||||||
Event::Command(command) => {
|
|
||||||
serial_writer.send_command(&command)?;
|
|
||||||
}
|
|
||||||
Event::Response(response) => {
|
|
||||||
stream_writer.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
|
|
||||||
stream_writer.write_all(&[response.id])?;
|
|
||||||
stream_writer.write_all(&[response.error as u8])?;
|
|
||||||
stream_writer.write_all(&(response.data.len() as u32).to_be_bytes())?;
|
|
||||||
stream_writer.write_all(&response.data)?;
|
|
||||||
stream_writer.flush()?;
|
|
||||||
}
|
|
||||||
Event::Packet(packet) => {
|
|
||||||
stream_writer.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
|
|
||||||
stream_writer.write_all(&[packet.id])?;
|
|
||||||
stream_writer.write_all(&(packet.data.len() as u32).to_be_bytes())?;
|
|
||||||
stream_writer.write_all(&packet.data)?;
|
|
||||||
stream_writer.flush()?;
|
|
||||||
}
|
|
||||||
Event::KeepAlive => {
|
|
||||||
stream_writer.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
|
|
||||||
stream_writer.flush()?;
|
|
||||||
}
|
|
||||||
Event::Closed(result) => match result {
|
|
||||||
Some(error) => return Err(error),
|
|
||||||
None => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_stream_thread(
|
|
||||||
stream: &mut TcpStream,
|
|
||||||
event_sender: Sender<Event>,
|
|
||||||
exit_flag: Arc<AtomicBool>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut stream_reader = BufReader::new(stream.try_clone()?);
|
|
||||||
|
|
||||||
let mut header = [0u8; 4];
|
|
||||||
let header_length = header.len();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut header_position = 0;
|
|
||||||
|
|
||||||
let timeout = stream.read_timeout()?;
|
|
||||||
stream.set_read_timeout(Some(Duration::from_millis(10)))?;
|
|
||||||
while header_position < header_length {
|
|
||||||
if exit_flag.load(Ordering::Relaxed) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
match stream_reader.read(&mut header[header_position..header_length]) {
|
|
||||||
Ok(0) => return Ok(()),
|
|
||||||
Ok(bytes) => header_position += bytes,
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {}
|
|
||||||
_ => return Err(error.into()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream.set_read_timeout(timeout)?;
|
|
||||||
|
|
||||||
let data_type: DataType = u32::from_be_bytes(header).try_into()?;
|
|
||||||
if !matches!(data_type, DataType::Command) {
|
|
||||||
return Err(Error::new("Received data type was not a command data type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buffer = [0u8; 4];
|
|
||||||
let mut id_buffer = [0u8; 1];
|
|
||||||
let mut args = [0u32; 2];
|
|
||||||
|
|
||||||
stream_reader.read_exact(&mut id_buffer)?;
|
|
||||||
let id = id_buffer[0];
|
|
||||||
|
|
||||||
stream_reader.read_exact(&mut buffer)?;
|
|
||||||
args[0] = u32::from_be_bytes(buffer);
|
|
||||||
stream_reader.read_exact(&mut buffer)?;
|
|
||||||
args[1] = u32::from_be_bytes(buffer);
|
|
||||||
|
|
||||||
stream_reader.read_exact(&mut buffer)?;
|
|
||||||
let command_data_length = u32::from_be_bytes(buffer) as usize;
|
|
||||||
let mut data = vec![0u8; command_data_length];
|
|
||||||
stream_reader.read_exact(&mut data)?;
|
|
||||||
|
|
||||||
if event_sender
|
|
||||||
.send(Event::Command(Command { id, args, data }))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_serial_thread(
|
|
||||||
serial_reader: Arc<Serial>,
|
|
||||||
event_sender: Sender<Event>,
|
|
||||||
exit_flag: Arc<AtomicBool>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut packets: VecDeque<Packet> = VecDeque::new();
|
|
||||||
|
|
||||||
while !exit_flag.load(Ordering::Relaxed) {
|
|
||||||
let response = serial_reader.process_incoming_data(DataType::Packet, &mut packets)?;
|
|
||||||
|
|
||||||
if let Some(response) = response {
|
|
||||||
if event_sender.send(Event::Response(response)).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(packet) = packets.pop_front() {
|
|
||||||
if event_sender.send(Event::Packet(packet)).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_keepalive_thread(event_sender: Sender<Event>, exit_flag: Arc<AtomicBool>) {
|
|
||||||
let mut keepalive = Instant::now();
|
|
||||||
|
|
||||||
while !exit_flag.load(Ordering::Relaxed) {
|
|
||||||
if keepalive.elapsed() >= Duration::from_secs(5) {
|
|
||||||
keepalive = Instant::now();
|
|
||||||
if event_sender.send(Event::KeepAlive).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{link::Packet, Error};
|
use super::{link::AsynchronousPacket, Error};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -7,7 +7,7 @@ pub enum ConfigId {
|
|||||||
RomWriteEnable,
|
RomWriteEnable,
|
||||||
RomShadowEnable,
|
RomShadowEnable,
|
||||||
DdMode,
|
DdMode,
|
||||||
IsvAddress,
|
ISViewer,
|
||||||
BootMode,
|
BootMode,
|
||||||
SaveType,
|
SaveType,
|
||||||
CicSeed,
|
CicSeed,
|
||||||
@ -25,7 +25,7 @@ pub enum Config {
|
|||||||
RomWriteEnable(Switch),
|
RomWriteEnable(Switch),
|
||||||
RomShadowEnable(Switch),
|
RomShadowEnable(Switch),
|
||||||
DdMode(DdMode),
|
DdMode(DdMode),
|
||||||
IsvAddress(u32),
|
ISViewer(ISViewer),
|
||||||
BootMode(BootMode),
|
BootMode(BootMode),
|
||||||
SaveType(SaveType),
|
SaveType(SaveType),
|
||||||
CicSeed(CicSeed),
|
CicSeed(CicSeed),
|
||||||
@ -45,7 +45,7 @@ impl From<ConfigId> for u32 {
|
|||||||
ConfigId::RomWriteEnable => 1,
|
ConfigId::RomWriteEnable => 1,
|
||||||
ConfigId::RomShadowEnable => 2,
|
ConfigId::RomShadowEnable => 2,
|
||||||
ConfigId::DdMode => 3,
|
ConfigId::DdMode => 3,
|
||||||
ConfigId::IsvAddress => 4,
|
ConfigId::ISViewer => 4,
|
||||||
ConfigId::BootMode => 5,
|
ConfigId::BootMode => 5,
|
||||||
ConfigId::SaveType => 6,
|
ConfigId::SaveType => 6,
|
||||||
ConfigId::CicSeed => 7,
|
ConfigId::CicSeed => 7,
|
||||||
@ -69,7 +69,7 @@ impl TryFrom<(ConfigId, u32)> for Config {
|
|||||||
ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?),
|
ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?),
|
||||||
ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?),
|
ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?),
|
||||||
ConfigId::DdMode => Self::DdMode(config.try_into()?),
|
ConfigId::DdMode => Self::DdMode(config.try_into()?),
|
||||||
ConfigId::IsvAddress => Self::IsvAddress(config),
|
ConfigId::ISViewer => Self::ISViewer(config.try_into()?),
|
||||||
ConfigId::BootMode => Self::BootMode(config.try_into()?),
|
ConfigId::BootMode => Self::BootMode(config.try_into()?),
|
||||||
ConfigId::SaveType => Self::SaveType(config.try_into()?),
|
ConfigId::SaveType => Self::SaveType(config.try_into()?),
|
||||||
ConfigId::CicSeed => Self::CicSeed(config.try_into()?),
|
ConfigId::CicSeed => Self::CicSeed(config.try_into()?),
|
||||||
@ -91,7 +91,7 @@ impl From<Config> for [u32; 2] {
|
|||||||
Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()],
|
Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()],
|
||||||
Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.into(), val.into()],
|
Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.into(), val.into()],
|
||||||
Config::DdMode(val) => [ConfigId::DdMode.into(), val.into()],
|
Config::DdMode(val) => [ConfigId::DdMode.into(), val.into()],
|
||||||
Config::IsvAddress(val) => [ConfigId::IsvAddress.into(), val.into()],
|
Config::ISViewer(val) => [ConfigId::ISViewer.into(), val.into()],
|
||||||
Config::BootMode(val) => [ConfigId::BootMode.into(), val.into()],
|
Config::BootMode(val) => [ConfigId::BootMode.into(), val.into()],
|
||||||
Config::SaveType(val) => [ConfigId::SaveType.into(), val.into()],
|
Config::SaveType(val) => [ConfigId::SaveType.into(), val.into()],
|
||||||
Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()],
|
Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()],
|
||||||
@ -199,6 +199,41 @@ impl From<DdMode> for u32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ISViewer {
|
||||||
|
Disabled,
|
||||||
|
Enabled(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ISViewer {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Disabled => f.write_str("Not listening"),
|
||||||
|
Self::Enabled(offset) => {
|
||||||
|
f.write_fmt(format_args!("Listening at 0x{:08X}", 0x1000_0000 + offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for ISViewer {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
0 => Self::Disabled,
|
||||||
|
offset => Self::Enabled(offset),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ISViewer> for u32 {
|
||||||
|
fn from(value: ISViewer) -> Self {
|
||||||
|
match value {
|
||||||
|
ISViewer::Disabled => 0,
|
||||||
|
ISViewer::Enabled(offset) => offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum BootMode {
|
pub enum BootMode {
|
||||||
Menu,
|
Menu,
|
||||||
Rom,
|
Rom,
|
||||||
@ -588,9 +623,9 @@ pub enum DataPacket {
|
|||||||
UpdateStatus(UpdateStatus),
|
UpdateStatus(UpdateStatus),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Packet> for DataPacket {
|
impl TryFrom<AsynchronousPacket> for DataPacket {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
fn try_from(value: AsynchronousPacket) -> Result<Self, Self::Error> {
|
||||||
Ok(match value.id {
|
Ok(match value.id {
|
||||||
b'B' => Self::Button,
|
b'B' => Self::Button,
|
||||||
b'G' => Self::DataFlushed,
|
b'G' => Self::DataFlushed,
|
||||||
|
Loading…
Reference in New Issue
Block a user