diff --git a/fw/rtl/memory/memory_dma.sv b/fw/rtl/memory/memory_dma.sv index b401ddf..bfd15d6 100644 --- a/fw/rtl/memory/memory_dma.sv +++ b/fw/rtl/memory/memory_dma.sv @@ -45,12 +45,26 @@ module memory_dma ( logic dma_start; logic dma_stop; + logic dma_stop_requested; always_comb begin dma_start = dma_scb.start && !dma_scb.stop && !dma_scb.busy; dma_stop = dma_scb.stop; end + always_ff @(posedge clk) begin + if (reset) begin + dma_stop_requested <= 1'b0; + end else begin + if (dma_stop) begin + dma_stop_requested <= 1'b1; + end + if (dma_start) begin + dma_stop_requested <= 1'b0; + end + end + end + // Remaining counter and FIFO enable @@ -197,7 +211,7 @@ module memory_dma ( remaining <= dma_scb.transfer_length; end - if ((mem_bus.write && rx_rdata_pop) || (!mem_bus.write && tx_wdata_push)) begin + if (!dma_stop_requested && ((mem_bus.write && rx_rdata_pop) || (!mem_bus.write && tx_wdata_push))) begin remaining <= remaining - 1'd1; end end diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index 9ec0afe..6712a7e 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -218,9 +218,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410" dependencies = [ "jobserver", "libc", @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -284,9 +284,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", @@ -799,9 +799,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loop9" @@ -906,9 +906,9 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -1196,9 +1196,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" dependencies = [ "bytemuck", ] @@ -1291,9 +1291,9 @@ dependencies = [ [[package]] name = "serialport" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828" +checksum = "de7c4f0cce25b9b3518eea99618112f9ee4549f974480c8f43d3c06f03c131a0" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1452,9 +1452,9 @@ dependencies = [ [[package]] name = "unescaper" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adf6ad32eb5b3cadff915f7b770faaac8f7ff0476633aa29eb0d9584d889d34" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" dependencies = [ "thiserror", ] diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 98cbfd3..b6f5ff0 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -8,7 +8,7 @@ documentation = "https://github.com/Polprzewodnikowy/SummerCart64" [dependencies] chrono = "0.4.38" -clap = { version = "4.5.7", features = ["derive"] } +clap = { version = "4.5.8", features = ["derive"] } clap-num = "1.1.1" colored = "2.1.0" crc32fast = "1.4.2" @@ -24,7 +24,7 @@ panic-message = "0.3.0" rand = "0.8.5" rust-ini = "0.18.0" serial2 = "0.2.26" -serialport = "4.3.0" +serialport = "4.4.0" [profile.release] lto = true diff --git a/sw/deployer/src/sc64/ftdi.rs b/sw/deployer/src/sc64/ftdi.rs index d5518e8..027a8f4 100644 --- a/sw/deployer/src/sc64/ftdi.rs +++ b/sw/deployer/src/sc64/ftdi.rs @@ -36,24 +36,24 @@ struct Wrapper { 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 DEFAULT_CHUNKSIZE: usize = 4096; + const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100); fn new( read_timeout: Option, write_timeout: Option, - ) -> std::io::Result { + ) -> std::io::Result { let context = unsafe { libftdi1_sys::ftdi_new() }; if context.is_null() { return Err(std::io::ErrorKind::OutOfMemory.into()); } - let mut wrapper = Wrapper { + let mut wrapper = Self { context, unclog_buffer: std::collections::VecDeque::new(), write_buffer: vec![], - read_timeout: Wrapper::DEFAULT_RW_TIMEOUT, - write_timeout: Wrapper::DEFAULT_RW_TIMEOUT, - read_chunksize: Wrapper::DEFAULT_CHUNKSIZE, - write_chunksize: Wrapper::DEFAULT_CHUNKSIZE, + 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)?; @@ -62,7 +62,7 @@ impl Wrapper { } fn list_devices(vendor: u16, product: u16) -> std::io::Result> { - let wrapper = Wrapper::new(None, None)?; + let wrapper = Self::new(None, None)?; let mut device_list: *mut libftdi1_sys::ftdi_device_list = std::ptr::null_mut(); let devices = unsafe { @@ -152,8 +152,8 @@ impl Wrapper { read_timeout: Option, write_timeout: Option, ) -> std::io::Result<()> { - let read_timeout = read_timeout.unwrap_or(Wrapper::DEFAULT_RW_TIMEOUT); - let write_timeout = write_timeout.unwrap_or(Wrapper::DEFAULT_RW_TIMEOUT); + 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)?; @@ -236,7 +236,7 @@ impl Wrapper { } fn set_latency_timer(&mut self, latency: Option) -> std::io::Result<()> { - let latency = u8::try_from(latency.unwrap_or(Wrapper::DEFAULT_POLL_TIMEOUT).as_millis()) + 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(()), @@ -320,7 +320,9 @@ impl Wrapper { match self.read(&mut vec![0u8; self.read_chunksize]) { Ok(_) => {} Err(error) => match error.kind() { - std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock => { + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock => { return Ok(()); } _ => return Err(error), @@ -369,7 +371,7 @@ impl Wrapper { Vec::from(buffer).as_mut_ptr(), buffer.len() as i32, &mut transferred, - 100, + Self::WRITE_CHUNK_TIMEOUT.as_millis() as u32, ) }; *written = transferred as usize; @@ -469,7 +471,7 @@ impl FtdiDevice { wrapper.set_module_detach_mode(ModuleDetachMode::AutoDetachReattach); wrapper.set_interface(InterfaceIndex::A)?; - const CHUNK_SIZE: usize = 64 * 1024; + const CHUNK_SIZE: usize = 16 * 1024; wrapper.read_data_set_chunksize(CHUNK_SIZE)?; wrapper.write_data_set_chunksize(CHUNK_SIZE)?; diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index 44b1f53..0113567 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -1,4 +1,4 @@ -use super::{error::Error, ftdi::FtdiDevice}; +use super::{error::Error, ftdi::FtdiDevice, serial::SerialDevice}; use std::{ collections::VecDeque, fmt::Display, @@ -75,34 +75,58 @@ pub trait Backend { fn flush(&mut self) -> std::io::Result<()>; - fn reset(&mut self) -> std::io::Result<()> { + fn discard_input(&mut self) -> std::io::Result<()> { Ok(()) } + fn discard_output(&mut self) -> std::io::Result<()> { + Ok(()) + } + + fn set_dtr(&mut self, _value: bool) -> std::io::Result<()> { + Ok(()) + } + + fn read_dsr(&mut self) -> std::io::Result { + Ok(false) + } + fn close(&mut self) {} - fn purge_incoming_data(&mut self) -> std::io::Result<()> { + fn reset(&mut self) -> std::io::Result<()> { + self.discard_output()?; + let timeout = Instant::now(); + self.set_dtr(true)?; loop { - match self.read(&mut vec![0; 1]) { - Ok(length) => match length { - 0 => return Ok(()), - _ => {} - }, - Err(error) => match error.kind() { - std::io::ErrorKind::Interrupted - | std::io::ErrorKind::TimedOut - | std::io::ErrorKind::WouldBlock => return Ok(()), - _ => return Err(error), - }, + if self.read_dsr()? { + break; } - if timeout.elapsed() >= RESET_TIMEOUT { + if timeout.elapsed() > RESET_TIMEOUT { return Err(std::io::Error::new( std::io::ErrorKind::TimedOut, - "SC64 read buffer flush took too long", + "Couldn't reset SC64 device (on)", )); } } + + self.discard_input()?; + + let timeout = Instant::now(); + self.set_dtr(false)?; + loop { + if !self.read_dsr()? { + break; + } + if timeout.elapsed() > RESET_TIMEOUT { + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (off)", + )); + } + } + + Ok(()) } fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result> { @@ -196,7 +220,7 @@ pub trait Backend { } pub struct SerialBackend { - device: serial2::SerialPort, + device: SerialDevice, } impl Backend for SerialBackend { @@ -212,47 +236,32 @@ impl Backend for SerialBackend { self.device.flush() } - fn reset(&mut self) -> std::io::Result<()> { - self.device.set_dtr(true)?; - let timeout = Instant::now(); - loop { - self.device.discard_buffers()?; - if self.device.read_dsr()? { - break; - } - if timeout.elapsed() > RESET_TIMEOUT { - return Err(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Couldn't reset SC64 device (on)", - )); - } - } + fn discard_input(&mut self) -> std::io::Result<()> { + self.device.discard_input() + } - self.purge_incoming_data()?; + fn discard_output(&mut self) -> std::io::Result<()> { + self.device.discard_output() + } - self.device.set_dtr(false)?; - let timeout = Instant::now(); - loop { - if !self.device.read_dsr()? { - break; - } - if timeout.elapsed() > RESET_TIMEOUT { - return Err(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Couldn't reset SC64 device (off)", - )); - } - } + fn set_dtr(&mut self, value: bool) -> std::io::Result<()> { + self.device.set_dtr(value) + } - Ok(()) + fn read_dsr(&mut self) -> std::io::Result { + self.device.read_dsr() } } fn new_serial_backend(port: &str) -> std::io::Result { - let mut serial = serial2::SerialPort::open(port, 115_200)?; - serial.set_read_timeout(POLL_TIMEOUT)?; - serial.set_write_timeout(WRITE_TIMEOUT)?; - Ok(SerialBackend { device: serial }) + Ok(SerialBackend { + device: SerialDevice::new( + port, + Some(POLL_TIMEOUT), + Some(READ_TIMEOUT), + Some(WRITE_TIMEOUT), + )?, + }) } struct FtdiBackend { @@ -272,40 +281,20 @@ impl Backend for FtdiBackend { self.device.flush() } - fn reset(&mut self) -> std::io::Result<()> { - self.device.discard_output()?; + fn discard_input(&mut self) -> std::io::Result<()> { + self.device.discard_input() + } - let timeout = Instant::now(); - self.device.set_dtr(true)?; - loop { - if self.device.read_dsr()? { - break; - } - if timeout.elapsed() > RESET_TIMEOUT { - return Err(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Couldn't reset SC64 device (on)", - )); - } - } + fn discard_output(&mut self) -> std::io::Result<()> { + self.device.discard_output() + } - self.device.discard_input()?; + fn set_dtr(&mut self, value: bool) -> std::io::Result<()> { + self.device.set_dtr(value) + } - let timeout = Instant::now(); - self.device.set_dtr(false)?; - loop { - if !self.device.read_dsr()? { - break; - } - if timeout.elapsed() > RESET_TIMEOUT { - return Err(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Couldn't reset SC64 device (off)", - )); - } - } - - Ok(()) + fn read_dsr(&mut self) -> std::io::Result { + self.device.read_dsr() } } @@ -580,18 +569,14 @@ pub fn list_local_devices() -> Result, Error> { } } - if let Ok(list) = serialport::available_ports() { + if let Ok(list) = SerialDevice::list(SC64_VID, SC64_PID) { for device in list.into_iter() { - if let serialport::SerialPortType::UsbPort(i) = device.port_type { - if let Some(serial) = i.serial_number { - if i.vid == SC64_VID && i.pid == SC64_PID && serial.starts_with(SC64_SID) { - devices.push(DeviceInfo { - backend: BackendType::Serial, - port: format!("{SERIAL_PREFIX}{}", device.port_name), - serial, - }); - } - } + if device.serial.starts_with(SC64_SID) { + devices.push(DeviceInfo { + backend: BackendType::Serial, + port: format!("{SERIAL_PREFIX}{}", device.port), + serial: device.serial, + }); } } } diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index 7f0ce8f..8ce6139 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -3,6 +3,7 @@ mod error; pub mod firmware; mod ftdi; mod link; +mod serial; pub mod server; mod time; mod types; diff --git a/sw/deployer/src/sc64/serial.rs b/sw/deployer/src/sc64/serial.rs new file mode 100644 index 0000000..248826e --- /dev/null +++ b/sw/deployer/src/sc64/serial.rs @@ -0,0 +1,155 @@ +pub struct DeviceInfo { + pub port: String, + pub serial: String, +} + +pub struct SerialDevice { + serial: serial2::SerialPort, + unclog_buffer: std::collections::VecDeque, + poll_timeout: std::time::Duration, + read_timeout: std::time::Duration, + write_timeout: std::time::Duration, +} + +impl SerialDevice { + const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16); + const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); + const WRITE_CHUNK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100); + const BUFFER_SIZE: usize = 16 * 1024; + + pub fn new( + port: &str, + poll_timeout: Option, + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result { + let mut device = Self { + serial: serial2::SerialPort::open(port, 115_200)?, + unclog_buffer: std::collections::VecDeque::new(), + poll_timeout: poll_timeout.unwrap_or(Self::DEFAULT_POLL_TIMEOUT), + read_timeout: read_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT), + write_timeout: write_timeout.unwrap_or(Self::DEFAULT_RW_TIMEOUT), + }; + device.serial.set_read_timeout(device.poll_timeout)?; + device.serial.set_write_timeout(Self::WRITE_CHUNK_TIMEOUT)?; + Ok(device) + } + + pub fn list(vendor: u16, product: u16) -> std::io::Result> { + let mut devices = vec![]; + + for port in serialport::available_ports()? { + if let serialport::SerialPortType::UsbPort(info) = port.port_type { + if info.vid == vendor && info.pid == product && info.serial_number.is_some() { + devices.push(DeviceInfo { + port: port.port_name, + serial: info.serial_number.unwrap(), + }) + } + } + } + + Ok(devices) + } + + pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> { + self.serial.set_dtr(value) + } + + pub fn read_dsr(&mut self) -> std::io::Result { + self.serial.read_dsr() + } + + pub fn discard_input(&mut self) -> std::io::Result<()> { + let timeout = std::time::Instant::now(); + self.serial.discard_input_buffer()?; + loop { + match self.serial.read(&mut vec![0u8; Self::BUFFER_SIZE]) { + Ok(_) => {} + Err(error) => match error.kind() { + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock => { + return Ok(()); + } + _ => return Err(error), + }, + }; + if timeout.elapsed() > self.read_timeout { + return Err(std::io::ErrorKind::TimedOut.into()); + } + } + } + + pub fn discard_output(&mut self) -> std::io::Result<()> { + self.serial.discard_output_buffer() + } + + fn unclog_pipe(&mut self) -> std::io::Result<()> { + let mut buffer = vec![0u8; Self::BUFFER_SIZE]; + let read = match self.serial.read(&mut buffer) { + Ok(read) => read, + Err(error) => match error.kind() { + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock => 0, + _ => return Err(error), + }, + }; + self.unclog_buffer.extend(buffer[0..read].iter()); + Ok(()) + } +} + +impl std::io::Read for SerialDevice { + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + if buffer.is_empty() { + Err(std::io::ErrorKind::InvalidInput.into()) + } else if self.unclog_buffer.is_empty() { + self.serial.read(buffer) + } else { + for (index, item) in buffer.iter_mut().enumerate() { + if let Some(byte) = self.unclog_buffer.pop_front() { + *item = byte; + } else { + return Ok(index); + } + } + Ok(buffer.len()) + } + } +} + +impl std::io::Write for SerialDevice { + fn write(&mut self, buffer: &[u8]) -> std::io::Result { + let timeout = std::time::Instant::now(); + loop { + match self.serial.write(buffer) { + Ok(bytes) => return Ok(bytes), + Err(error) => match error.kind() { + std::io::ErrorKind::TimedOut => self.unclog_pipe()?, + _ => return Err(error), + }, + }; + if timeout.elapsed() > self.write_timeout { + return Err(std::io::ErrorKind::TimedOut.into()); + } + } + } + + fn flush(&mut self) -> std::io::Result<()> { + let timeout = std::time::Instant::now(); + loop { + match self.serial.flush() { + Ok(()) => return Ok(()), + Err(error) => match error.kind() { + std::io::ErrorKind::TimedOut => self.unclog_pipe()?, + _ => return Err(error), + }, + }; + if timeout.elapsed() > self.write_timeout { + return Err(std::io::ErrorKind::TimedOut.into()); + } + } + } +}