diff --git a/machine/src/devices/console.rs b/machine/src/devices/console.rs index 6df1176..b842a0e 100644 --- a/machine/src/devices/console.rs +++ b/machine/src/devices/console.rs @@ -4,26 +4,51 @@ #![allow(dead_code)] -use {crate::platform, core::fmt}; +use { + crate::{devices::SerialOps, platform}, + core::fmt, +}; /// A trait that must be implemented by devices that are candidates for the /// global console. #[allow(unused_variables)] -pub trait ConsoleOps { - fn putc(&self, c: char) {} - fn puts(&self, string: &str) {} - fn getc(&self) -> char { - ' ' - } - fn flush(&self) {} +pub trait ConsoleOps: SerialOps { + /// Send a character + fn write_char(&self, c: char); + /// Display a string + fn write_string(&self, string: &str); + /// Receive a character + fn read_char(&self) -> char; } /// A dummy console that just ignores its inputs. pub struct NullConsole; + impl Drop for NullConsole { fn drop(&mut self) {} } -impl ConsoleOps for NullConsole {} + +impl ConsoleOps for NullConsole { + fn write_char(&self, _c: char) {} + + fn write_string(&self, _string: &str) {} + + fn read_char(&self) -> char { + ' ' + } +} + +impl SerialOps for NullConsole { + fn read_byte(&self) -> u8 { + 0 + } + + fn write_byte(&self, _byte: u8) {} + + fn flush(&self) {} + + fn clear_rx(&self) {} +} /// Possible outputs which the console can store. pub enum Output { @@ -83,15 +108,15 @@ impl Console { /// A command prompt. pub fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] { - self.puts("\n$> "); + self.write_string("\n$> "); let mut i = 0; let mut input; loop { - input = self.getc(); + input = self.read_char(); if input == '\n' { - self.puts("\n"); // do \r\n output + self.write_char('\n'); // do \r\n output return &buf[..i]; } else { if i < buf.len() { @@ -101,7 +126,7 @@ impl Console { return &buf[..i]; } - self.putc(input); + self.write_char(input); } } } @@ -113,21 +138,32 @@ impl Drop for Console { /// Dispatch the respective function to the currently stored output device. impl ConsoleOps for Console { - fn putc(&self, c: char) { - self.current_ptr().putc(c); + fn write_char(&self, c: char) { + self.current_ptr().write_char(c); } - fn puts(&self, string: &str) { - self.current_ptr().puts(string); + fn write_string(&self, string: &str) { + self.current_ptr().write_string(string); } - fn getc(&self) -> char { - self.current_ptr().getc() + fn read_char(&self) -> char { + self.current_ptr().read_char() } +} +impl SerialOps for Console { + fn read_byte(&self) -> u8 { + self.current_ptr().read_byte() + } + fn write_byte(&self, byte: u8) { + self.current_ptr().write_byte(byte) + } fn flush(&self) { self.current_ptr().flush() } + fn clear_rx(&self) { + self.current_ptr().clear_rx() + } } /// Implementing this trait enables usage of the format_args! macros, which in @@ -136,8 +172,7 @@ impl ConsoleOps for Console { /// See src/macros.rs. impl fmt::Write for Console { fn write_str(&mut self, s: &str) -> fmt::Result { - self.current_ptr().puts(s); - + self.current_ptr().write_string(s); Ok(()) } } diff --git a/machine/src/devices/mod.rs b/machine/src/devices/mod.rs index a361cc4..180be7b 100644 --- a/machine/src/devices/mod.rs +++ b/machine/src/devices/mod.rs @@ -2,5 +2,9 @@ * SPDX-License-Identifier: BlueOak-1.0.0 */ pub mod console; +pub mod serial; -pub use console::{Console, ConsoleOps}; +pub use { + console::{Console, ConsoleOps}, + serial::SerialOps, +}; diff --git a/machine/src/devices/serial.rs b/machine/src/devices/serial.rs new file mode 100644 index 0000000..82c4a99 --- /dev/null +++ b/machine/src/devices/serial.rs @@ -0,0 +1,12 @@ +pub trait SerialOps { + /// Read one byte from serial without translation. + fn read_byte(&self) -> u8; + /// Write one byte to serial without translation. + fn write_byte(&self, byte: u8); + /// Wait until the TX FIFO is empty, aka all characters have been put on the + /// line. + fn flush(&self); + /// Consume input until RX FIFO is empty, aka all pending characters have been + /// consumed. + fn clear_rx(&self); +} diff --git a/machine/src/platform/rpi3/mini_uart.rs b/machine/src/platform/rpi3/mini_uart.rs index c59a42e..2f0d0a4 100644 --- a/machine/src/platform/rpi3/mini_uart.rs +++ b/machine/src/platform/rpi3/mini_uart.rs @@ -9,7 +9,10 @@ use tock_registers::interfaces::{Readable, Writeable}; use { super::{gpio, BcmHost}, - crate::{devices::ConsoleOps, platform::MMIODerefWrapper}, + crate::{ + devices::{ConsoleOps, SerialOps}, + platform::MMIODerefWrapper, + }, cfg_if::cfg_if, core::{convert::From, fmt}, tock_registers::{ @@ -212,44 +215,24 @@ impl Drop for PreparedMiniUart { } } -impl ConsoleOps for PreparedMiniUart { +impl SerialOps for PreparedMiniUart { cfg_if! { if #[cfg(not(feature = "noserial"))] { - /// Send a character - fn putc(&self, c: char) { - // wait until we can send - crate::arch::loop_until(|| self.0.registers.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY)); - - // write the character to the buffer - self.0.registers.AUX_MU_IO.set(c as u32); - } - - /// Display a string - fn puts(&self, string: &str) { - for c in string.chars() { - // convert newline to carriage return + newline - if c == '\n' { - self.putc('\r') - } - - self.putc(c); - } - } - - /// Receive a character - fn getc(&self) -> char { + /// Receive a byte without console translation + fn read_byte(&self) -> u8 { // wait until something is in the buffer crate::arch::loop_until(|| self.0.registers.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY)); // read it and return - let mut ret = self.0.registers.AUX_MU_IO.get() as u8 as char; + self.0.registers.AUX_MU_IO.get() as u8 + } - // convert carriage return to newline - if ret == '\r' { - ret = '\n' - } + fn write_byte(&self, b: u8) { + // wait until we can send + crate::arch::loop_until(|| self.0.registers.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY)); - ret + // write the character to the buffer + self.0.registers.AUX_MU_IO.set(b as u32); } /// Wait until the TX FIFO is empty, aka all characters have been put on the @@ -257,20 +240,69 @@ impl ConsoleOps for PreparedMiniUart { fn flush(&self) { crate::arch::loop_until(|| self.0.registers.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_IDLE)); } + + /// Consume input until RX FIFO is empty, aka all pending characters have been + /// consumed. + fn clear_rx(&self) { + crate::arch::loop_while(|| { + let pending = self.0.registers.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY); + if pending { self.read_char(); } + pending + }); + } } else { - fn putc(&self, _c: char) {} - fn puts(&self, _string: &str) {} - fn getc(&self) -> char { + fn read_byte(&self) -> u8 { 0 } + fn write_byte(&self, _byte: u8) {} + fn flush(&self) {} + fn clear_rx(&self) {} + } + } +} + +impl ConsoleOps for PreparedMiniUart { + cfg_if! { + if #[cfg(not(feature = "noserial"))] { + /// Send a character + fn write_char(&self, c: char) { + self.write_byte(c as u8); + } + + /// Display a string + fn write_string(&self, string: &str) { + for c in string.chars() { + // convert newline to carriage return + newline + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + } + + /// Receive a character + fn read_char(&self) -> char { + let mut ret = self.read_byte() as char; + + // convert carriage return to newline -- this doesn't work well for reading binaries... + if ret == '\r' { + ret = '\n' + } + + ret + } + } else { + fn write_char(&self, _c: char) {} + fn write_string(&self, _string: &str) {} + fn read_char(&self) -> char { '\n' } - fn flush(&self) {} } } } impl fmt::Write for PreparedMiniUart { fn write_str(&mut self, s: &str) -> fmt::Result { - self.puts(s); + self.write_string(s); Ok(()) } } diff --git a/machine/src/platform/rpi3/pl011_uart.rs b/machine/src/platform/rpi3/pl011_uart.rs index cd1e04c..7b83b2a 100644 --- a/machine/src/platform/rpi3/pl011_uart.rs +++ b/machine/src/platform/rpi3/pl011_uart.rs @@ -14,7 +14,11 @@ use { mailbox::{self, MailboxOps}, BcmHost, }, - crate::{arch::loop_until, devices::ConsoleOps, platform::MMIODerefWrapper}, + crate::{ + arch::loop_until, + devices::{ConsoleOps, SerialOps}, + platform::MMIODerefWrapper, + }, snafu::Snafu, tock_registers::{ interfaces::{Readable, Writeable}, @@ -237,35 +241,49 @@ impl Drop for PreparedPL011Uart { } } -impl ConsoleOps for PreparedPL011Uart { - /// Send a character - fn putc(&self, c: char) { +impl SerialOps for PreparedPL011Uart { + fn write_byte(&self, b: u8) { // wait until we can send loop_until(|| !self.0.registers.FR.is_set(FR::TXFF)); // write the character to the buffer - self.0.registers.DR.set(c as u32); + self.0.registers.DR.set(b as u32); } - /// Display a string - fn puts(&self, string: &str) { - for c in string.chars() { - // convert newline to carriage return + newline - if c == '\n' { - self.putc('\r') - } - - self.putc(c); - } - } - - /// Receive a character - fn getc(&self) -> char { + fn read_byte(&self) -> u8 { // wait until something is in the buffer loop_until(|| !self.0.registers.FR.is_set(FR::RXFE)); // read it and return - let mut ret = self.0.registers.DR.get() as u8 as char; + self.0.registers.DR.get() as u8 + } + + fn flush(&self) {} + + fn clear_rx(&self) {} +} + +impl ConsoleOps for PreparedPL011Uart { + /// Send a character + fn write_char(&self, c: char) { + self.write_byte(c as u8) + } + + /// Display a string + fn write_string(&self, string: &str) { + for c in string.chars() { + // convert newline to carriage return + newline + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + } + + /// Receive a character + fn read_char(&self) -> char { + let mut ret = self.read_byte() as char; // convert carriage return to newline if ret == '\r' { diff --git a/nucleus/src/main.rs b/nucleus/src/main.rs index 7f651ef..dea9eae 100644 --- a/nucleus/src/main.rs +++ b/nucleus/src/main.rs @@ -19,6 +19,8 @@ #[cfg(not(test))] use core::panic::PanicInfo; +#[allow(unused_imports)] +use machine::devices::SerialOps; use { cfg_if::cfg_if, machine::{ @@ -96,7 +98,6 @@ fn init_uart_serial() { // physical wires (e.g. the Framebuffer), you don't need to do this, // because flush() is anyways called implicitly by replace_with(). This // is just a special case. - use machine::devices::console::ConsoleOps; CONSOLE.lock(|c| c.flush()); match uart.prepare(mbox, &gpio) {