From 561fb8dbb412a72aeec511abf44777e93ed4f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Sun, 16 Apr 2023 02:09:27 +0100 Subject: [PATCH] chore: initial printer device logic done The image data buffer printing is still pending. Still a lot of TODOs and edge cases to be solved. --- frontends/sdl/src/main.rs | 2 +- src/devices/printer.rs | 250 ++++++++++++++++++++++++++++++++++++-- src/devices/stdout.rs | 12 +- src/serial.rs | 30 ++--- 4 files changed, 262 insertions(+), 32 deletions(-) diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index d0aba46b..893960b3 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -444,7 +444,7 @@ fn main() { // creates a new Game Boy instance and loads both the boot ROM // and the initial game ROM to "start the engine" let mut game_boy = GameBoy::new(); - game_boy.attach_stdout_serial(); + game_boy.attach_printer_serial(); game_boy.load_boot_default(); // creates a new generic emulator structure then starts diff --git a/src/devices/printer.rs b/src/devices/printer.rs index e5e81931..25c16aac 100644 --- a/src/devices/printer.rs +++ b/src/devices/printer.rs @@ -1,26 +1,256 @@ -use crate::serial::SerialDevice; +use std::fmt::{self, Display, Formatter}; -pub struct PrinterDevice {} +use crate::{serial::SerialDevice, warnln}; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum PrinterState { + MagicBytes1 = 0x00, + MagicBytes2 = 0x01, + Identification = 0x02, + Compression = 0x03, + LengthLow = 0x04, + LengthHigh = 0x05, + Data = 0x06, + ChecksumLow = 0x07, + ChecksumHigh = 0x08, + KeepAlive = 0x09, + Status = 0x0a, + Other = 0xff, +} + +impl PrinterState { + pub fn description(&self) -> &'static str { + match self { + PrinterState::MagicBytes1 => "Magic Bytes 1", + PrinterState::MagicBytes2 => "Magic Bytes 2", + PrinterState::Identification => "Identification", + PrinterState::Compression => "Compression", + PrinterState::LengthLow => "Length Low", + PrinterState::LengthHigh => "Length High", + PrinterState::Data => "Data", + PrinterState::ChecksumLow => "Checksum Low", + PrinterState::ChecksumHigh => "Checksum High", + PrinterState::KeepAlive => "Keep Alive", + PrinterState::Status => "Status", + PrinterState::Other => "Other", + } + } + + fn from_u8(value: u8) -> Self { + match value { + 0x00 => PrinterState::MagicBytes1, + 0x01 => PrinterState::MagicBytes2, + 0x02 => PrinterState::Identification, + 0x03 => PrinterState::Compression, + 0x04 => PrinterState::LengthLow, + 0x05 => PrinterState::LengthHigh, + 0x06 => PrinterState::Data, + 0x07 => PrinterState::ChecksumLow, + 0x08 => PrinterState::ChecksumHigh, + 0x09 => PrinterState::KeepAlive, + 0x0a => PrinterState::Status, + _ => PrinterState::Other, + } + } +} + +impl Display for PrinterState { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum PrinterCommand { + Init = 0x01, + Start = 0x02, + Data = 0x04, + Status = 0x0f, + Other = 0xff, +} + +impl PrinterCommand { + pub fn description(&self) -> &'static str { + match self { + PrinterCommand::Init => "Init", + PrinterCommand::Start => "Start", + PrinterCommand::Data => "Data", + PrinterCommand::Status => "Status", + PrinterCommand::Other => "Other", + } + } + + fn from_u8(value: u8) -> Self { + match value { + 0x01 => PrinterCommand::Init, + 0x02 => PrinterCommand::Start, + 0x04 => PrinterCommand::Data, + 0x0f => PrinterCommand::Status, + _ => PrinterCommand::Other, + } + } +} + +impl Display for PrinterCommand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +pub struct PrinterDevice { + state: PrinterState, + command: PrinterCommand, + compression: bool, + command_length: u16, + length_left: u16, + checksum: u16, + status: u8, + byte_out: u8, + data: [u8; 0x280], +} impl PrinterDevice { pub fn new() -> Self { - Self {} + Self { + state: PrinterState::MagicBytes1, + command: PrinterCommand::Other, + compression: false, + command_length: 0, + length_left: 0, + checksum: 0x0, + status: 0x0, + byte_out: 0x0, + data: [0x00; 0x280], + } } -} -impl Default for PrinterDevice { - fn default() -> Self { - Self::new() + pub fn reset(&mut self) { + self.state = PrinterState::MagicBytes1; + self.command = PrinterCommand::Other; + self.compression = false; + self.command_length = 0; + self.length_left = 0; + self.checksum = 0x0; + self.status = 0x0; + self.byte_out = 0x0; + self.data = [0x00; 0x280] + } + + fn run_command(&mut self, command: PrinterCommand) { + match command { + PrinterCommand::Init => { + self.status = 0x00; + self.byte_out = self.status; + } + PrinterCommand::Start => { + self.byte_out = self.status; + self.status = 0x06; + } + PrinterCommand::Data => { + if self.command_length == 0x280 { + println!("Printer: Going to copy the image for printing"); + } + // in case the command is of size 0 we assume this is + // an EOF and we ignore this data operation + else if self.command_length == 0x0 { + } else { + warnln!( + "Printer: Wrong size for data: {:04x} bytes", + self.command_length + ); + } + self.status = 0x08; + self.byte_out = self.status; + } + PrinterCommand::Status => { + self.byte_out = self.status; + + // in case the current status is printing let's + // mark it as done + if self.status == 0x06 { + // @TODO: check if this value should be 0x04 instead + // this seems to be a bug with the print demo + self.status = 0x00; + } + } + PrinterCommand::Other => { + warnln!("Printer: Invalid command: {:02x}", self.state as u8); + return; + } + } } } impl SerialDevice for PrinterDevice { fn send(&mut self) -> u8 { - 0xff + self.byte_out } fn receive(&mut self, byte: u8) { - print!("{}", byte as char); - // @TODO: implement this one + self.byte_out = 0x00; + + match self.state { + PrinterState::MagicBytes1 => { + if byte != 0x88 { + warnln!("Printer: Invalid magic byte 1: {:02x}", byte); + return; + } + self.command = PrinterCommand::Other; + self.command_length = 0; + } + PrinterState::MagicBytes2 => { + if byte != 0x33 { + if byte != 0x88 { + self.state = PrinterState::MagicBytes1; + } + warnln!("Printer: Invalid magic byte 2: {:02x}", byte); + return; + } + } + PrinterState::Identification => self.command = PrinterCommand::from_u8(byte), + PrinterState::Compression => { + self.compression = byte & 0x01 == 0x01; + if self.compression { + warnln!("Printer: Using compressed data, currently unsupported"); + } + } + PrinterState::LengthLow => self.length_left = byte as u16, + PrinterState::LengthHigh => self.length_left |= (byte as u16) << 8, + PrinterState::Data => { + self.data[self.command_length as usize] = byte; + self.command_length += 1; + self.length_left -= 1; + } + PrinterState::ChecksumLow => self.checksum = byte as u16, + PrinterState::ChecksumHigh => { + self.checksum |= (byte as u16) << 8; + self.byte_out = 0x81; + } + PrinterState::KeepAlive => { + self.run_command(self.command); + } + PrinterState::Status => { + self.state = PrinterState::MagicBytes1; + return; + } + PrinterState::Other => { + warnln!("Printer: Invalid state: {:02x}", self.state as u8); + return; + } + } + + if self.state != PrinterState::Data { + self.state = PrinterState::from_u8(self.state as u8 + 1); + } + + if self.state == PrinterState::Data && self.length_left == 0 { + self.state = PrinterState::from_u8(self.state as u8 + 1); + } + } +} + +impl Default for PrinterDevice { + fn default() -> Self { + Self::new() } } diff --git a/src/devices/stdout.rs b/src/devices/stdout.rs index efdd32cf..f9a7ad4f 100644 --- a/src/devices/stdout.rs +++ b/src/devices/stdout.rs @@ -12,12 +12,6 @@ impl StdoutDevice { } } -impl Default for StdoutDevice { - fn default() -> Self { - Self::new(true) - } -} - impl SerialDevice for StdoutDevice { fn send(&mut self) -> u8 { 0xff @@ -30,3 +24,9 @@ impl SerialDevice for StdoutDevice { } } } + +impl Default for StdoutDevice { + fn default() -> Self { + Self::new(true) + } +} diff --git a/src/serial.rs b/src/serial.rs index d528bac8..5726ac29 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -51,17 +51,13 @@ impl Serial { } pub fn clock(&mut self, cycles: u8) { - if self.shift_clock { - return; - } - if !self.transferring { return; } self.timer = self.timer.saturating_sub(cycles as i16); if self.timer <= 0 { - let bit = self.byte_receive & (0x01 << self.bit_count); + let bit = (self.byte_receive >> (7 - self.bit_count)) & 0x01; self.data = (self.data << 1) | bit; self.tick_transfer(); @@ -93,11 +89,15 @@ impl Serial { self.clock_speed = value & 0x02 == 0x02; self.transferring = value & 0x80 == 0x80; + // @TODO: THIS SEEMS LIKE A HACK, we'll need to check with + // the device driver how to handle no communication and receive + // we must simulate no cable communication + if self.transferring && !self.shift_clock { + self.transferring = false; + } // in case a transfer of byte has been requested and - // this is the device responsible for the shifting - // of the transfer's clock then we need to start - // the transfer setup - if self.transferring && self.shift_clock { + // this is the then we need to start the transfer setup + else if self.transferring { // @TODO: if the GBC mode exists there should // be special check logic here //self.length = if self.gb.is_cgb() && self.clock_speed { 16 } else { 512 }; @@ -183,12 +183,6 @@ impl NullDevice { } } -impl Default for NullDevice { - fn default() -> Self { - Self::new() - } -} - impl SerialDevice for NullDevice { fn send(&mut self) -> u8 { 0xff @@ -196,3 +190,9 @@ impl SerialDevice for NullDevice { fn receive(&mut self, _: u8) {} } + +impl Default for NullDevice { + fn default() -> Self { + Self::new() + } +} -- GitLab