feat!: Update ConsoleOps

Split ConsoleOps vs SerialOps, rename console
methods, make them saner. Add docs.
Add clear_rx() fn.

Drop default trait impls for safety.
This commit is contained in:
Berkus Decker 2021-12-17 23:18:51 +02:00
parent 16ec45b97c
commit 61eb2f9538
6 changed files with 181 additions and 79 deletions

View File

@ -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(())
}
}

View File

@ -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,
};

View File

@ -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);
}

View File

@ -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(())
}
}

View File

@ -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' {

View File

@ -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) {