1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! RS-232 serial port driver

use core::fmt::{Display, Write, Error, Formatter};
use crate::sync::{Once, SpinLockIRQ};
use crate::io::Io;
use crate::i386::pio::Pio;

/// The base IO port of a COM
#[derive(Debug, Copy, Clone)]
pub struct ComPort(u16);

/// COM1: I/O port 0x3F8, IRQ 4
#[cfg(any(all(target_arch="x86", not(test)), doc))]
const COM1: ComPort = ComPort(0x3F8);
/// COM2: I/O port 0x2F8, IRQ 3
#[cfg(any(all(target_arch="x86", not(test)), doc))]
const COM2: ComPort = ComPort(0x2F8);
/// COM3: I/O port 0x3E8, IRQ 4
#[cfg(any(all(target_arch="x86", not(test)), doc))]
const COM3: ComPort = ComPort(0x3E8);
/// COM4: I/O port 0x2E8, IRQ 3
#[cfg(any(all(target_arch="x86", not(test)), doc))]
const COM4: ComPort = ComPort(0x2E8);

// TODO: device drivers should be compiled only for i386
#[cfg(test)]
const COM1: ComPort = ComPort(0x7777);

/// The possible colors for serial
#[allow(missing_docs, clippy::missing_docs_in_private_items)]
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum SerialColor {
    Black        = 0,
    Red          = 1,
    Green        = 2,
    Yellow       = 3,
    Blue         = 4,
    Magenta      = 5,
    Cyan         = 6,
    LightGray    = 7,
    Default      = 9,
    DarkGray     = 60,
    LightRed     = 61,
    LightGreen   = 62,
    LightYellow  = 63,
    LightBlue    = 64,
    LightMagenta = 65,
    LightCyan    = 66,
    White        = 67,
}

#[derive(Debug, Copy, Clone)]
/// A foreground and a background combination
pub struct SerialAttributes {
    /// foreground color
    fg: SerialColor,
    /// background color
    bg: SerialColor,
}

impl SerialAttributes {
    /// Creates a color attribute with `fg` foreground and default background.
    pub fn fg(fg: SerialColor) -> SerialAttributes {
        SerialAttributes { fg, bg: SerialColor::Default }
    }

    /// Creates a color attribute with `fg` foreground and `bg` background.
    pub fn fg_bg(fg: SerialColor, bg: SerialColor) -> SerialAttributes {
        SerialAttributes { fg, bg }
    }

    /// Creates a color attribute with default foreground and default background.
    pub fn default() -> SerialAttributes {
        SerialAttributes { fg: SerialColor::Default, bg: SerialColor::Default }
    }
}

/// To log something with color attributes do something like this:
///
/// ```
/// use ::device::rs232::{SerialLogger, SerialAttributes, SerialColor};
/// use ::core::fmt::Write;
///
/// write!(SerialLogger, "Hello {}World{}!",
///     SerialAttributes::fg(SerialColor::Green), // a green foreground with default background.
///     SerialAttributes::default() // don't forget to set back to default attributes at the end.
/// );
/// ```
impl Display for SerialAttributes {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "\x1B[{};{}m", self.fg as u8 + 30, self.bg as u8 + 40)
    }
}

/// The serial logger.
///
/// Initialized on first use.
///
/// Log functions will access the [SerialInternal] it wraps, and send text to it.
static G_SERIAL: Once<SpinLockIRQ<SerialInternal<Pio<u8>>>> = Once::new();

/// A COM output. Wraps the IO ports of this COM, and provides function for writing to it.
struct SerialInternal<T> {
    /// The DATA IO port of this COM
    data_port: T,
    /// The STATUS IO port of this COM
    status_port: T
}

impl SerialInternal<Pio<u8>> {
    /// Creates a COM port from it's base IO address.
    #[cfg(any(all(target_arch="x86", not(test)), doc))]
    #[allow(unused)]
    pub fn new(com_port: ComPort) -> SerialInternal<Pio<u8>> {
        let mut data_port       = Pio::<u8>::new(com_port.0 + 0);
        let mut interrupt_port  = Pio::<u8>::new(com_port.0 + 1);
        let mut baud_diviser_lo = Pio::<u8>::new(com_port.0 + 0); // when DLB is set, data and intr
        let mut baud_diviser_hi = Pio::<u8>::new(com_port.0 + 1); // become baud divisor lo and hi
        let mut fifo_port       = Pio::<u8>::new(com_port.0 + 2);
        let mut lcr_port        = Pio::<u8>::new(com_port.0 + 3);
        let mut mcr_port        = Pio::<u8>::new(com_port.0 + 4);
        let mut status_port     = Pio::<u8>::new(com_port.0 + 5);

        interrupt_port .write(0x00); // Disable interrupts
        lcr_port       .write(0x80); // Enable DLAB (set baud rate divisor)
        baud_diviser_lo.write(0x03); // set divisor to 3 (lo byte) 38400 baud rate
        baud_diviser_hi.write(0x00); //                  (hi byte)
        lcr_port       .write(0x03); // 8 bits, no parity, one stop bit. Disables DLAB
        fifo_port      .write(0xC7); // Enable FIFO, clear them, with 14-byte threshold
                                           // Note : no idea what this is
        //mcr_port     .write(0x0B);       // IRQs enabled, RTS/DSR set

        SerialInternal { data_port, status_port }
    }

    #[cfg(test)]
    pub fn new(_com_port: ComPort) -> SerialInternal<Pio<u8>> { panic!("mock implementation !") }

    /// Outputs a string to this COM.
    fn send_string(&mut self, string: &str) {
        for byte in string.bytes() {
            // Wait for the transmit buffer to be empty.
            while self.status_port.read() & 0x20 == 0 {}
            self.data_port.write(byte);
        }
    }
}


/* ********************************************************************************************** */

/// A logger that sends its output to COM1.
///
/// Use it like this:
/// ```
/// use ::core::fmt::Write;
///
/// write!(SerialLogger, "I got {} problems, but logging ain't one", 99);
/// ```
#[derive(Debug)]
pub struct SerialLogger;

impl SerialLogger {
    /// Re-take the lock protecting multiple access to the device.
    ///
    /// # Safety
    ///
    /// This function should only be used when panicking.
    pub unsafe fn force_unlock(&mut self) {
        G_SERIAL.call_once(|| SpinLockIRQ::new(SerialInternal::<Pio<u8>>::new(COM1))).force_unlock();
    }
}

impl Write for SerialLogger {
    /// Writes a string to COM1.
    #[cfg(not(test))]
    fn write_str(&mut self, s: &str) -> Result<(), ::core::fmt::Error> {
        let mut internal = G_SERIAL.call_once(|| SpinLockIRQ::new(SerialInternal::<Pio<u8>>::new(COM1))).lock();
        internal.send_string(s);
        Ok(())
    }

    #[cfg(test)]
    /// When printing in tests, write to stdout.
    fn write_str(&mut self, s: &str) -> Result<(), ::core::fmt::Error> {
        use std::println;
        println!("{}", s);
        Ok(())
    }
}