From ac6b76b4d15f2563c8c3f347878fafc6f99f9e03 Mon Sep 17 00:00:00 2001 From: Ryan Kennedy Date: Sun, 15 Mar 2020 17:30:28 -0500 Subject: [PATCH] Ability to manipulate palettes --- src/colors.rs | 54 ++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/registers.rs | 44 ++++++++++++++++++++++++ src/vga.rs | 25 ++++++++++++-- src/writers/graphics_640x480x16.rs | 10 ++++-- src/writers/text_40x25.rs | 9 +++-- src/writers/text_40x50.rs | 9 +++-- src/writers/text_80x25.rs | 9 +++-- testing/tests/vga.rs | 21 ++++++++++-- 9 files changed, 169 insertions(+), 13 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index 3654018..2757844 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,5 +1,7 @@ //! Common color structures used in vga. +pub const PALETTE_SIZE: usize = 768; + /// Represents a 16 bit color used for vga display. #[derive(Debug, Clone, Copy)] #[repr(u8)] @@ -50,3 +52,55 @@ impl TextModeColor { TextModeColor((background as u8) << 4 | (foreground as u8)) } } + +/// Represents the default vga 256 color palette. +pub const DEFAULT_PALETTE: [u8; PALETTE_SIZE] = [ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2A, 0x0, 0x2A, 0x0, 0x0, 0x2A, 0x2A, 0x2A, 0x0, 0x0, 0x2A, 0x0, + 0x2A, 0x2A, 0x2A, 0x0, 0x2A, 0x2A, 0x2A, 0x0, 0x0, 0x15, 0x0, 0x0, 0x3F, 0x0, 0x2A, 0x15, 0x0, + 0x2A, 0x3F, 0x2A, 0x0, 0x15, 0x2A, 0x0, 0x3F, 0x2A, 0x2A, 0x15, 0x2A, 0x2A, 0x3F, 0x0, 0x15, + 0x0, 0x0, 0x15, 0x2A, 0x0, 0x3F, 0x0, 0x0, 0x3F, 0x2A, 0x2A, 0x15, 0x0, 0x2A, 0x15, 0x2A, 0x2A, + 0x3F, 0x0, 0x2A, 0x3F, 0x2A, 0x0, 0x15, 0x15, 0x0, 0x15, 0x3F, 0x0, 0x3F, 0x15, 0x0, 0x3F, + 0x3F, 0x2A, 0x15, 0x15, 0x2A, 0x15, 0x3F, 0x2A, 0x3F, 0x15, 0x2A, 0x3F, 0x3F, 0x15, 0x0, 0x0, + 0x15, 0x0, 0x2A, 0x15, 0x2A, 0x0, 0x15, 0x2A, 0x2A, 0x3F, 0x0, 0x0, 0x3F, 0x0, 0x2A, 0x3F, + 0x2A, 0x0, 0x3F, 0x2A, 0x2A, 0x15, 0x0, 0x15, 0x15, 0x0, 0x3F, 0x15, 0x2A, 0x15, 0x15, 0x2A, + 0x3F, 0x3F, 0x0, 0x15, 0x3F, 0x0, 0x3F, 0x3F, 0x2A, 0x15, 0x3F, 0x2A, 0x3F, 0x15, 0x15, 0x0, + 0x15, 0x15, 0x2A, 0x15, 0x3F, 0x0, 0x15, 0x3F, 0x2A, 0x3F, 0x15, 0x0, 0x3F, 0x15, 0x2A, 0x3F, + 0x3F, 0x0, 0x3F, 0x3F, 0x2A, 0x15, 0x15, 0x15, 0x15, 0x15, 0x3F, 0x15, 0x3F, 0x15, 0x15, 0x3F, + 0x3F, 0x3F, 0x15, 0x15, 0x3F, 0x15, 0x3F, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, +]; diff --git a/src/lib.rs b/src/lib.rs index 1ced4d9..553a8ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod registers; mod vga; mod writers; +pub use self::colors::{Color16Bit, TextModeColor, DEFAULT_PALETTE, PALETTE_SIZE}; pub use self::configurations::{ VgaConfiguration, MODE_40X25_CONFIGURATION, MODE_40X50_CONFIGURATION, MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION, diff --git a/src/registers.rs b/src/registers.rs index d0c51ef..4aa5a76 100644 --- a/src/registers.rs +++ b/src/registers.rs @@ -1,3 +1,4 @@ +use crate::colors::PALETTE_SIZE; use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; const ST00_READ_ADDRESS: u16 = 0x3C2; @@ -23,6 +24,10 @@ const CRX_INDEX_MDA_ADDRESS: u16 = 0x3B4; const CRX_DATA_CGA_ADDRESS: u16 = 0x3D5; const CRX_DATA_MDA_ADDRESS: u16 = 0x3B5; +const COLOR_PALETTE_DATA_ADDRESS: u16 = 0x3C9; +const COLOR_PALETTE_INDEX_READ_ADDRESS: u16 = 0x3C7; +const COLOR_PALETTE_INDEX_WRITE_ADDRESSS: u16 = 0x3C8; + #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum EmulationMode { @@ -389,3 +394,42 @@ impl CrtcControllerRegisters { } } } + +#[derive(Debug)] +pub struct ColorPaletteRegisters { + data_port: Port, + index_read_port: Port, + index_write_port: Port, +} + +impl ColorPaletteRegisters { + pub fn new() -> ColorPaletteRegisters { + ColorPaletteRegisters { + data_port: Port::new(COLOR_PALETTE_DATA_ADDRESS), + index_read_port: Port::new(COLOR_PALETTE_INDEX_READ_ADDRESS), + index_write_port: Port::new(COLOR_PALETTE_INDEX_WRITE_ADDRESSS), + } + } + + pub fn load_palette(&mut self, palette: &[u8; PALETTE_SIZE]) { + unsafe { + self.index_write_port.write(0); + } + for i in palette.iter() { + unsafe { + self.data_port.write(*i); + } + } + } + + pub fn read_palette(&mut self, palette: &mut [u8; PALETTE_SIZE]) { + unsafe { + self.index_read_port.write(0); + } + for i in 0..PALETTE_SIZE { + unsafe { + palette[i] = self.data_port.read(); + } + } + } +} diff --git a/src/vga.rs b/src/vga.rs index d225626..c986a41 100644 --- a/src/vga.rs +++ b/src/vga.rs @@ -1,15 +1,16 @@ //! Provides access to the vga graphics card. use super::{ + colors::PALETTE_SIZE, configurations::{ VgaConfiguration, MODE_40X25_CONFIGURATION, MODE_40X50_CONFIGURATION, MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION, }, fonts::{VgaFont, TEXT_8X16_FONT, TEXT_8X8_FONT}, registers::{ - AttributeControllerIndex, AttributeControllerRegisters, CrtcControllerIndex, - CrtcControllerRegisters, EmulationMode, GeneralRegisters, GraphicsControllerIndex, - GraphicsControllerRegisters, SequencerIndex, SequencerRegisters, + AttributeControllerIndex, AttributeControllerRegisters, ColorPaletteRegisters, + CrtcControllerIndex, CrtcControllerRegisters, EmulationMode, GeneralRegisters, + GraphicsControllerIndex, GraphicsControllerRegisters, SequencerIndex, SequencerRegisters, }, }; use conquer_once::spin::Lazy; @@ -90,6 +91,7 @@ pub struct Vga { graphics_controller_registers: GraphicsControllerRegisters, attribute_controller_registers: AttributeControllerRegisters, crtc_controller_registers: CrtcControllerRegisters, + color_palette_registers: ColorPaletteRegisters, most_recent_video_mode: Option, } @@ -101,6 +103,7 @@ impl Vga { graphics_controller_registers: GraphicsControllerRegisters::new(), attribute_controller_registers: AttributeControllerRegisters::new(), crtc_controller_registers: CrtcControllerRegisters::new(), + color_palette_registers: ColorPaletteRegisters::new(), most_recent_video_mode: None, } } @@ -172,6 +175,22 @@ impl Vga { EmulationMode::from(self.general_registers.read_msr() & 0x1) } + /// Loads a new palette into the vga, as specified by `palette`. + /// + /// Each palette must be `PALETTE_SIZE` bytes long, with every 3 + /// bytes representing one color `(R, G, B)`. + pub fn load_palette(&mut self, palette: &[u8; PALETTE_SIZE]) { + self.color_palette_registers.load_palette(palette); + } + + /// Reads the current vga palette into `palette`. + /// + /// Each palette must be `PALETTE_SIZE` bytes long, with every 3 + /// bytes representing one color `(R, G, B)`. + pub fn read_palette(&mut self, palette: &mut [u8; PALETTE_SIZE]) { + self.color_palette_registers.read_palette(palette); + } + fn load_font(&mut self, vga_font: &VgaFont) { // Save registers let ( diff --git a/src/writers/graphics_640x480x16.rs b/src/writers/graphics_640x480x16.rs index a9a1d02..e2fc0b4 100644 --- a/src/writers/graphics_640x480x16.rs +++ b/src/writers/graphics_640x480x16.rs @@ -1,5 +1,5 @@ use crate::{ - colors::Color16Bit, + colors::{Color16Bit, DEFAULT_PALETTE}, vga::{Plane, Vga, VideoMode, VGA}, }; use spinning_top::SpinlockGuard; @@ -34,6 +34,7 @@ impl Graphics640x480x16 { /// Clears the screen by setting all pixels to `Color16Bit::Black`. pub fn clear_screen(&self) { + // TODO: Clear the screen by using 4-plane mode instead of slow `set_pixel`. for x in 0..WIDTH { for y in 0..HEIGHT { self.set_pixel(x, y, Color16Bit::Yellow); @@ -72,7 +73,12 @@ impl Graphics640x480x16 { /// Sets the graphics device to `VideoMode::Mode640x480x16`. pub fn set_mode(&self) { - VGA.lock().set_video_mode(VideoMode::Mode640x480x16); + let mut vga = VGA.lock(); + vga.set_video_mode(VideoMode::Mode640x480x16); + + // Some bios mess up the palette when switching modes, + // so explicitly set it. + vga.load_palette(&DEFAULT_PALETTE); } /// Returns the start of the `FrameBuffer` as `*mut u8` as diff --git a/src/writers/text_40x25.rs b/src/writers/text_40x25.rs index 0eebb85..6a5e2bd 100644 --- a/src/writers/text_40x25.rs +++ b/src/writers/text_40x25.rs @@ -1,6 +1,6 @@ use super::ScreenCharacter; use crate::{ - colors::{Color16Bit, TextModeColor}, + colors::{Color16Bit, TextModeColor, DEFAULT_PALETTE}, vga::{Vga, VideoMode, VGA}, }; use spinning_top::SpinlockGuard; @@ -66,7 +66,12 @@ impl Text40x25 { /// Sets the graphics device to `VideoMode::Mode40x25`. pub fn set_mode(&self) { - VGA.lock().set_video_mode(VideoMode::Mode40x25); + let mut vga = VGA.lock(); + vga.set_video_mode(VideoMode::Mode40x25); + + // Some bios mess up the palette when switching modes, + // so explicitly set it. + vga.load_palette(&DEFAULT_PALETTE); } /// Returns the start of the `FrameBuffer` as `*mut ScreenCharacter` diff --git a/src/writers/text_40x50.rs b/src/writers/text_40x50.rs index 3b252d6..6ca3e16 100644 --- a/src/writers/text_40x50.rs +++ b/src/writers/text_40x50.rs @@ -1,6 +1,6 @@ use super::ScreenCharacter; use crate::{ - colors::{Color16Bit, TextModeColor}, + colors::{Color16Bit, TextModeColor, DEFAULT_PALETTE}, vga::{Vga, VideoMode, VGA}, }; use spinning_top::SpinlockGuard; @@ -66,7 +66,12 @@ impl Text40x50 { /// Sets the graphics device to `VideoMode::Mode40x50`. pub fn set_mode(&self) { - VGA.lock().set_video_mode(VideoMode::Mode40x50); + let mut vga = VGA.lock(); + vga.set_video_mode(VideoMode::Mode40x50); + + // Some bios mess up the palette when switching modes, + // so explicitly set it. + vga.load_palette(&DEFAULT_PALETTE); } /// Returns the start of the `FrameBuffer` as `*mut ScreenCharacter` diff --git a/src/writers/text_80x25.rs b/src/writers/text_80x25.rs index d60e035..5026684 100644 --- a/src/writers/text_80x25.rs +++ b/src/writers/text_80x25.rs @@ -1,6 +1,6 @@ use super::ScreenCharacter; use crate::{ - colors::{Color16Bit, TextModeColor}, + colors::{Color16Bit, TextModeColor, DEFAULT_PALETTE}, vga::{Vga, VideoMode, VGA}, }; use spinning_top::SpinlockGuard; @@ -66,7 +66,12 @@ impl Text80x25 { /// Sets the graphics device to `VideoMode::Mode80x25`. pub fn set_mode(&self) { - VGA.lock().set_video_mode(VideoMode::Mode80x25); + let mut vga = VGA.lock(); + vga.set_video_mode(VideoMode::Mode80x25); + + // Some bios mess up the palette when switching modes, + // so explicitly set it. + vga.load_palette(&DEFAULT_PALETTE); } /// Returns the start of the `FrameBuffer` as `*mut ScreenCharacter` diff --git a/testing/tests/vga.rs b/testing/tests/vga.rs index 0c8be16..e6557ab 100644 --- a/testing/tests/vga.rs +++ b/testing/tests/vga.rs @@ -7,8 +7,9 @@ use core::panic::PanicInfo; use testing::{gdt, interrupts, serial_print, serial_println}; use vga::{ - Vga, VgaConfiguration, VideoMode, MODE_40X25_CONFIGURATION, MODE_40X50_CONFIGURATION, - MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION, VGA, + Vga, VgaConfiguration, VideoMode, DEFAULT_PALETTE, MODE_40X25_CONFIGURATION, + MODE_40X50_CONFIGURATION, MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION, + PALETTE_SIZE, VGA, }; #[no_mangle] // don't mangle the name of this function @@ -75,6 +76,22 @@ fn set_mode_640x480x16() { serial_println!("[ok]"); } +#[test_case] +fn load_palette() { + serial_print!("load palette... "); + + let mut palette = [0u8; PALETTE_SIZE]; + let mut vga = VGA.lock(); + vga.load_palette(&DEFAULT_PALETTE); + vga.read_palette(&mut palette); + + for i in 0..PALETTE_SIZE { + assert_eq!(palette[i], DEFAULT_PALETTE[i]); + } + + serial_println!("[ok]"); +} + fn check_registers(vga: &mut Vga, configuration: &VgaConfiguration) { let emulation_mode = vga.get_emulation_mode(); assert_eq!(vga.read_msr(), configuration.miscellaneous_output);