From bac1e92b910f43492f41cc7ccd98477e706b506e Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Wed, 16 Oct 2024 15:05:12 +0200 Subject: [PATCH] Refactor vga driver (#688) * Add blank command * Move vga driver to module * Remove vga command * Split vga module * Rename vga device type * Add /dev/vga/mode device file * Restore font after mode change * Remove cvs support for palette * Refactor api::vga::color * Move api::vga::color to sys::vga::color * Move api::vga::palette to sys::vga::palette * Use 256 colors palette * Refactor Color * Rename Color#to_vga_reg to Color#register * Add Palette#set * Add write-only /dev/vga/palette * Remove unused set_palette * Add read operation to /dev/vga/palette * Add palette backup and restore * Check for device presence * Backup palette only in 80x25 mode * Clear screen on mode change * Make /dev/vga/mode readable * Fix double buffer allocation * Add framebuffer:addr() * Add /dev/vga/buffer device * Reorder device files * Rename some functions * Move test * Run clippy * Add color test * Refactor code * Move blank to userspace --- Makefile | 1 + dsk/ini/palettes/gruvbox-dark.csv | 17 -- dsk/ini/palettes/gruvbox-light.csv | 17 -- dsk/var/pkg/blank | 1 + dsk/var/pkg/index.html | 1 + src/api/fs.rs | 28 ++- src/api/vga/color.rs | 95 ------- src/api/vga/mod.rs | 19 +- src/api/vga/palette.rs | 65 ----- src/bin/blank.rs | 20 ++ src/sys/fs/device.rs | 177 +++++++------ src/sys/vga/buffer.rs | 46 ++++ src/sys/vga/color.rs | 96 +++++++ src/sys/vga/font.rs | 56 +++++ src/sys/vga/mod.rs | 141 +++++++++++ src/sys/vga/palette.rs | 139 ++++++++++ src/sys/vga/screen.rs | 241 ++++++++++++++++++ src/sys/{vga.rs => vga/writer.rs} | 392 +++-------------------------- src/usr/install.rs | 5 +- src/usr/mod.rs | 1 - src/usr/render.rs | 61 ++--- src/usr/shell.rs | 5 +- src/usr/vga.rs | 81 ------ 23 files changed, 936 insertions(+), 769 deletions(-) delete mode 100644 dsk/ini/palettes/gruvbox-dark.csv delete mode 100644 dsk/ini/palettes/gruvbox-light.csv create mode 100644 dsk/var/pkg/blank delete mode 100644 src/api/vga/color.rs delete mode 100644 src/api/vga/palette.rs create mode 100644 src/bin/blank.rs create mode 100644 src/sys/vga/buffer.rs create mode 100644 src/sys/vga/color.rs create mode 100644 src/sys/vga/font.rs create mode 100644 src/sys/vga/mod.rs create mode 100644 src/sys/vga/palette.rs create mode 100644 src/sys/vga/screen.rs rename src/sys/{vga.rs => vga/writer.rs} (63%) delete mode 100644 src/usr/vga.rs diff --git a/Makefile b/Makefile index 9fc4f9f12..41bb07a97 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ user-rust: basename -s .rs src/bin/*.rs | xargs -I {} \ cargo rustc $(user-cargo-opts) --bin {} cargo rustc $(user-cargo-opts) --bin exec -- $(linker-opts) + cargo rustc $(user-cargo-opts) --bin blank -- $(linker-opts) cargo rustc $(user-cargo-opts) --bin hello -- $(linker-opts) cargo rustc $(user-cargo-opts) --bin geocal -- $(linker-opts) cargo rustc $(user-cargo-opts) --bin geodate -- $(linker-opts) diff --git a/dsk/ini/palettes/gruvbox-dark.csv b/dsk/ini/palettes/gruvbox-dark.csv deleted file mode 100644 index 1414de891..000000000 --- a/dsk/ini/palettes/gruvbox-dark.csv +++ /dev/null @@ -1,17 +0,0 @@ -# VGA Palette File (Red, Green, Blue) -0x28, 0x28, 0x28 # Black -0x45, 0x85, 0x88 # Navy -0x98, 0x97, 0x1A # Green -0x68, 0x9D, 0x6A # Teal -0xCC, 0x24, 0x1D # Red -0xB1, 0x62, 0x86 # Purple -0xD7, 0x99, 0x21 # Maroon -0xEB, 0xDB, 0xB2 # Silver -0xA8, 0x99, 0x84 # Gray -0x83, 0xa5, 0x98 # Blue -0xB8, 0xBB, 0x26 # Lime -0x8E, 0xC0, 0x7C # Aqua -0xFB, 0x49, 0x34 # Red -0xD3, 0x86, 0x9B # Fushia -0xFA, 0xBD, 0x2F # Yellow -0xFB, 0xF1, 0xC7 # White diff --git a/dsk/ini/palettes/gruvbox-light.csv b/dsk/ini/palettes/gruvbox-light.csv deleted file mode 100644 index 13e5085ba..000000000 --- a/dsk/ini/palettes/gruvbox-light.csv +++ /dev/null @@ -1,17 +0,0 @@ -# VGA Palette File (Red, Green, Blue) -0xFB, 0xF1, 0xC7 # Black -0x45, 0x85, 0x88 # Navy -0x98, 0x97, 0x1A # Green -0x68, 0x9D, 0x6A # Teal -0xCC, 0x24, 0x1D # Red -0xB1, 0x62, 0x86 # Purple -0xD7, 0x99, 0x21 # Maroon -0x3C, 0x38, 0x36 # Silver -0x7C, 0x6F, 0x64 # Gray -0x07, 0x66, 0x78 # Blue -0x79, 0x74, 0x0E # Lime -0x42, 0x7B, 0x58 # Aqua -0x9D, 0x00, 0x06 # Red -0x8F, 0x3F, 0x71 # Fushia -0xB5, 0x76, 0x14 # Yellow -0x28, 0x28, 0x28 # White diff --git a/dsk/var/pkg/blank b/dsk/var/pkg/blank new file mode 100644 index 000000000..8e36ec5c1 --- /dev/null +++ b/dsk/var/pkg/blank @@ -0,0 +1 @@ +/bin/blank diff --git a/dsk/var/pkg/index.html b/dsk/var/pkg/index.html index 12159e471..17d5d57b2 100644 --- a/dsk/var/pkg/index.html +++ b/dsk/var/pkg/index.html @@ -1,4 +1,5 @@ beep +blank chess exec fonts diff --git a/src/api/fs.rs b/src/api/fs.rs index 196fc230e..0841f9002 100644 --- a/src/api/fs.rs +++ b/src/api/fs.rs @@ -154,20 +154,24 @@ fn device_buffer(name: &str) -> Result, ()> { Ok(buf) } +// TODO: Move this to sys::fs::device fn device_type(name: &str) -> Result { match name { - "null" => Ok(DeviceType::Null), - "file" => Ok(DeviceType::File), - "console" => Ok(DeviceType::Console), - "random" => Ok(DeviceType::Random), - "uptime" => Ok(DeviceType::Uptime), - "realtime" => Ok(DeviceType::Realtime), - "rtc" => Ok(DeviceType::RTC), - "tcp" => Ok(DeviceType::TcpSocket), - "udp" => Ok(DeviceType::UdpSocket), - "font" => Ok(DeviceType::VgaFont), - "ata" => Ok(DeviceType::Drive), - _ => Err(()), + "null" => Ok(DeviceType::Null), + "file" => Ok(DeviceType::File), + "console" => Ok(DeviceType::Console), + "random" => Ok(DeviceType::Random), + "uptime" => Ok(DeviceType::Uptime), + "realtime" => Ok(DeviceType::Realtime), + "rtc" => Ok(DeviceType::RTC), + "tcp" => Ok(DeviceType::TcpSocket), + "udp" => Ok(DeviceType::UdpSocket), + "vga-buffer" => Ok(DeviceType::VgaBuffer), + "vga-font" => Ok(DeviceType::VgaFont), + "vga-mode" => Ok(DeviceType::VgaMode), + "vga-palette" => Ok(DeviceType::VgaPalette), + "ata" => Ok(DeviceType::Drive), + _ => Err(()), } } diff --git a/src/api/vga/color.rs b/src/api/vga/color.rs deleted file mode 100644 index 2ca1d0263..000000000 --- a/src/api/vga/color.rs +++ /dev/null @@ -1,95 +0,0 @@ -/// The standard color palette in VGA text mode -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum Color { - DarkBlack = 0x0, - DarkBlue = 0x1, - DarkGreen = 0x2, - DarkCyan = 0x3, - DarkRed = 0x4, - DarkMagenta = 0x5, - DarkYellow = 0x6, - DarkWhite = 0x7, - - BrightBlack = 0x8, - BrightBlue = 0x9, - BrightGreen = 0xA, - BrightCyan = 0xB, - BrightRed = 0xC, - BrightMagenta = 0xD, - BrightYellow = 0xE, - BrightWhite = 0xF, -} - -const COLORS: [Color; 16] = [ - Color::DarkBlack, - Color::DarkBlue, - Color::DarkGreen, - Color::DarkCyan, - Color::DarkRed, - Color::DarkMagenta, - Color::DarkYellow, - Color::DarkWhite, - Color::BrightBlack, - Color::BrightBlue, - Color::BrightGreen, - Color::BrightCyan, - Color::BrightRed, - Color::BrightMagenta, - Color::BrightYellow, - Color::BrightWhite, -]; - -pub fn colors() -> [Color; 16] { - COLORS -} - -pub fn from_index(index: usize) -> Color { - COLORS[index] -} - -pub fn from_ansi(code: u8) -> Color { - match code { - 30 => Color::DarkBlack, - 31 => Color::DarkRed, - 32 => Color::DarkGreen, - 33 => Color::DarkYellow, - 34 => Color::DarkBlue, - 35 => Color::DarkMagenta, - 36 => Color::DarkCyan, - 37 => Color::DarkWhite, - 90 => Color::BrightBlack, - 91 => Color::BrightRed, - 92 => Color::BrightGreen, - 93 => Color::BrightYellow, - 94 => Color::BrightBlue, - 95 => Color::BrightMagenta, - 96 => Color::BrightCyan, - 97 => Color::BrightWhite, - _ => Color::DarkBlack, // TODO: Error - } -} - -impl Color { - pub fn to_vga_reg(&self) -> u8 { - match self { - Color::DarkBlack => 0x00, - Color::DarkBlue => 0x01, - Color::DarkGreen => 0x02, - Color::DarkCyan => 0x03, - Color::DarkRed => 0x04, - Color::DarkMagenta => 0x05, - Color::DarkYellow => 0x14, - Color::DarkWhite => 0x07, - Color::BrightBlack => 0x38, - Color::BrightBlue => 0x39, - Color::BrightGreen => 0x3A, - Color::BrightCyan => 0x3B, - Color::BrightRed => 0x3C, - Color::BrightMagenta => 0x3D, - Color::BrightYellow => 0x3E, - Color::BrightWhite => 0x3F, - } - } -} diff --git a/src/api/vga/mod.rs b/src/api/vga/mod.rs index e8902ad9d..768b586ea 100644 --- a/src/api/vga/mod.rs +++ b/src/api/vga/mod.rs @@ -1,5 +1,16 @@ -pub mod color; -pub mod palette; +use crate::api::fs; -pub use color::Color; -pub use palette::Palette; +pub fn graphic_mode() { + let dev = "/dev/vga/mode"; + if fs::is_device(dev) { + fs::write(dev, b"320x200").ok(); + } +} + +pub fn text_mode() { + let dev = "/dev/vga/mode"; + if fs::is_device(dev) { + fs::write(dev, b"80x25").ok(); + print!("\x1b[2J\x1b[1;1H"); // Clear screen and move to top + } +} diff --git a/src/api/vga/palette.rs b/src/api/vga/palette.rs deleted file mode 100644 index bfc494afc..000000000 --- a/src/api/vga/palette.rs +++ /dev/null @@ -1,65 +0,0 @@ -use alloc::vec::Vec; -use core::convert::TryInto; - -// TODO: Move this to kernel after removing the `vga set palette` command -pub struct Palette { - pub colors: [(u8, u8, u8); 16], -} - -impl Palette { - pub fn default() -> Palette { - Palette { - colors: [ - (0x00, 0x00, 0x00), // Black - (0x00, 0x00, 0x80), // Blue - (0x00, 0x80, 0x00), // Green - (0x00, 0x80, 0x80), // Cyan - (0x80, 0x00, 0x00), // Red - (0x80, 0x00, 0x80), // Magenta - (0x80, 0x80, 0x00), // Brown (Dark Yellow) - (0xC0, 0xC0, 0xC0), // Light Gray - (0x80, 0x80, 0x80), // Dark Gray (Gray) - (0x00, 0x00, 0xFF), // Light Blue - (0x00, 0xFF, 0x00), // Light Green - (0x00, 0xFF, 0xFF), // Light Cyan - (0xFF, 0x00, 0x00), // Light Red - (0xFF, 0x00, 0xFF), // Pink (Light Magenta) - (0xFF, 0xFF, 0x00), // Yellow (Light Yellow) - (0xFF, 0xFF, 0xFF), // White - ], - } - } -} - -// TODO: Remove this -pub fn from_csv(s: &str) -> Result { - let colors: Vec<_> = s.lines().filter_map(|line| { - let line = line.split('#').next().unwrap(); // Remove comments - let color: Vec = line.split(',').filter_map(|value| { - let radix = if value.contains("0x") { 16 } else { 10 }; - let value = value.trim().trim_start_matches("0x"); - u8::from_str_radix(value, radix).ok() - }).collect(); - if color.len() == 3 { // RGB values - Some((color[0], color[1], color[2])) - } else { - None - } - }).collect(); - if let Ok(colors) = colors.try_into() { // Array of 16 colors - Ok(Palette { colors }) - } else { - Err(()) - } -} - -#[test_case] -fn test_from_csv() { - assert!(from_csv("").is_err()); - assert!(from_csv("0,0,0,0").is_err()); - - let s = include_str!("../../../dsk/ini/palettes/gruvbox-dark.csv"); - let palette = from_csv(s).unwrap(); - assert_eq!(palette.colors[0x03].0, 0x68); - assert_eq!(palette.colors[0x0D].1, 0x86); -} diff --git a/src/bin/blank.rs b/src/bin/blank.rs new file mode 100644 index 000000000..1f452c321 --- /dev/null +++ b/src/bin/blank.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use moros::print; +use moros::api::io; +use moros::api::vga; +use moros::entry_point; + +entry_point!(main); + +fn main(_args: &[&str]) { + vga::graphic_mode(); + print!("\x1b]R\x1b[1A"); // Reset palette + while io::stdin().read_char().is_none() { + x86_64::instructions::hlt(); + } + vga::text_mode(); +} diff --git a/src/sys/fs/device.rs b/src/sys/fs/device.rs index 9a70a18f4..ee4197bf5 100644 --- a/src/sys/fs/device.rs +++ b/src/sys/fs/device.rs @@ -10,7 +10,7 @@ use crate::sys::console::Console; use crate::sys::net::socket::tcp::TcpSocket; use crate::sys::net::socket::udp::UdpSocket; use crate::sys::rng::Random; -use crate::sys::vga::VgaFont; +use crate::sys::vga::{VgaFont, VgaMode, VgaPalette, VgaBuffer}; use alloc::vec; use alloc::vec::Vec; @@ -20,17 +20,20 @@ use core::convert::TryInto; #[derive(PartialEq, Eq, Clone, Copy)] #[repr(u8)] pub enum DeviceType { - Null = 0, - File = 1, - Console = 2, - Random = 3, - Uptime = 4, - Realtime = 5, - RTC = 6, - TcpSocket = 7, - UdpSocket = 8, - Drive = 9, - VgaFont = 10, + Null = 0, + File = 1, + Console = 2, + Random = 3, + Uptime = 4, + Realtime = 5, + RTC = 6, + TcpSocket = 7, + UdpSocket = 8, + Drive = 9, + VgaBuffer = 10, + VgaFont = 11, + VgaMode = 12, + VgaPalette = 13, } impl TryFrom<&[u8]> for DeviceType { @@ -48,7 +51,10 @@ impl TryFrom<&[u8]> for DeviceType { 7 => Ok(DeviceType::TcpSocket), 8 => Ok(DeviceType::UdpSocket), 9 => Ok(DeviceType::Drive), - 10 => Ok(DeviceType::VgaFont), + 10 => Ok(DeviceType::VgaBuffer), + 11 => Ok(DeviceType::VgaFont), + 12 => Ok(DeviceType::VgaMode), + 13 => Ok(DeviceType::VgaPalette), _ => Err(()), } } @@ -60,14 +66,17 @@ impl DeviceType { // store specific device informations. pub fn buf(self) -> Vec { let len = match self { - DeviceType::RTC => RTC::size(), - DeviceType::Uptime => Uptime::size(), - DeviceType::Realtime => Realtime::size(), - DeviceType::Console => Console::size(), - DeviceType::TcpSocket => TcpSocket::size(), - DeviceType::UdpSocket => UdpSocket::size(), - DeviceType::Drive => Drive::size(), - _ => 1, + DeviceType::RTC => RTC::size(), + DeviceType::Uptime => Uptime::size(), + DeviceType::Realtime => Realtime::size(), + DeviceType::Console => Console::size(), + DeviceType::TcpSocket => TcpSocket::size(), + DeviceType::UdpSocket => UdpSocket::size(), + DeviceType::Drive => Drive::size(), + DeviceType::VgaBuffer => VgaBuffer::size(), + DeviceType::VgaMode => VgaMode::size(), + DeviceType::VgaPalette => VgaPalette::size(), + _ => 1, }; let mut res = vec![0; len]; res[0] = self as u8; // Device type @@ -86,7 +95,10 @@ pub enum Device { RTC(RTC), TcpSocket(TcpSocket), UdpSocket(UdpSocket), + VgaBuffer(VgaBuffer), VgaFont(VgaFont), + VgaMode(VgaMode), + VgaPalette(VgaPalette), Drive(Drive), } @@ -95,16 +107,19 @@ impl TryFrom<&[u8]> for Device { fn try_from(buf: &[u8]) -> Result { match buf.try_into()? { - DeviceType::Null => Ok(Device::Null), - DeviceType::File => Ok(Device::File(File::new())), - DeviceType::Console => Ok(Device::Console(Console::new())), - DeviceType::Random => Ok(Device::Random(Random::new())), - DeviceType::Uptime => Ok(Device::Uptime(Uptime::new())), - DeviceType::Realtime => Ok(Device::Realtime(Realtime::new())), - DeviceType::RTC => Ok(Device::RTC(RTC::new())), - DeviceType::TcpSocket => Ok(Device::TcpSocket(TcpSocket::new())), - DeviceType::UdpSocket => Ok(Device::UdpSocket(UdpSocket::new())), - DeviceType::VgaFont => Ok(Device::VgaFont(VgaFont::new())), + DeviceType::Null => Ok(Device::Null), + DeviceType::File => Ok(Device::File(File::new())), + DeviceType::Console => Ok(Device::Console(Console::new())), + DeviceType::Random => Ok(Device::Random(Random::new())), + DeviceType::Uptime => Ok(Device::Uptime(Uptime::new())), + DeviceType::Realtime => Ok(Device::Realtime(Realtime::new())), + DeviceType::RTC => Ok(Device::RTC(RTC::new())), + DeviceType::TcpSocket => Ok(Device::TcpSocket(TcpSocket::new())), + DeviceType::UdpSocket => Ok(Device::UdpSocket(UdpSocket::new())), + DeviceType::VgaBuffer => Ok(Device::VgaBuffer(VgaBuffer::new())), + DeviceType::VgaFont => Ok(Device::VgaFont(VgaFont::new())), + DeviceType::VgaMode => Ok(Device::VgaMode(VgaMode::new())), + DeviceType::VgaPalette => Ok(Device::VgaPalette(VgaPalette::new())), DeviceType::Drive if buf.len() > 2 => { let bus = buf[1]; let dsk = buf[2]; @@ -154,65 +169,77 @@ impl Device { impl FileIO for Device { fn read(&mut self, buf: &mut [u8]) -> Result { match self { - Device::Null => Err(()), - Device::File(io) => io.read(buf), - Device::Console(io) => io.read(buf), - Device::Random(io) => io.read(buf), - Device::Uptime(io) => io.read(buf), - Device::Realtime(io) => io.read(buf), - Device::RTC(io) => io.read(buf), - Device::TcpSocket(io) => io.read(buf), - Device::UdpSocket(io) => io.read(buf), - Device::VgaFont(io) => io.read(buf), - Device::Drive(io) => io.read(buf), + Device::Null => Err(()), + Device::File(io) => io.read(buf), + Device::Console(io) => io.read(buf), + Device::Random(io) => io.read(buf), + Device::Uptime(io) => io.read(buf), + Device::Realtime(io) => io.read(buf), + Device::RTC(io) => io.read(buf), + Device::TcpSocket(io) => io.read(buf), + Device::UdpSocket(io) => io.read(buf), + Device::VgaBuffer(io) => io.read(buf), + Device::VgaFont(io) => io.read(buf), + Device::VgaMode(io) => io.read(buf), + Device::VgaPalette(io) => io.read(buf), + Device::Drive(io) => io.read(buf), } } fn write(&mut self, buf: &[u8]) -> Result { match self { - Device::Null => Ok(0), - Device::File(io) => io.write(buf), - Device::Console(io) => io.write(buf), - Device::Random(io) => io.write(buf), - Device::Uptime(io) => io.write(buf), - Device::Realtime(io) => io.write(buf), - Device::RTC(io) => io.write(buf), - Device::TcpSocket(io) => io.write(buf), - Device::UdpSocket(io) => io.write(buf), - Device::VgaFont(io) => io.write(buf), - Device::Drive(io) => io.write(buf), + Device::Null => Ok(0), + Device::File(io) => io.write(buf), + Device::Console(io) => io.write(buf), + Device::Random(io) => io.write(buf), + Device::Uptime(io) => io.write(buf), + Device::Realtime(io) => io.write(buf), + Device::RTC(io) => io.write(buf), + Device::TcpSocket(io) => io.write(buf), + Device::UdpSocket(io) => io.write(buf), + Device::VgaBuffer(io) => io.write(buf), + Device::VgaFont(io) => io.write(buf), + Device::VgaMode(io) => io.write(buf), + Device::VgaPalette(io) => io.write(buf), + Device::Drive(io) => io.write(buf), } } fn close(&mut self) { match self { - Device::Null => {} - Device::File(io) => io.close(), - Device::Console(io) => io.close(), - Device::Random(io) => io.close(), - Device::Uptime(io) => io.close(), - Device::Realtime(io) => io.close(), - Device::RTC(io) => io.close(), - Device::TcpSocket(io) => io.close(), - Device::UdpSocket(io) => io.close(), - Device::VgaFont(io) => io.close(), - Device::Drive(io) => io.close(), + Device::Null => {} + Device::File(io) => io.close(), + Device::Console(io) => io.close(), + Device::Random(io) => io.close(), + Device::Uptime(io) => io.close(), + Device::Realtime(io) => io.close(), + Device::RTC(io) => io.close(), + Device::TcpSocket(io) => io.close(), + Device::UdpSocket(io) => io.close(), + Device::VgaBuffer(io) => io.close(), + Device::VgaFont(io) => io.close(), + Device::VgaMode(io) => io.close(), + Device::VgaPalette(io) => io.close(), + Device::Drive(io) => io.close(), } } fn poll(&mut self, event: IO) -> bool { match self { - Device::Null => false, - Device::File(io) => io.poll(event), - Device::Console(io) => io.poll(event), - Device::Random(io) => io.poll(event), - Device::Uptime(io) => io.poll(event), - Device::Realtime(io) => io.poll(event), - Device::RTC(io) => io.poll(event), - Device::TcpSocket(io) => io.poll(event), - Device::UdpSocket(io) => io.poll(event), - Device::VgaFont(io) => io.poll(event), - Device::Drive(io) => io.poll(event), + Device::Null => false, + Device::File(io) => io.poll(event), + Device::Console(io) => io.poll(event), + Device::Random(io) => io.poll(event), + Device::Uptime(io) => io.poll(event), + Device::Realtime(io) => io.poll(event), + Device::RTC(io) => io.poll(event), + Device::TcpSocket(io) => io.poll(event), + Device::UdpSocket(io) => io.poll(event), + Device::VgaBuffer(io) => io.poll(event), + Device::VgaFont(io) => io.poll(event), + Device::VgaMode(io) => io.poll(event), + Device::VgaPalette(io) => io.poll(event), + Device::Drive(io) => io.poll(event), } } } diff --git a/src/sys/vga/buffer.rs b/src/sys/vga/buffer.rs new file mode 100644 index 000000000..25b20d03e --- /dev/null +++ b/src/sys/vga/buffer.rs @@ -0,0 +1,46 @@ +use crate::api::fs::{FileIO, IO}; + +#[derive(Debug, Clone)] +pub struct Buffer; + +impl Buffer { + pub fn new() -> Self { + Self + } + + pub fn addr() -> u64 { + 0xA0000 + } + + pub fn size() -> usize { + 320 * 200 + } +} + +impl FileIO for Buffer { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(()) // TODO + } + + fn write(&mut self, buf: &[u8]) -> Result { + let len = buf.len(); + let src = buf.as_ptr(); + let dst = Self::addr() as *mut u8; + if Self::size() < len { + return Err(()); + } + unsafe { + core::ptr::copy_nonoverlapping(src, dst, len); + } + Ok(len) + } + + fn close(&mut self) {} + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => false, // TODO + IO::Write => true, + } + } +} diff --git a/src/sys/vga/color.rs b/src/sys/vga/color.rs new file mode 100644 index 000000000..48cbc02e9 --- /dev/null +++ b/src/sys/vga/color.rs @@ -0,0 +1,96 @@ +/// The standard color palette in VGA text mode +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + DarkBlack = 0x0, + DarkBlue = 0x1, + DarkGreen = 0x2, + DarkCyan = 0x3, + DarkRed = 0x4, + DarkMagenta = 0x5, + DarkYellow = 0x6, + DarkWhite = 0x7, + + BrightBlack = 0x8, + BrightBlue = 0x9, + BrightGreen = 0xA, + BrightCyan = 0xB, + BrightRed = 0xC, + BrightMagenta = 0xD, + BrightYellow = 0xE, + BrightWhite = 0xF, +} + +impl Color { + pub fn from_index(code: usize) -> Color { + match code { + 0x0 => Color::DarkBlack, + 0x1 => Color::DarkBlue, + 0x2 => Color::DarkGreen, + 0x3 => Color::DarkCyan, + 0x4 => Color::DarkRed, + 0x5 => Color::DarkMagenta, + 0x6 => Color::DarkYellow, + 0x7 => Color::DarkWhite, + 0x8 => Color::BrightBlack, + 0x9 => Color::BrightBlue, + 0xA => Color::BrightGreen, + 0xB => Color::BrightCyan, + 0xC => Color::BrightRed, + 0xD => Color::BrightMagenta, + 0xE => Color::BrightYellow, + 0xF => Color::BrightWhite, + _ => Color::DarkBlack, // TODO: Error + } + } + + pub fn from_ansi(code: u8) -> Color { + match code { + 30 => Color::DarkBlack, + 31 => Color::DarkRed, + 32 => Color::DarkGreen, + 33 => Color::DarkYellow, + 34 => Color::DarkBlue, + 35 => Color::DarkMagenta, + 36 => Color::DarkCyan, + 37 => Color::DarkWhite, + 90 => Color::BrightBlack, + 91 => Color::BrightRed, + 92 => Color::BrightGreen, + 93 => Color::BrightYellow, + 94 => Color::BrightBlue, + 95 => Color::BrightMagenta, + 96 => Color::BrightCyan, + 97 => Color::BrightWhite, + _ => Color::DarkBlack, // TODO: Error + } + } + + pub fn register(&self) -> usize { + match self { + Color::DarkBlack => 0x00, + Color::DarkBlue => 0x01, + Color::DarkGreen => 0x02, + Color::DarkCyan => 0x03, + Color::DarkRed => 0x04, + Color::DarkMagenta => 0x05, + Color::DarkYellow => 0x14, + Color::DarkWhite => 0x07, + Color::BrightBlack => 0x38, + Color::BrightBlue => 0x39, + Color::BrightGreen => 0x3A, + Color::BrightCyan => 0x3B, + Color::BrightRed => 0x3C, + Color::BrightMagenta => 0x3D, + Color::BrightYellow => 0x3E, + Color::BrightWhite => 0x3F, + } + } +} + +#[test_case] +fn test_color() { + assert_eq!(Color::from_index(6), Color::DarkYellow); + assert_eq!(Color::from_ansi(33), Color::DarkYellow); + assert_eq!(Color::DarkYellow.register(), 0x14); +} diff --git a/src/sys/vga/font.rs b/src/sys/vga/font.rs new file mode 100644 index 000000000..64895878d --- /dev/null +++ b/src/sys/vga/font.rs @@ -0,0 +1,56 @@ +use super::writer::WRITER; + +use crate::api::font::Font; +use crate::api::fs::{FileIO, IO}; + +use core::convert::TryFrom; +use spin::Mutex; +use x86_64::instructions::interrupts; + +static FONT: Mutex> = Mutex::new(None); + +#[derive(Debug, Clone)] +pub struct VgaFont; + +impl VgaFont { + pub fn new() -> Self { + Self + } +} + +impl FileIO for VgaFont { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(()) // TODO + } + + fn write(&mut self, buf: &[u8]) -> Result { + if let Ok(font) = Font::try_from(buf) { + *FONT.lock() = Some(font.clone()); + write_font(&font); + Ok(buf.len()) // TODO: Use font.data.len() ? + } else { + Err(()) + } + } + + fn close(&mut self) {} + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => false, // TODO + IO::Write => true, + } + } +} + +fn write_font(font: &Font) { + interrupts::without_interrupts(|| + WRITER.lock().set_font(font) + ) +} + +pub fn restore_font() { + if let Some(ref font) = *FONT.lock() { + write_font(font); + } +} diff --git a/src/sys/vga/mod.rs b/src/sys/vga/mod.rs new file mode 100644 index 000000000..59d3dae39 --- /dev/null +++ b/src/sys/vga/mod.rs @@ -0,0 +1,141 @@ +mod color; +mod font; +mod buffer; +mod palette; +mod screen; +mod writer; + +pub use font::VgaFont; +pub use screen::VgaMode; +pub use palette::Palette as VgaPalette; +pub use buffer::Buffer as VgaBuffer; + +use color::Color; +use palette::Palette; +use writer::WRITER; + +use alloc::string::String; +use bit_field::BitField; +use core::cmp; +use core::fmt; +use core::fmt::Write; +use core::num::ParseIntError; +use x86_64::instructions::interrupts; +use x86_64::instructions::port::Port; + +const ATTR_ADDR_REG: u16 = 0x3C0; +const ATTR_WRITE_REG: u16 = 0x3C0; +const ATTR_READ_REG: u16 = 0x3C1; +const MISC_WRITE_REG: u16 = 0x3C2; +const SEQUENCER_ADDR_REG: u16 = 0x3C4; +const SEQUENCER_DATA_REG: u16 = 0x3C5; +const DAC_ADDR_READ_MODE_REG: u16 = 0x3C7; +const DAC_ADDR_WRITE_MODE_REG: u16 = 0x3C8; +const DAC_DATA_REG: u16 = 0x3C9; +const GRAPHICS_ADDR_REG: u16 = 0x3CE; +const GRAPHICS_DATA_REG: u16 = 0x3CF; +const CRTC_ADDR_REG: u16 = 0x3D4; +const CRTC_DATA_REG: u16 = 0x3D5; +const INPUT_STATUS_REG: u16 = 0x3DA; +const INSTAT_READ_REG: u16 = 0x3DA; + +#[doc(hidden)] +pub fn print_fmt(args: fmt::Arguments) { + interrupts::without_interrupts(|| + WRITER.lock().write_fmt(args).expect("Could not print to VGA") + ) +} + +// ASCII Printable +// Backspace +// New Line +// Carriage Return +// Extended ASCII Printable +pub fn is_printable(c: u8) -> bool { + matches!(c, 0x20..=0x7E | 0x08 | 0x0A | 0x0D | 0x80..=0xFF) +} + +// 0x00 -> top +// 0x0F -> bottom +// 0x1F -> max (invisible) +fn set_underline_location(location: u8) { + interrupts::without_interrupts(|| { + let mut addr: Port = Port::new(CRTC_ADDR_REG); + let mut data: Port = Port::new(CRTC_DATA_REG); + unsafe { + addr.write(0x14); // Underline Location Register + data.write(location); + } + }) +} + +fn disable_underline() { + set_underline_location(0x1F); +} + +fn disable_blinking() { + interrupts::without_interrupts(|| { + let reg = 0x10; // Attribute Mode Control Register + let mut attr = get_attr_ctrl_reg(reg); + attr.set_bit(3, false); // Clear "Blinking Enable" bit + set_attr_ctrl_reg(reg, attr); + }) +} + +fn set_attr_ctrl_reg(index: u8, value: u8) { + interrupts::without_interrupts(|| { + let mut isr: Port = Port::new(INPUT_STATUS_REG); + let mut addr: Port = Port::new(ATTR_ADDR_REG); + unsafe { + isr.read(); // Reset to address mode + let tmp = addr.read(); + addr.write(index); + addr.write(value); + addr.write(tmp); + } + }) +} + +fn get_attr_ctrl_reg(index: u8) -> u8 { + interrupts::without_interrupts(|| { + let mut isr: Port = Port::new(INPUT_STATUS_REG); + let mut addr: Port = Port::new(ATTR_ADDR_REG); + let mut data: Port = Port::new(ATTR_READ_REG); + let index = index | 0x20; // Set "Palette Address Source" bit + unsafe { + isr.read(); // Reset to address mode + let tmp = addr.read(); + addr.write(index); + let res = data.read(); + addr.write(tmp); + res + } + }) +} + +pub fn init() { + // Map palette registers to color registers + set_attr_ctrl_reg(0x0, 0x00); + set_attr_ctrl_reg(0x1, 0x01); + set_attr_ctrl_reg(0x2, 0x02); + set_attr_ctrl_reg(0x3, 0x03); + set_attr_ctrl_reg(0x4, 0x04); + set_attr_ctrl_reg(0x5, 0x05); + set_attr_ctrl_reg(0x6, 0x14); + set_attr_ctrl_reg(0x7, 0x07); + set_attr_ctrl_reg(0x8, 0x38); + set_attr_ctrl_reg(0x9, 0x39); + set_attr_ctrl_reg(0xA, 0x3A); + set_attr_ctrl_reg(0xB, 0x3B); + set_attr_ctrl_reg(0xC, 0x3C); + set_attr_ctrl_reg(0xD, 0x3D); + set_attr_ctrl_reg(0xE, 0x3E); + set_attr_ctrl_reg(0xF, 0x3F); + + Palette::default().write(); + + disable_blinking(); + disable_underline(); + + WRITER.lock().clear_screen(); +} diff --git a/src/sys/vga/palette.rs b/src/sys/vga/palette.rs new file mode 100644 index 000000000..1df106d1e --- /dev/null +++ b/src/sys/vga/palette.rs @@ -0,0 +1,139 @@ +use super::*; + +use crate::api::fs::{FileIO, IO}; + +use core::convert::TryFrom; +use spin::Mutex; + +static PALETTE: Mutex> = Mutex::new(None); + +const DEFAULT_COLORS: [(u8, u8, u8); 16] = [ + (0x00, 0x00, 0x00), // DarkBlack + (0x00, 0x00, 0x80), // DarkBlue + (0x00, 0x80, 0x00), // DarkGreen + (0x00, 0x80, 0x80), // DarkCyan + (0x80, 0x00, 0x00), // DarkRed + (0x80, 0x00, 0x80), // DarkMagenta + (0x80, 0x80, 0x00), // DarkYellow + (0xC0, 0xC0, 0xC0), // DarkWhite + (0x80, 0x80, 0x80), // BrightBlack + (0x00, 0x00, 0xFF), // BrightBlue + (0x00, 0xFF, 0x00), // BrightGreen + (0x00, 0xFF, 0xFF), // BrightCyan + (0xFF, 0x00, 0x00), // BrightRed + (0xFF, 0x00, 0xFF), // BrightMagenta + (0xFF, 0xFF, 0x00), // BrightYellow + (0xFF, 0xFF, 0xFF), // BrightWhite +]; + +#[derive(Debug, Clone)] +pub struct Palette { + pub colors: [(u8, u8, u8); 256], +} + +impl Palette { + pub fn new() -> Self { + Self { colors: [(0, 0, 0); 256] } + } + + pub fn default() -> Self { + let mut palette = Palette::new(); + for (i, (r, g, b)) in DEFAULT_COLORS.iter().enumerate() { + let i = Color::from_index(i).register(); + palette.colors[i] = (*r, *g, *b); + } + palette + } + + pub fn read() -> Self { + let mut palette = Palette::new(); + for i in 0..256 { + palette.colors[i] = read_palette(i); + } + palette + } + + pub fn write(&self) { + for (i, (r, g, b)) in self.colors.iter().enumerate() { + write_palette(i, *r, *g, *b); + } + } + + pub fn to_bytes(&self) -> [u8; 256 * 3] { + let mut buf = [0; 256 * 3]; + for (i, (r, g, b)) in self.colors.iter().enumerate() { + buf[i * 3 + 0] = *r; + buf[i * 3 + 1] = *g; + buf[i * 3 + 2] = *b; + } + buf + } + + pub fn size() -> usize { + 256 * 3 + } +} + +impl TryFrom<&[u8]> for Palette { + type Error = (); + + fn try_from(buf: &[u8]) -> Result { + if buf.len() != Palette::size() { + return Err(()); + } + let mut colors = [(0, 0, 0); 256]; + for (i, rgb) in buf.chunks(3).enumerate() { + colors[i] = (rgb[0], rgb[1], rgb[2]) + } + + Ok(Palette { colors }) + } +} + +impl FileIO for VgaPalette { + fn read(&mut self, buf: &mut [u8]) -> Result { + let res = Palette::read().to_bytes(); + if buf.len() < res.len() { + return Err(()); + } + buf.clone_from_slice(&res); + Ok(res.len()) + } + + fn write(&mut self, buf: &[u8]) -> Result { + let palette = Palette::try_from(buf)?; + palette.write(); + Ok(buf.len()) + } + + fn close(&mut self) {} + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => true, + } + } +} + +fn write_palette(i: usize, r: u8, g: u8, b: u8) { + interrupts::without_interrupts(|| + WRITER.lock().set_palette(i, r, g, b) + ) +} + +fn read_palette(i: usize) -> (u8, u8, u8) { + interrupts::without_interrupts(|| + WRITER.lock().palette(i) + ) +} + +pub fn restore_palette() { + if let Some(ref palette) = *PALETTE.lock() { + palette.write(); + } +} + +pub fn backup_palette() { + *PALETTE.lock() = Some(Palette::read()) +} diff --git a/src/sys/vga/screen.rs b/src/sys/vga/screen.rs new file mode 100644 index 000000000..023eed090 --- /dev/null +++ b/src/sys/vga/screen.rs @@ -0,0 +1,241 @@ +use super::*; + +use buffer::Buffer; + +use crate::api::fs::{FileIO, IO}; + +use spin::Mutex; + +#[derive(Copy, Clone)] +enum ModeName { + T80x25, + G320x200x256, + G640x480x16, +} + +const BUFFER: [u8; 640 * 480] = [0; 640 * 480]; + +static MODE: Mutex> = Mutex::new(None); + +// Source: https://www.singlix.com/trdos/archive/vga/Graphics%20in%20pmode.pdf +const T_80_25: [u8; 61] = [ + // MISC + 0x67, + // SEQ + 0x03, 0x00, 0x03, 0x00, 0x02, + // CRTC + 0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F, 0x00, 0x4F, 0x0D, 0x0E, + 0x00, 0x00, 0x00, 0x50, 0x9C, 0x0E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3, + 0xFF, + // GC + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00, 0xFF, + // AC + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3F, 0x0C, 0x00, 0x0F, 0x08, 0x00 +]; + +const G_320_200_256: [u8; 61] = [ + // MISC + 0x63, + // SEQ + 0x03, 0x01, 0x0F, 0x00, 0x0E, + // CRTC + 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0x41, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3, + 0xFF, + // GC + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF, + // AC + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x41, 0x00, 0x0F, 0x00, 0x00 +]; + +const G_640_480_16: [u8; 61] = [ + // MISC + 0xE3, + // SEQ + 0x03, 0x01, 0x08, 0x00, 0x06, + // CRTC + 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xEA, 0x0C, 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3, + 0xFF, + // GC + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0F, 0xFF, + // AC + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3F, 0x01, 0x00, 0x0F, 0x00, 0x00 +]; + +const SEQ_REGS_COUNT: usize = 5; +const CRTC_REGS_COUNT: usize = 25; +const GC_REGS_COUNT: usize = 9; +const AC_REGS_COUNT: usize = 21; + +// Source: https://www.singlix.com/trdos/archive/vga/Graphics%20in%20pmode.pdf +fn set_mode(mode: ModeName) { + *MODE.lock() = Some(mode); + let mut regs = match mode { + ModeName::T80x25 => T_80_25, + ModeName::G320x200x256 => G_320_200_256, + ModeName::G640x480x16 => G_640_480_16, + }.to_vec(); + + interrupts::without_interrupts(|| { + let mut misc_write: Port = Port::new(MISC_WRITE_REG); + let mut crtc_addr: Port = Port::new(CRTC_ADDR_REG); + let mut crtc_data: Port = Port::new(CRTC_DATA_REG); + let mut seq_addr: Port = Port::new(SEQUENCER_ADDR_REG); + let mut seq_data: Port = Port::new(SEQUENCER_DATA_REG); + let mut gc_addr: Port = Port::new(GRAPHICS_ADDR_REG); + let mut gc_data: Port = Port::new(GRAPHICS_DATA_REG); + let mut ac_addr: Port = Port::new(ATTR_ADDR_REG); + let mut ac_write: Port = Port::new(ATTR_WRITE_REG); + let mut instat_read: Port = Port::new(INSTAT_READ_REG); + + let mut i = 0; + + unsafe { + misc_write.write(regs[i]); + i += 1; + + for j in 0..SEQ_REGS_COUNT { + seq_addr.write(j as u8); + seq_data.write(regs[i]); + i += 1; + } + + // Unlock CRTC regs + crtc_addr.write(0x03); + let data = crtc_data.read(); + crtc_data.write(data | 0x80); + crtc_addr.write(0x11); + let data = crtc_data.read(); + crtc_data.write(data & !0x80); + + // Keep them unlocked + regs[0x03] |= 0x80; + regs[0x11] &= !0x80; + + for j in 0..CRTC_REGS_COUNT { + crtc_addr.write(j as u8); + crtc_data.write(regs[i]); + i += 1; + } + + for j in 0..GC_REGS_COUNT { + gc_addr.write(j as u8); + gc_data.write(regs[i]); + i += 1; + } + + for j in 0..AC_REGS_COUNT { + instat_read.read(); + ac_addr.write(j as u8); + ac_write.write(regs[i]); + i += 1; + } + + // Lock 16-color palette and unblank display + instat_read.read(); + ac_addr.write(0x20); + } + }); +} + +fn is_80x25_mode() -> bool { + match *MODE.lock() { + Some(ModeName::T80x25) | None => true, + _ => false + } +} + +fn set_80x25_mode() { + clear_screen(); + set_mode(ModeName::T80x25); + disable_blinking(); + disable_underline(); + palette::restore_palette(); + font::restore_font(); +} + +fn set_320x200_mode() { + if is_80x25_mode() { + palette::backup_palette(); + } + set_mode(ModeName::G320x200x256); + clear_screen(); +} + +fn set_640x480_mode() { + if is_80x25_mode() { + palette::backup_palette(); + } + set_mode(ModeName::G640x480x16); + clear_screen(); +} + +fn clear_screen() { + // Clear screen + let size = match *MODE.lock() { + Some(ModeName::G320x200x256) => 320 * 200, + Some(ModeName::G640x480x16) => 640 * 480, + _ => return, + }; + let src = BUFFER.as_ptr(); + let dst = Buffer::addr() as *mut u8; + unsafe { + core::ptr::copy_nonoverlapping(src, dst, size); + } +} + +#[derive(Debug, Clone)] +pub struct VgaMode; + +impl VgaMode { + pub fn new() -> Self { + Self + } + + pub fn size() -> usize { + 16 // Must be larger than 8 bytes to be readable as a block device + } +} + +impl FileIO for VgaMode { + fn read(&mut self, buf: &mut [u8]) -> Result { + match *MODE.lock() { + Some(ModeName::T80x25) | None => write_mode(buf, b"80x25"), + Some(ModeName::G320x200x256) => write_mode(buf, b"320x200"), + Some(ModeName::G640x480x16) => write_mode(buf, b"640x480"), + } + } + + fn write(&mut self, buf: &[u8]) -> Result { + match buf { + b"80x25" => set_80x25_mode(), + b"320x200" => set_320x200_mode(), + b"640x480" => set_640x480_mode(), + _ => return Err(()), + } + Ok(buf.len()) + } + + fn close(&mut self) {} + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => true, + } + } +} + +fn write_mode(buf: &mut [u8], mode: &[u8]) -> Result { + let n = mode.len(); + if buf.len() < n { + Err(()) + } else { + buf[0..n].clone_from_slice(mode); + Ok(n) + } +} diff --git a/src/sys/vga.rs b/src/sys/vga/writer.rs similarity index 63% rename from src/sys/vga.rs rename to src/sys/vga/writer.rs index 4d5c8bae8..727b6518c 100644 --- a/src/sys/vga.rs +++ b/src/sys/vga/writer.rs @@ -1,169 +1,14 @@ +use super::*; + +use buffer::Buffer; + use crate::api::font::Font; -use crate::api::fs::{FileIO, IO}; -use crate::api::vga::color; -use crate::api::vga::{Color, Palette}; use crate::sys; -use alloc::string::String; -use bit_field::BitField; -use core::convert::TryFrom; -use core::cmp; -use core::fmt; -use core::fmt::Write; -use core::num::ParseIntError; +//use core::fmt::Write; use lazy_static::lazy_static; use spin::Mutex; use vte::{Params, Parser, Perform}; -use x86_64::instructions::interrupts; -use x86_64::instructions::port::Port; - -const ATTR_ADDR_REG: u16 = 0x3C0; -const ATTR_WRITE_REG: u16 = 0x3C0; -const ATTR_READ_REG: u16 = 0x3C1; -const MISC_WRITE_REG: u16 = 0x3C2; -const SEQUENCER_ADDR_REG: u16 = 0x3C4; -const SEQUENCER_DATA_REG: u16 = 0x3C5; -const DAC_ADDR_WRITE_MODE_REG: u16 = 0x3C8; -const DAC_DATA_REG: u16 = 0x3C9; -const GRAPHICS_ADDR_REG: u16 = 0x3CE; -const GRAPHICS_DATA_REG: u16 = 0x3CF; -const CRTC_ADDR_REG: u16 = 0x3D4; -const CRTC_DATA_REG: u16 = 0x3D5; -const INPUT_STATUS_REG: u16 = 0x3DA; -const INSTAT_READ_REG: u16 = 0x3DA; - -// Source: https://www.singlix.com/trdos/archive/vga/Graphics%20in%20pmode.pdf -const T_80_25: [u8; 61] = [ - // MISC - 0x67, - // SEQ - 0x03, 0x00, 0x03, 0x00, 0x02, - // CRTC - 0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F, 0x00, 0x4F, 0x0D, 0x0E, - 0x00, 0x00, 0x00, 0x50, 0x9C, 0x0E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3, - 0xFF, - // GC - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00, 0xFF, - // AC - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, - 0x3C, 0x3D, 0x3E, 0x3F, 0x0C, 0x00, 0x0F, 0x08, 0x00 -]; - -const G_320_200_256: [u8; 61] = [ - // MISC - 0x63, - // SEQ - 0x03, 0x01, 0x0F, 0x00, 0x0E, - // CRTC - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0x41, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3, - 0xFF, - // GC - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF, - // AC - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - 0x0C, 0x0D, 0x0E, 0x0F, 0x41, 0x00, 0x0F, 0x00, 0x00 -]; - -const G_640_480_16: [u8; 61] = [ - // MISC - 0xE3, - // SEQ - 0x03, 0x01, 0x08, 0x00, 0x06, - // CRTC - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xEA, 0x0C, 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3, - 0xFF, - // GC - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0F, 0xFF, - // AC - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, - 0x3C, 0x3D, 0x3E, 0x3F, 0x01, 0x00, 0x0F, 0x00, 0x00 -]; - -const SEQ_REGS_COUNT: usize = 5; -const CRTC_REGS_COUNT: usize = 25; -const GC_REGS_COUNT: usize = 9; -const AC_REGS_COUNT: usize = 21; - -pub fn set_80x25_mode() { - set_mode(&T_80_25); - disable_blinking(); - disable_underline(); -} - -pub fn set_320x200_mode() { - set_mode(&G_320_200_256); -} - -pub fn set_640x480_mode() { - set_mode(&G_640_480_16); -} - -// Source: https://www.singlix.com/trdos/archive/vga/Graphics%20in%20pmode.pdf -fn set_mode(regs: &[u8]) { - interrupts::without_interrupts(|| { - let mut misc_write: Port = Port::new(MISC_WRITE_REG); - let mut crtc_addr: Port = Port::new(CRTC_ADDR_REG); - let mut crtc_data: Port = Port::new(CRTC_DATA_REG); - let mut seq_addr: Port = Port::new(SEQUENCER_ADDR_REG); - let mut seq_data: Port = Port::new(SEQUENCER_DATA_REG); - let mut gc_addr: Port = Port::new(GRAPHICS_ADDR_REG); - let mut gc_data: Port = Port::new(GRAPHICS_DATA_REG); - let mut ac_addr: Port = Port::new(ATTR_ADDR_REG); - let mut ac_write: Port = Port::new(ATTR_WRITE_REG); - let mut instat_read: Port = Port::new(INSTAT_READ_REG); - - let mut regs = regs.to_vec(); - let mut i = 0; - - unsafe { - misc_write.write(regs[i]); - i += 1; - - for j in 0..SEQ_REGS_COUNT { - seq_addr.write(j as u8); - seq_data.write(regs[i]); - i += 1; - } - - // Unlock CRTC regs - crtc_addr.write(0x03); - let data = crtc_data.read(); - crtc_data.write(data | 0x80); - crtc_addr.write(0x11); - let data = crtc_data.read(); - crtc_data.write(data & !0x80); - - // Keep them unlocked - regs[0x03] |= 0x80; - regs[0x11] &= !0x80; - - for j in 0..CRTC_REGS_COUNT { - crtc_addr.write(j as u8); - crtc_data.write(regs[i]); - i += 1; - } - - for j in 0..GC_REGS_COUNT { - gc_addr.write(j as u8); - gc_data.write(regs[i]); - i += 1; - } - - for j in 0..AC_REGS_COUNT { - instat_read.read(); - ac_addr.write(j as u8); - ac_write.write(regs[i]); - i += 1; - } - - // Lock 16-color palette and unblank display - instat_read.read(); - ac_addr.write(0x20); - } - }); -} const FG: Color = Color::DarkWhite; const BG: Color = Color::DarkBlack; @@ -410,7 +255,7 @@ impl Writer { self.scroll_buffer[y + dy][x..SCREEN_WIDTH].fill(c); } - fn clear_screen(&mut self) { + pub fn clear_screen(&mut self) { self.scroll_reader = 0; self.scroll_bottom = SCREEN_HEIGHT; for y in 0..SCREEN_HEIGHT { @@ -418,22 +263,15 @@ impl Writer { } } - pub fn set_color(&mut self, foreground: Color, background: Color) { + fn set_color(&mut self, foreground: Color, background: Color) { self.color_code = ColorCode::new(foreground, background); } - pub fn color(&self) -> (Color, Color) { - let cc = self.color_code.0; - let fg = color::from_index(cc.get_bits(0..4) as usize); - let bg = color::from_index(cc.get_bits(4..8) as usize); - (fg, bg) - } - // Source: https://slideplayer.com/slide/3888880 pub fn set_font(&mut self, font: &Font) { let mut sequencer: Port = Port::new(SEQUENCER_ADDR_REG); let mut graphics: Port = Port::new(GRAPHICS_ADDR_REG); - let buffer = 0xA0000 as *mut u8; + let buffer = Buffer::addr() as *mut u8; unsafe { sequencer.write(0x0100); // do a sync reset @@ -468,9 +306,21 @@ impl Writer { let mut data: Port = Port::new(DAC_DATA_REG); unsafe { addr.write(i as u8); - data.write(vga_color(r)); - data.write(vga_color(g)); - data.write(vga_color(b)); + data.write(r >> 2); // Convert 8-bit to 6-bit color + data.write(g >> 2); + data.write(b >> 2); + } + } + + pub fn palette(&mut self, i: usize) -> (u8, u8, u8) { + let mut addr: Port = Port::new(DAC_ADDR_READ_MODE_REG); + let mut data: Port = Port::new(DAC_DATA_REG); + unsafe { + addr.write(i as u8); + let r = data.read() << 2; // Convert 6-bit to 8-bit color + let g = data.read() << 2; + let b = data.read() << 2; + (r, g, b) } } @@ -509,23 +359,6 @@ impl Writer { } } -// Convert 8-bit to 6-bit color -fn vga_color(color: u8) -> u8 { - color >> 2 -} - -fn parse_palette(palette: &str) -> Result<(usize, u8, u8, u8), ParseIntError> { - debug_assert!(palette.len() == 8); - debug_assert!(palette.starts_with('P')); - - let i = usize::from_str_radix(&palette[1..2], 16)?; - let r = u8::from_str_radix(&palette[2..4], 16)?; - let g = u8::from_str_radix(&palette[4..6], 16)?; - let b = u8::from_str_radix(&palette[6..8], 16)?; - - Ok((i, r, g, b)) -} - /// Source: https://vt100.net/emu/dec_ansi_parser impl Perform for Writer { fn print(&mut self, c: char) { @@ -548,10 +381,10 @@ impl Perform for Writer { bg = BG; } 30..=37 | 90..=97 => { - fg = color::from_ansi(param[0] as u8); + fg = Color::from_ansi(param[0] as u8); } 40..=47 | 100..=107 => { - bg = color::from_ansi((param[0] as u8) - 10); + bg = Color::from_ansi((param[0] as u8) - 10); } _ => {} } @@ -685,14 +518,13 @@ impl Perform for Writer { match s.chars().next() { Some('P') if s.len() == 8 => { if let Ok((i, r, g, b)) = parse_palette(&s) { - let i = color::from_index(i).to_vga_reg() as usize; + let i = Color::from_index(i).register(); self.set_palette(i, r, g, b); } } Some('R') if s.len() == 1 => { let palette = Palette::default(); for (i, (r, g, b)) in palette.colors.iter().enumerate() { - let i = color::from_index(i).to_vga_reg() as usize; self.set_palette(i, *r, *g, *b); } } @@ -714,172 +546,16 @@ impl fmt::Write for Writer { } } -#[derive(Debug, Clone)] -pub struct VgaFont; - -impl VgaFont { - pub fn new() -> Self { - Self - } -} - -impl FileIO for VgaFont { - fn read(&mut self, _buf: &mut [u8]) -> Result { - Err(()) // TODO - } - - fn write(&mut self, buf: &[u8]) -> Result { - if let Ok(font) = Font::try_from(buf) { - set_font(&font); - Ok(buf.len()) // TODO: Use font.data.len() ? - } else { - Err(()) - } - } - - fn close(&mut self) {} - - fn poll(&mut self, event: IO) -> bool { - match event { - IO::Read => false, // TODO - IO::Write => true, - } - } -} - -#[doc(hidden)] -pub fn print_fmt(args: fmt::Arguments) { - interrupts::without_interrupts(|| - WRITER.lock().write_fmt(args).expect("Could not print to VGA") - ) -} - -pub fn color() -> (Color, Color) { - interrupts::without_interrupts(|| - WRITER.lock().color() - ) -} - -pub fn set_color(foreground: Color, background: Color) { - interrupts::without_interrupts(|| - WRITER.lock().set_color(foreground, background) - ) -} - -// ASCII Printable -// Backspace -// New Line -// Carriage Return -// Extended ASCII Printable -pub fn is_printable(c: u8) -> bool { - matches!(c, 0x20..=0x7E | 0x08 | 0x0A | 0x0D | 0x80..=0xFF) -} - -// TODO: Remove this -pub fn set_font(font: &Font) { - interrupts::without_interrupts(|| - WRITER.lock().set_font(font) - ) -} - -// TODO: Remove this -pub fn set_palette(palette: Palette) { - interrupts::without_interrupts(|| - for (i, (r, g, b)) in palette.colors.iter().enumerate() { - let i = color::from_index(i).to_vga_reg() as usize; - WRITER.lock().set_palette(i, *r, *g, *b) - } - ) -} -pub fn set_palette_color(i: usize, r: u8, g: u8, b: u8) { - interrupts::without_interrupts(|| - WRITER.lock().set_palette(i, r, g, b) - ) -} - -// 0x00 -> top -// 0x0F -> bottom -// 0x1F -> max (invisible) -fn set_underline_location(location: u8) { - interrupts::without_interrupts(|| { - let mut addr: Port = Port::new(CRTC_ADDR_REG); - let mut data: Port = Port::new(CRTC_DATA_REG); - unsafe { - addr.write(0x14); // Underline Location Register - data.write(location); - } - }) -} - -fn disable_underline() { - set_underline_location(0x1F); -} - -fn disable_blinking() { - interrupts::without_interrupts(|| { - let reg = 0x10; // Attribute Mode Control Register - let mut attr = get_attr_ctrl_reg(reg); - attr.set_bit(3, false); // Clear "Blinking Enable" bit - set_attr_ctrl_reg(reg, attr); - }) -} - -fn set_attr_ctrl_reg(index: u8, value: u8) { - interrupts::without_interrupts(|| { - let mut isr: Port = Port::new(INPUT_STATUS_REG); - let mut addr: Port = Port::new(ATTR_ADDR_REG); - unsafe { - isr.read(); // Reset to address mode - let tmp = addr.read(); - addr.write(index); - addr.write(value); - addr.write(tmp); - } - }) -} +fn parse_palette(palette: &str) -> Result<(usize, u8, u8, u8), ParseIntError> { + debug_assert!(palette.len() == 8); + debug_assert!(palette.starts_with('P')); -fn get_attr_ctrl_reg(index: u8) -> u8 { - interrupts::without_interrupts(|| { - let mut isr: Port = Port::new(INPUT_STATUS_REG); - let mut addr: Port = Port::new(ATTR_ADDR_REG); - let mut data: Port = Port::new(ATTR_READ_REG); - let index = index | 0x20; // Set "Palette Address Source" bit - unsafe { - isr.read(); // Reset to address mode - let tmp = addr.read(); - addr.write(index); - let res = data.read(); - addr.write(tmp); - res - } - }) -} + let i = usize::from_str_radix(&palette[1..2], 16)?; + let r = u8::from_str_radix(&palette[2..4], 16)?; + let g = u8::from_str_radix(&palette[4..6], 16)?; + let b = u8::from_str_radix(&palette[6..8], 16)?; -pub fn init() { - // Map palette registers to color registers - set_attr_ctrl_reg(0x0, 0x00); - set_attr_ctrl_reg(0x1, 0x01); - set_attr_ctrl_reg(0x2, 0x02); - set_attr_ctrl_reg(0x3, 0x03); - set_attr_ctrl_reg(0x4, 0x04); - set_attr_ctrl_reg(0x5, 0x05); - set_attr_ctrl_reg(0x6, 0x14); - set_attr_ctrl_reg(0x7, 0x07); - set_attr_ctrl_reg(0x8, 0x38); - set_attr_ctrl_reg(0x9, 0x39); - set_attr_ctrl_reg(0xA, 0x3A); - set_attr_ctrl_reg(0xB, 0x3B); - set_attr_ctrl_reg(0xC, 0x3C); - set_attr_ctrl_reg(0xD, 0x3D); - set_attr_ctrl_reg(0xE, 0x3E); - set_attr_ctrl_reg(0xF, 0x3F); - - set_palette(Palette::default()); - - disable_blinking(); - disable_underline(); - - WRITER.lock().clear_screen(); + Ok((i, r, g, b)) } #[test_case] diff --git a/src/usr/install.rs b/src/usr/install.rs index 3b3555a4c..47c92ce8d 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -55,7 +55,10 @@ pub fn copy_files(verbose: bool) { create_dev("/dev/console", "console", verbose); create_dev("/dev/net/tcp", "tcp", verbose); create_dev("/dev/net/udp", "udp", verbose); - create_dev("/dev/vga/font", "font", verbose); + create_dev("/dev/vga/buffer", "vga-buffer", verbose); + create_dev("/dev/vga/font", "vga-font", verbose); + create_dev("/dev/vga/mode", "vga-mode", verbose); + create_dev("/dev/vga/palette", "vga-palette", verbose); copy_file!("/ini/banner.txt", verbose); copy_file!("/ini/boot.sh", verbose); diff --git a/src/usr/mod.rs b/src/usr/mod.rs index ed98d2cbb..cba745ec6 100644 --- a/src/usr/mod.rs +++ b/src/usr/mod.rs @@ -38,6 +38,5 @@ pub mod socket; pub mod tcp; pub mod time; pub mod user; -pub mod vga; pub mod view; pub mod write; diff --git a/src/usr/render.rs b/src/usr/render.rs index 6e1147c91..cc295afe4 100644 --- a/src/usr/render.rs +++ b/src/usr/render.rs @@ -2,14 +2,12 @@ use crate::api::console::Style; use crate::api::fs; use crate::api::io; use crate::api::process::ExitCode; -use crate::usr::shell; -use crate::sys; +use crate::api::vga; use alloc::vec::Vec; use alloc::string::{String, ToString}; use core::mem::size_of; -const FRAMEBUFFER: usize = 0xA0000; const WIDTH: usize = 320; const HEIGHT: usize = 200; @@ -76,7 +74,7 @@ fn parse_bmp(data: &[u8]) -> Result { let pixels = data[pixels_offset..].to_vec(); let width = dib_header.width as u32; - let height = dib_header.height.abs() as u32; + let height = dib_header.height.unsigned_abs(); if pixels.len() != (width * height) as usize { return Err("Invalid BMP file: wrong pixels count".to_string()); } @@ -84,15 +82,6 @@ fn parse_bmp(data: &[u8]) -> Result { Ok(BmpInfo { width, height, palette, pixels }) } -fn clear() { - let ptr = FRAMEBUFFER as *mut u8; - let size = WIDTH * HEIGHT; - unsafe { - let buf = core::slice::from_raw_parts_mut(ptr, size); - buf.fill(0x00); - } -} - fn help() { let csi_option = Style::color("aqua"); let csi_title = Style::color("yellow"); @@ -127,38 +116,21 @@ impl Config { pub fn text_mode(&mut self) { if self.mode == Mode::Graphic { - text_mode(); + vga::text_mode(); self.mode = Mode::Text; } } pub fn graphic_mode(&mut self) { if self.mode == Mode::Text { - graphic_mode(); + vga::graphic_mode(); self.mode = Mode::Graphic; } } } -fn graphic_mode() { - // TODO: Backup font and palette - sys::vga::set_320x200_mode(); - clear(); -} - -fn text_mode() { - clear(); - sys::vga::set_80x25_mode(); - - // TODO: Restore font and palette backup instead of this - shell::exec("shell /ini/palettes/gruvbox-dark.sh").ok(); - shell::exec("read /ini/fonts/zap-light-8x16.psf => /dev/vga/font").ok(); - - print!("\x1b[2J\x1b[1;1H"); // Clear screen and move to top -} - fn render_bmp(path: &str, config: &mut Config) -> Result { - if let Ok(buf) = fs::read_to_bytes(&path) { + if let Ok(buf) = fs::read_to_bytes(path) { if let Ok(bmp) = parse_bmp(&buf) { let width = bmp.width as usize; let height = bmp.height as usize; @@ -178,7 +150,7 @@ fn render_bmp(path: &str, config: &mut Config) -> Result { // BMP stores images bottom-up let bmp_y = height - 1 - y; - let i = (bmp_y * (width + row_padding) + x) as usize; + let i = bmp_y * (width + row_padding) + x; img.push(bmp.pixels[i]); } } @@ -186,15 +158,25 @@ fn render_bmp(path: &str, config: &mut Config) -> Result { config.graphic_mode(); // Load palette + let mut palette = [0; 256 * 3]; for (i, (r, g, b)) in bmp.palette.iter().enumerate() { - sys::vga::set_palette_color(i, *r, *g, *b); + palette[i * 3 + 0] = *r; + palette[i * 3 + 1] = *g; + palette[i * 3 + 2] = *b; + } + let dev = "/dev/vga/palette"; + if !fs::is_device(dev) || fs::write(dev, &palette).is_err() { + config.text_mode(); + error!("Could not write to '{}'", dev); + return Err(ExitCode::Failure); } // Display image - let src = img.as_ptr(); - let dst = FRAMEBUFFER as *mut u8; - unsafe { - core::ptr::copy_nonoverlapping(src, dst, size); + let dev = "/dev/vga/buffer"; + if !fs::is_device(dev) || fs::write(dev, &img).is_err() { + config.text_mode(); + error!("Could not write to '{}'", dev); + return Err(ExitCode::Failure); } Ok(read_command()) @@ -261,7 +243,6 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { return Ok(()); } let files = &args[1..]; - let mut config = Config::new(); let mut i = 0; let n = files.len(); diff --git a/src/usr/shell.rs b/src/usr/shell.rs index 043d97515..9bf27023b 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -14,12 +14,12 @@ use alloc::vec::Vec; use core::sync::atomic::{fence, Ordering}; // TODO: Scan /bin -const AUTOCOMPLETE_COMMANDS: [&str; 41] = [ +const AUTOCOMPLETE_COMMANDS: [&str; 40] = [ "2048", "calc", "chess", "copy", "date", "decode", "delete", "dhcp", "diff", "disk", "edit", "elf", "encode", "env", "goto", "hash", "help", "hex", "host", "http", "httpd", "install", "keyboard", "life", "lisp", "list", "memory", "move", "net", "pci", "quit", "read", "render", "shell", - "socket", "tcp", "time", "user", "vga", "view", "write", + "socket", "tcp", "time", "user", "view", "write", ]; struct Config { @@ -562,7 +562,6 @@ fn dispatch(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { "unset" => cmd_unset(args, config), "version" => cmd_version(), "user" => usr::user::main(args), - "vga" => usr::vga::main(args), "view" => usr::view::main(args), "write" => usr::write::main(args), "panic" => panic!("{}", args[1..].join(" ")), diff --git a/src/usr/vga.rs b/src/usr/vga.rs deleted file mode 100644 index 89d21c3b1..000000000 --- a/src/usr/vga.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::api::console::Style; -use crate::api::fs; -use crate::api::process::ExitCode; -use crate::api::font::Font; -use crate::api::vga::palette; -use crate::sys; - -use core::convert::TryFrom; - -// TODO: Remove this command in the next version of MOROS -pub fn main(args: &[&str]) -> Result<(), ExitCode> { - if args.len() == 1 { - help(); - return Err(ExitCode::UsageError); - } - - match args[1] { - "-h" | "--help" => { - help(); - Ok(()) - } - "set" => { - if args.len() == 4 && args[2] == "font" { - warning!("Use VGA font device"); - if let Ok(buf) = fs::read_to_bytes(args[3]) { - if let Ok(font) = Font::try_from(buf.as_slice()) { - sys::vga::set_font(&font); - Ok(()) - } else { - error!("Could not parse font file"); - Err(ExitCode::Failure) - } - } else { - error!("Could not read font file"); - Err(ExitCode::Failure) - } - } else if args.len() == 4 && args[2] == "palette" { - warning!("Use ANSI OSC palette sequence"); - if let Ok(csv) = fs::read_to_string(args[3]) { - if let Ok(palette) = palette::from_csv(&csv) { - sys::vga::set_palette(palette); - Ok(()) - } else { - error!("Could not parse palette file"); - Err(ExitCode::Failure) - } - } else { - error!("Could not read palette file"); - Err(ExitCode::Failure) - } - } else { - error!("Invalid command"); - Err(ExitCode::Failure) - } - } - _ => { - error!("Invalid command"); - Err(ExitCode::Failure) - } - } -} - -fn help() { - let csi_option = Style::color("aqua"); - let csi_title = Style::color("yellow"); - let csi_reset = Style::reset(); - println!( - "{}Usage:{} vga {}{1}", - csi_title, csi_reset, csi_option - ); - println!(); - println!("{}Commands:{}", csi_title, csi_reset); - println!( - " {}set font {} Set VGA font", - csi_option, csi_reset - ); - println!( - " {}set palette {} Set VGA color palette", - csi_option, csi_reset - ); -}