509 lines
13 KiB
Rust
Raw Normal View History

2023-08-03 18:49:29 +03:00
//! Console device interfaces
2023-08-03 13:01:30 +03:00
use core::mem::size_of;
use abi::{error::Error, primitive_enum};
2023-08-03 18:49:29 +03:00
use alloc::vec::Vec;
2023-08-06 17:06:34 +03:00
use bitflags::bitflags;
2023-08-03 13:01:30 +03:00
use crate::{
debug::DebugSink,
mem::{
phys::{self, PageUsage},
ConvertAddress,
},
sync::IrqSafeSpinlock,
2023-08-03 18:49:29 +03:00
task::tasklet::TaskFlow,
2023-08-06 17:06:34 +03:00
util::StaticVector,
2023-08-03 13:01:30 +03:00
};
const CONSOLE_ROW_LEN: usize = 128;
2023-08-06 17:06:34 +03:00
const DEFAULT_FG_COLOR: ColorAttribute = ColorAttribute::White;
const DEFAULT_BG_COLOR: ColorAttribute = ColorAttribute::Blue;
2023-08-03 13:01:30 +03:00
primitive_enum! {
2023-08-03 18:49:29 +03:00
#[doc = "Color attribute of a console character"]
2023-08-03 13:01:30 +03:00
pub enum ColorAttribute: u8 {
2023-08-03 18:49:29 +03:00
#[doc = "..."]
2023-08-03 13:01:30 +03:00
Black = 0,
2023-08-03 18:49:29 +03:00
#[doc = "..."]
2023-08-03 13:01:30 +03:00
Red = 1,
2023-08-03 18:49:29 +03:00
#[doc = "..."]
2023-08-03 13:01:30 +03:00
Green = 2,
2023-08-03 18:49:29 +03:00
#[doc = "..."]
2023-08-06 17:06:34 +03:00
Yellow = 3,
#[doc = "..."]
Blue = 4,
#[doc = "..."]
Magenta = 5,
#[doc = "..."]
Cyan = 6,
2023-08-03 18:49:29 +03:00
#[doc = "..."]
2023-08-03 13:01:30 +03:00
White = 7,
}
}
2023-08-06 17:06:34 +03:00
bitflags! {
#[derive(Clone, Copy)]
pub struct Attributes: u8 {
const BOLD = 1 << 0;
}
}
2023-08-03 13:01:30 +03:00
impl ColorAttribute {
2023-08-06 17:06:34 +03:00
pub fn from_vt100(val: u8) -> Self {
match val {
0..=7 => Self::try_from(val).unwrap(),
_ => todo!(),
}
}
2023-08-03 18:49:29 +03:00
/// Converts the attribute to RGBA representation
2023-08-06 17:06:34 +03:00
pub fn as_rgba(&self, bold: bool) -> u32 {
let color = match self {
Self::Black => 0x000000,
Self::Red => 0x7F0000,
Self::Green => 0x007F00,
Self::Yellow => 0x7F7F00,
Self::Blue => 0x00007F,
Self::Magenta => 0x7F007F,
Self::Cyan => 0x007F7F,
Self::White => 0x7F7F7F,
};
if bold {
color * 2
} else {
color
2023-08-03 13:01:30 +03:00
}
}
}
2023-08-03 18:49:29 +03:00
/// Represents a single character with its attributes
2023-08-03 13:01:30 +03:00
#[derive(Clone, Copy)]
2023-08-06 17:06:34 +03:00
#[repr(C)]
pub struct ConsoleChar {
attributes: u16,
char: u8,
_pad: u8,
}
2023-08-03 13:01:30 +03:00
2023-08-03 18:49:29 +03:00
/// Represents a single line in the console buffer
2023-08-03 13:01:30 +03:00
#[derive(Clone, Copy)]
pub struct ConsoleRow {
dirty: u8,
chars: [ConsoleChar; CONSOLE_ROW_LEN],
}
2023-08-03 18:49:29 +03:00
/// Buffer that contains text rows of the console with their attributes + tracks dirty rows which
/// need to be flushed to the display
2023-08-03 13:01:30 +03:00
pub struct ConsoleBuffer {
rows: &'static mut [ConsoleRow],
width: u32,
height: u32,
}
2023-08-06 17:06:34 +03:00
pub enum EscapeState {
Normal,
Escape,
Csi,
}
2023-08-03 18:49:29 +03:00
/// Common state for console output devices
2023-08-03 13:01:30 +03:00
pub struct ConsoleState {
2023-08-03 18:49:29 +03:00
/// Current cursor row
pub cursor_row: u32,
/// Current cursor column
pub cursor_col: u32,
/// Current foreground color
pub fg_color: ColorAttribute,
2023-08-06 17:06:34 +03:00
pub bg_color: ColorAttribute,
pub attributes: Attributes,
esc_args: StaticVector<u32, 4>,
esc_state: EscapeState,
2023-08-03 18:49:29 +03:00
/// Row buffer
2023-08-03 13:01:30 +03:00
pub buffer: ConsoleBuffer,
}
2023-08-03 18:49:29 +03:00
/// Helper type to iterate over dirty rows in the buffer
2023-08-03 13:01:30 +03:00
pub struct RowIter<'a> {
buffer: &'a mut ConsoleBuffer,
index: u32,
}
2023-08-03 18:49:29 +03:00
/// Interface to implement buffered console semantics on an abstract console output device
2023-08-03 13:01:30 +03:00
pub trait DisplayConsole {
2023-08-03 18:49:29 +03:00
/// Returns the state lock
2023-08-03 13:01:30 +03:00
fn state(&self) -> &IrqSafeSpinlock<ConsoleState>;
2023-08-03 18:49:29 +03:00
/// Allocates a new buffer for the console according to the display's size
fn reallocate_buffer(&self) -> Result<(), Error>;
2023-08-03 13:01:30 +03:00
2023-08-03 18:49:29 +03:00
/// Flushes the data from console buffer to the display
2023-08-03 13:01:30 +03:00
fn flush(&self, state: &mut ConsoleState);
2023-08-03 18:49:29 +03:00
/// Writes characters to the backing buffer + handles special control sequences
2023-08-03 13:01:30 +03:00
fn write_char(&self, c: u8) {
let mut state = self.state().lock();
if state.putc(c) {
2023-08-03 18:49:29 +03:00
self.flush(&mut state);
2023-08-03 13:01:30 +03:00
}
}
}
impl ConsoleChar {
2023-08-03 18:49:29 +03:00
/// Empty character
2023-08-06 17:06:34 +03:00
pub const BLANK: Self = Self {
attributes: 0,
char: 0,
_pad: 0,
};
2023-08-03 13:01:30 +03:00
2023-08-03 18:49:29 +03:00
/// Constructs a console character from a char and its attributes
2023-08-03 13:01:30 +03:00
#[inline]
2023-08-06 17:06:34 +03:00
pub fn from_parts(
char: u8,
fg: ColorAttribute,
bg: ColorAttribute,
attributes: Attributes,
) -> Self {
let attributes = ((attributes.bits() as u16) << 8)
| ((u8::from(bg) as u16) << 4)
| (u8::from(fg) as u16);
Self {
attributes,
char,
_pad: 0,
}
2023-08-03 13:01:30 +03:00
}
#[inline]
2023-08-06 17:06:34 +03:00
pub fn attributes(self) -> (ColorAttribute, ColorAttribute, Attributes) {
let fg = ColorAttribute::try_from((self.attributes & 0xF) as u8).unwrap();
let bg = ColorAttribute::try_from(((self.attributes >> 4) & 0xF) as u8).unwrap();
let attributes = Attributes::from_bits((self.attributes >> 8) as u8).unwrap();
2023-08-03 18:49:29 +03:00
2023-08-06 17:06:34 +03:00
(fg, bg, attributes)
2023-08-03 13:01:30 +03:00
}
2023-08-03 18:49:29 +03:00
/// Returns the character data of this [ConsoleChar]
2023-08-03 13:01:30 +03:00
#[inline]
pub const fn character(self) -> u8 {
2023-08-06 17:06:34 +03:00
self.char
2023-08-03 13:01:30 +03:00
}
}
impl<'a> RowIter<'a> {
2023-08-03 18:49:29 +03:00
/// Returns the next dirty row
pub fn next_dirty(&mut self) -> Option<(u32, &[ConsoleChar])> {
2023-08-03 13:01:30 +03:00
loop {
if self.index == self.buffer.height {
return None;
}
2023-08-03 18:49:29 +03:00
if !self.buffer.rows[self.index as usize].clear_dirty() {
self.index += 1;
continue;
}
2023-08-03 13:01:30 +03:00
let row_index = self.index;
let row = &self.buffer.rows[self.index as usize];
self.index += 1;
2023-08-03 18:49:29 +03:00
return Some((row_index, &row.chars));
2023-08-03 13:01:30 +03:00
}
}
}
impl ConsoleRow {
2023-08-03 18:49:29 +03:00
/// Constructs a row filled with blank characters
2023-08-03 13:01:30 +03:00
pub const fn zeroed() -> Self {
Self {
2023-08-06 17:06:34 +03:00
dirty: 1,
chars: [ConsoleChar {
attributes: ((DEFAULT_BG_COLOR as u8) as u16) << 4,
char: b' ',
_pad: 0,
}; CONSOLE_ROW_LEN],
2023-08-03 13:01:30 +03:00
}
}
2023-08-03 18:49:29 +03:00
/// Returns `true` if the row's dirty flag is set
2023-08-03 13:01:30 +03:00
#[inline]
pub const fn is_dirty(&self) -> bool {
self.dirty != 0
}
2023-08-03 18:49:29 +03:00
/// Clears "dirty" flag for the row
#[inline]
pub fn clear_dirty(&mut self) -> bool {
let old = self.dirty;
self.dirty = 0;
old == 1
}
/// Clears the console row with blank characters
2023-08-06 17:06:34 +03:00
pub fn clear(&mut self, bg: ColorAttribute) {
2023-08-03 13:01:30 +03:00
self.dirty = 1;
2023-08-06 17:06:34 +03:00
self.chars
.fill(ConsoleChar::from_parts(b' ', bg, bg, Attributes::empty()));
2023-08-03 13:01:30 +03:00
}
}
impl ConsoleBuffer {
2023-08-03 18:49:29 +03:00
/// Constructs a fixed-size console buffer
2023-08-03 13:01:30 +03:00
pub const fn fixed(rows: &'static mut [ConsoleRow], width: u32, height: u32) -> Self {
Self {
rows,
width,
height,
}
}
2023-08-03 18:49:29 +03:00
/// Reallocates the internal buffer with a new size
2023-08-03 13:01:30 +03:00
pub fn reallocate(&mut self, new_height: u32) -> Result<(), Error> {
// TODO suppress debugging output here
if new_height <= self.height {
// Keep using the old buffer
return Ok(());
}
let size = size_of::<ConsoleRow>() * (new_height as usize);
let page_count = (size + 0xFFF) / 0x1000;
let pages = phys::alloc_pages_contiguous(page_count, PageUsage::Used)?;
let data = unsafe {
core::slice::from_raw_parts_mut(
pages.virtualize() as *mut ConsoleRow,
new_height as usize,
)
};
// Copy rows from the old buffer
2023-08-03 18:49:29 +03:00
data[0..self.height as usize].copy_from_slice(self.rows);
2023-08-03 13:01:30 +03:00
data[self.height as usize..].fill(ConsoleRow::zeroed());
self.rows = data;
self.height = new_height;
Ok(())
}
fn set_char(&mut self, row: u32, col: u32, c: ConsoleChar) {
self.rows[row as usize].dirty = 1;
self.rows[row as usize].chars[col as usize] = c;
}
2023-08-03 18:49:29 +03:00
/// Returns an iterator over dirty rows, while clearing dirty flag for them
pub fn flush_rows(&mut self) -> RowIter {
2023-08-03 13:01:30 +03:00
RowIter {
buffer: self,
index: 0,
}
}
2023-08-06 17:06:34 +03:00
fn clear(&mut self, bg: ColorAttribute) {
for row in self.rows.iter_mut() {
row.clear(bg);
}
}
fn scroll_once(&mut self, bg: ColorAttribute) {
2023-08-03 13:01:30 +03:00
self.rows.copy_within(1.., 0);
2023-08-06 17:06:34 +03:00
self.rows[(self.height - 1) as usize].clear(bg);
2023-08-03 18:49:29 +03:00
// Mark everything dirty
self.rows.iter_mut().for_each(|row| {
row.dirty = 1;
});
2023-08-03 13:01:30 +03:00
}
}
impl ConsoleState {
2023-08-03 18:49:29 +03:00
/// Constructs a new console state with given buffer
2023-08-03 13:01:30 +03:00
pub fn new(buffer: ConsoleBuffer) -> Self {
Self {
cursor_row: 0,
cursor_col: 0,
2023-08-06 17:06:34 +03:00
fg_color: DEFAULT_FG_COLOR,
bg_color: DEFAULT_BG_COLOR,
attributes: Attributes::empty(),
esc_args: StaticVector::new(),
esc_state: EscapeState::Normal,
2023-08-03 13:01:30 +03:00
buffer,
}
}
2023-08-06 17:06:34 +03:00
fn putc_normal(&mut self, c: u8) -> bool {
2023-08-03 13:01:30 +03:00
let mut flush = false;
match c {
2023-08-06 17:06:34 +03:00
c if c >= 127 => {
2023-08-04 11:00:31 +03:00
self.buffer.set_char(
self.cursor_row,
self.cursor_col,
2023-08-06 17:06:34 +03:00
ConsoleChar::from_parts(
b'?',
self.fg_color,
ColorAttribute::Red,
self.attributes,
),
2023-08-04 11:00:31 +03:00
);
self.cursor_col += 1;
}
2023-08-06 17:06:34 +03:00
b'\x1b' => {
self.esc_state = EscapeState::Escape;
return false;
}
2023-08-03 13:01:30 +03:00
b'\n' => {
self.cursor_row += 1;
self.cursor_col = 0;
flush = true;
}
_ => {
self.buffer.set_char(
self.cursor_row,
self.cursor_col,
2023-08-06 17:06:34 +03:00
ConsoleChar::from_parts(c, self.fg_color, self.bg_color, self.attributes),
2023-08-03 13:01:30 +03:00
);
self.cursor_col += 1;
}
}
if self.cursor_col == self.buffer.width {
self.cursor_col = 0;
self.cursor_row += 1;
}
if self.cursor_row == self.buffer.height {
2023-08-06 17:06:34 +03:00
self.buffer.scroll_once(self.bg_color);
2023-08-03 13:01:30 +03:00
self.cursor_row = self.buffer.height - 1;
flush = true;
}
flush
}
2023-08-06 17:06:34 +03:00
fn handle_csi(&mut self, c: u8) -> bool {
match c {
// Move back one character
b'D' => {
if self.cursor_col > 0 {
self.cursor_col = self.cursor_col - 1;
}
}
// Manipulate display attributes
b'm' => match self.esc_args[0] {
// Reset
0 => {
self.fg_color = DEFAULT_FG_COLOR;
self.bg_color = DEFAULT_BG_COLOR;
self.attributes = Attributes::empty();
}
// Bold
1 => {
self.attributes |= Attributes::BOLD;
}
// Foreground colors
31..=37 => {
let vt_color = self.esc_args[0] % 10;
self.fg_color = ColorAttribute::from_vt100(vt_color as u8);
}
_ => loop {},
},
// Move cursor to position
b'f' => {
let row = self.esc_args[1].clamp(1, self.buffer.height) - 1;
let col = self.esc_args[1].clamp(1, CONSOLE_ROW_LEN as u32) - 1;
self.cursor_row = row;
self.cursor_col = col;
}
// Clear rows/columns/screen
b'J' => match self.esc_args[0] {
// Erase lines down
0 => loop {},
// Erase lines up
1 => loop {},
// Erase all
2 => {
self.buffer.clear(self.bg_color);
}
_ => (),
},
_ => loop {},
}
self.esc_state = EscapeState::Normal;
false
}
fn handle_csi_byte(&mut self, c: u8) -> bool {
match c {
b'0'..=b'9' => {
let arg = self.esc_args.last_mut().unwrap();
*arg *= 10;
*arg += (c - b'0') as u32;
false
}
b';' => {
self.esc_args.push(0);
false
}
_ => self.handle_csi(c),
}
}
fn putc(&mut self, c: u8) -> bool {
match self.esc_state {
EscapeState::Normal => self.putc_normal(c),
EscapeState::Escape => match c {
b'[' => {
self.esc_state = EscapeState::Csi;
self.esc_args.clear();
self.esc_args.push(0);
false
}
_ => {
self.esc_state = EscapeState::Normal;
false
}
},
EscapeState::Csi => self.handle_csi_byte(c),
}
}
2023-08-03 13:01:30 +03:00
}
impl DebugSink for dyn DisplayConsole {
fn putc(&self, c: u8) -> Result<(), Error> {
self.write_char(c);
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
2023-08-03 18:49:29 +03:00
static CONSOLES: IrqSafeSpinlock<Vec<&'static dyn DisplayConsole>> =
IrqSafeSpinlock::new(Vec::new());
/// Adds a console device to a auto-flush list
pub fn add_console_autoflush(console: &'static dyn DisplayConsole) {
CONSOLES.lock().push(console);
}
/// Periodically flushes data from console buffers onto their displays
pub fn task_update_consoles() -> TaskFlow {
for console in CONSOLES.lock().iter() {
let mut state = console.state().lock();
console.flush(&mut state);
}
TaskFlow::Continue
}