diff options
author | Astatin <[email protected]> | 2025-06-29 02:27:13 +0200 |
---|---|---|
committer | Astatin <[email protected]> | 2025-06-29 02:27:13 +0200 |
commit | 4e46a48e042ca0d4d6814624e91108afeb5e7636 (patch) | |
tree | 46afde2ab9315026b4c1588c6919e297f60c6f21 | |
parent | 4cb95575feedda5e2006d706a9e85db6569043ca (diff) |
Add serial over tcp support
-rw-r--r-- | http.rom | bin | 0 -> 389 bytes | |||
-rw-r--r-- | src/desktop/serial.rs | 174 | ||||
-rw-r--r-- | src/main.rs | 20 | ||||
-rw-r--r-- | test | bin | 0 -> 339 bytes |
4 files changed, 181 insertions, 13 deletions
diff --git a/http.rom b/http.rom Binary files differnew file mode 100644 index 0000000..79b6dd2 --- /dev/null +++ b/http.rom diff --git a/src/desktop/serial.rs b/src/desktop/serial.rs index 29fad9d..3eac454 100644 --- a/src/desktop/serial.rs +++ b/src/desktop/serial.rs @@ -1,7 +1,7 @@ use std::fs::File; use std::io::{Read, Write}; use std::sync::mpsc::{self, Receiver, Sender}; -use std::sync::{Arc, Mutex}; +use std::net::{TcpListener, TcpStream}; use std::thread; use crate::io::Serial; @@ -25,11 +25,6 @@ impl Serial for UnconnectedSerial { } } -enum FIFOMessage { - Request(u8), - Response(u8), -} - pub struct FIFOSerial { transfer_requested: bool, current_transfer: bool, @@ -37,13 +32,14 @@ pub struct FIFOSerial { external_clock: bool, next_byte_transfer_cycle: u128, + no_response: bool, input: Receiver<u8>, output: Sender<u8>, } impl FIFOSerial { - pub fn new(input_path: String, output_path: String) -> FIFOSerial { + pub fn new(input_path: String, output_path: String, no_response: bool) -> FIFOSerial { let (tx, input) = mpsc::channel::<u8>(); thread::spawn(move || { let mut input_f = File::open(input_path).unwrap(); @@ -69,6 +65,8 @@ impl FIFOSerial { current_transfer: false, current_data: 0, + no_response, + external_clock: false, next_byte_transfer_cycle: 0, @@ -84,6 +82,152 @@ impl Serial for FIFOSerial { } fn read_control(&self) -> u8 { + (if self.external_clock { 0 } else { 0x01 }) | + (if self.transfer_requested { 0x80 } else { 0 }) + } + + fn write_data(&mut self, data: u8) { + self.current_data = data; + } + + fn write_control(&mut self, control: u8) { + self.external_clock = (control & 0b01) == 0; + self.transfer_requested = (control & 0x80) != 0; + } + + fn update_serial(&mut self, cycles: u128) -> bool { + if let Ok(x) = self.input.try_recv() { + if self.current_transfer { + self.current_data = x; + self.external_clock = false; + self.transfer_requested = false; + self.next_byte_transfer_cycle = cycles + ((CPU_CLOCK_SPEED as u128) / 1024); + self.current_transfer = false; + } else { + if !self.no_response { + self.output.send(self.current_data).unwrap(); + } + self.current_data = x; + self.external_clock = true; + self.transfer_requested = false; + } + true + } else if !self.external_clock && !self.current_transfer + && self.transfer_requested { + if cycles > self.next_byte_transfer_cycle { + self.output.send(self.current_data).unwrap(); + self.current_transfer = true; + if self.no_response { + self.current_data = 0; + self.transfer_requested = false; + self.next_byte_transfer_cycle = cycles + ((CPU_CLOCK_SPEED as u128) / 1024); + self.current_transfer = false; + } + } + false + } else { + false + } + } +} + +pub struct TcpSerial { + transfer_requested: bool, + current_transfer: bool, + current_data: u8, + + external_clock: bool, + next_byte_transfer_cycle: u128, + next_byte_receive_cycle: u128, + + no_response: bool, + + input: Receiver<u8>, + output: Sender<u8>, +} + +impl TcpSerial { + pub fn handle_stream(mut stream: TcpStream, tx: Sender<u8>, rx: Receiver<u8>) { + let mut stream_clone = stream.try_clone().unwrap(); + thread::spawn(move || { + loop { + let mut byte = [0]; + + stream_clone.read(&mut byte).unwrap(); + + tx.send(byte[0]).unwrap(); + } + }); + + thread::spawn(move || { + for b in rx.iter() { + stream.write(&[b]).unwrap(); + } + }); + } + + pub fn new_listener(port: u16, no_response: bool) -> Self { + let (tx, input) = mpsc::channel::<u8>(); + let (output, rx) = mpsc::channel::<u8>(); + thread::spawn(move || { + match TcpListener::bind(("0.0.0.0", port)).unwrap().accept() { + Ok((socket, addr)) => { + println!("Connection on {:?}", addr); + Self::handle_stream(socket, tx, rx); + } + _ => () + }; + }); + + Self { + transfer_requested: false, + current_transfer: false, + current_data: 0, + + no_response, + + external_clock: false, + next_byte_transfer_cycle: 0, + next_byte_receive_cycle: 0, + + input, + output, + } + } + + pub fn connect(addr: String, no_response: bool) -> Self { + let (tx, input) = mpsc::channel::<u8>(); + let (output, rx) = mpsc::channel::<u8>(); + thread::spawn(move || { + if let Ok(socket) = TcpStream::connect(&addr) { + println!("Connected to {:?}", addr); + Self::handle_stream(socket, tx, rx); + } + }); + + Self { + transfer_requested: false, + current_transfer: false, + current_data: 0, + + no_response, + + external_clock: false, + next_byte_transfer_cycle: 0, + next_byte_receive_cycle: 0, + + input, + output, + } + } +} + +impl Serial for TcpSerial { + fn read_data(&self) -> u8 { + self.current_data + } + + fn read_control(&self) -> u8 { (if self.external_clock { 0 } else { 0x01 }) | (if self.transfer_requested { 0x80 } else { 0 }) } @@ -98,6 +242,9 @@ impl Serial for FIFOSerial { } fn update_serial(&mut self, cycles: u128) -> bool { + if cycles < self.next_byte_transfer_cycle { + return false; + } if let Ok(x) = self.input.try_recv() { if self.current_transfer { self.current_data = x; @@ -106,19 +253,26 @@ impl Serial for FIFOSerial { self.next_byte_transfer_cycle = cycles + ((CPU_CLOCK_SPEED as u128) / 1024); self.current_transfer = false; } else { - self.output.send(self.current_data).unwrap(); - println!("recv {:02x}, send back {:02x}", x, self.current_data); + if !self.no_response || self.transfer_requested { + self.output.send(self.current_data).unwrap(); + } self.current_data = x; self.external_clock = true; self.transfer_requested = false; } + self.next_byte_transfer_cycle = cycles + ((CPU_CLOCK_SPEED as u128) / 16384); true } else if !self.external_clock && !self.current_transfer && self.transfer_requested { if cycles > self.next_byte_transfer_cycle { self.output.send(self.current_data).unwrap(); - println!("send {:02x}", self.current_data); self.current_transfer = true; + if self.no_response { + self.current_data = 0; + self.transfer_requested = false; + self.next_byte_transfer_cycle = cycles + ((CPU_CLOCK_SPEED as u128) / 1024); + self.current_transfer = false; + } } false } else { diff --git a/src/main.rs b/src/main.rs index d4cdb34..4399548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,19 @@ struct Cli { /// Window title #[arg(long, default_value = "Gameboy Emulator")] - title: String + title: String, + + /// Serial tcp listen port + #[arg(short='L', long)] + listen: Option<u16>, + + /// Serial tcp connect port + #[arg(short, long)] + connect: Option<String>, + + /// Don't send (or expect) a byte as a response to a serial transfer + #[arg(long, default_value_t = false)] + no_response: bool, } fn main() { @@ -75,8 +87,10 @@ fn main() { let window = desktop::window::DesktopWindow::new(cli.title).unwrap(); - let serial: Box<dyn Serial> = match (cli.fifo_input, cli.fifo_output) { - (Some(fifo_input), Some(fifo_output)) => Box::new(desktop::serial::FIFOSerial::new(fifo_input, fifo_output)), + let serial: Box<dyn Serial> = match (cli.fifo_input, cli.fifo_output, cli.listen, cli.connect) { + (_, _, Some(port), _) => Box::new(desktop::serial::TcpSerial::new_listener(port, cli.no_response)), + (_, _, _, Some(addr)) => Box::new(desktop::serial::TcpSerial::connect(addr, cli.no_response)), + (Some(fifo_input), Some(fifo_output), _, _) => Box::new(desktop::serial::FIFOSerial::new(fifo_input, fifo_output, cli.no_response)), _ => Box::new(desktop::serial::UnconnectedSerial {}) }; |