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