//! 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, registers::{ AttributeControllerIndex, AttributeControllerRegisters, ColorPaletteRegisters, CrtcControllerIndex, CrtcControllerRegisters, EmulationMode, GeneralRegisters, GraphicsControllerIndex, GraphicsControllerRegisters, SequencerIndex, SequencerRegisters, }, }; use conquer_once::spin::Lazy; use spinning_top::Spinlock; /// Provides mutable access to the vga graphics card. pub static VGA: Lazy> = Lazy::new(|| Spinlock::new(Vga::new())); /// Represents the starting address of the frame buffer for /// various video modes. #[derive(Debug, Copy, Clone)] #[repr(u32)] pub enum FrameBuffer { /// The starting address for graphics modes. GraphicsMode = 0xa0000, /// The starting address for color text modes. CgaMode = 0xb8000, /// The starting address for monochrome text modes. MdaMode = 0xb0000, } impl From for FrameBuffer { fn from(value: u8) -> FrameBuffer { match value { 0x1 => FrameBuffer::GraphicsMode, 0x2 => FrameBuffer::MdaMode, 0x3 => FrameBuffer::CgaMode, _ => panic!("{:X} is not a valid FrameBuffer value", value), } } } impl From for u32 { fn from(value: FrameBuffer) -> u32 { value as u32 } } /// Represents a plane for reading and writing vga data. #[allow(dead_code)] #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Plane { /// Represents `Plane 0 (0x0)`. Plane0 = 0x0, /// Represents `Plane 1 (0x1)`. Plane1 = 0x1, /// Represents `Plane 2 (0x2)`. Plane2 = 0x2, /// Represents `Plane 3 (0x3)`. Plane3 = 0x3, } impl From for u8 { fn from(value: Plane) -> u8 { value as u8 } } /// Represents a specified vga video mode. #[derive(Debug, Clone, Copy)] pub enum VideoMode { /// Represents text mode 40x25. Mode40x25, /// Represents text mode 40x50. Mode40x50, /// Represents text mode 80x25. Mode80x25, /// Represents graphics mode 640x480x16. Mode640x480x16, } /// Represents a vga graphics card with it's common registers, /// as well as the most recent video mode. pub struct Vga { general_registers: GeneralRegisters, sequencer_registers: SequencerRegisters, graphics_controller_registers: GraphicsControllerRegisters, attribute_controller_registers: AttributeControllerRegisters, crtc_controller_registers: CrtcControllerRegisters, color_palette_registers: ColorPaletteRegisters, most_recent_video_mode: Option, } impl Vga { fn new() -> Vga { Vga { general_registers: GeneralRegisters::new(), sequencer_registers: SequencerRegisters::new(), 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, } } /// Sets the vga graphics card to the given `VideoMode`. pub fn set_video_mode(&mut self, video_mode: VideoMode) { match video_mode { VideoMode::Mode40x25 => self.set_video_mode_40x25(), VideoMode::Mode40x50 => self.set_video_mode_40x50(), VideoMode::Mode80x25 => self.set_video_mode_80x25(), VideoMode::Mode640x480x16 => self.set_video_mode_640x480x16(), } } /// Gets the `FrameBuffer` address as specified by the /// `Miscellaneous Output Register`. pub fn get_frame_buffer(&mut self) -> FrameBuffer { let miscellaneous_graphics = self .graphics_controller_registers .read(GraphicsControllerIndex::Miscellaneous); let memory_map_mode = (miscellaneous_graphics >> 0x2) & 0x3; FrameBuffer::from(memory_map_mode) } /// Returns the most recent video mode, or `None` if no /// video mode has been set yet. pub fn get_most_recent_video_mode(&self) -> Option { self.most_recent_video_mode } /// Returns the current value of the miscellaneous output register. pub fn read_msr(&mut self) -> u8 { self.general_registers.read_msr() } /// Returns the current value of the sequencer register, as determined by `index`. pub fn read_sequencer(&mut self, index: SequencerIndex) -> u8 { self.sequencer_registers.read(index) } /// Returns the current value of the graphics controller register, as determined by `index`. pub fn read_graphics_controller(&mut self, index: GraphicsControllerIndex) -> u8 { self.graphics_controller_registers.read(index) } /// Returns the current value of the attribute controller register, as determined by `emulation_mode` /// and `index`. pub fn read_attribute_controller( &mut self, emulation_mode: EmulationMode, index: AttributeControllerIndex, ) -> u8 { self.attribute_controller_registers .read(emulation_mode, index) } /// Returns the current value of the crtc controller, as determined by `emulation_mode` /// and `index`. pub fn read_crtc_controller( &mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex, ) -> u8 { self.crtc_controller_registers.read(emulation_mode, index) } /// Writes `value` to the crtc controller, as determined by `index`. pub fn write_crtc_controller( &mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex, value: u8, ) { self.crtc_controller_registers .write(emulation_mode, index, value); } /// Returns the current `EmulationMode` as determined by the miscellaneous output register. pub fn get_emulation_mode(&mut self) -> EmulationMode { 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); } /// Loads a vga text mode font as specified by `vga_font`. pub fn load_font(&mut self, vga_font: &VgaFont) { // Save registers let ( plane_mask, sequencer_memory_mode, read_plane_select, graphics_mode, miscellaneous_graphics, ) = self.save_font_registers(); // Switch to flat addressing self.sequencer_registers .write(SequencerIndex::MemoryMode, sequencer_memory_mode | 0x04); // Disable Even/Odd addressing self.graphics_controller_registers .write(GraphicsControllerIndex::GraphicsMode, graphics_mode & !0x10); self.graphics_controller_registers.write( GraphicsControllerIndex::Miscellaneous, miscellaneous_graphics & !0x02, ); // Write font to plane self.set_plane(Plane::Plane2); let frame_buffer = u32::from(self.get_frame_buffer()) as *mut u8; for character in 0..vga_font.characters { for row in 0..vga_font.character_height { let offset = (character * 32) + row; let font_offset = (character * vga_font.character_height) + row; unsafe { frame_buffer .offset(offset as isize) .write_volatile(vga_font.font_data[font_offset as usize]); } } } self.restore_font_registers( plane_mask, sequencer_memory_mode, read_plane_select, graphics_mode, miscellaneous_graphics, ); } fn restore_font_registers( &mut self, plane_mask: u8, sequencer_memory_mode: u8, read_plane_select: u8, graphics_mode: u8, miscellaneous_graphics: u8, ) { self.sequencer_registers .write(SequencerIndex::PlaneMask, plane_mask); self.sequencer_registers .write(SequencerIndex::MemoryMode, sequencer_memory_mode); self.graphics_controller_registers .write(GraphicsControllerIndex::ReadPlaneSelect, read_plane_select); self.graphics_controller_registers .write(GraphicsControllerIndex::GraphicsMode, graphics_mode); self.graphics_controller_registers.write( GraphicsControllerIndex::Miscellaneous, miscellaneous_graphics, ); } fn save_font_registers(&mut self) -> (u8, u8, u8, u8, u8) { ( self.sequencer_registers.read(SequencerIndex::PlaneMask), self.sequencer_registers.read(SequencerIndex::MemoryMode), self.graphics_controller_registers .read(GraphicsControllerIndex::ReadPlaneSelect), self.graphics_controller_registers .read(GraphicsControllerIndex::GraphicsMode), self.graphics_controller_registers .read(GraphicsControllerIndex::Miscellaneous), ) } /// Turns on the given `Plane` in the vga graphics card. pub fn set_plane(&mut self, plane: Plane) { let mut plane = u8::from(plane); plane &= 0x3; self.graphics_controller_registers .write(GraphicsControllerIndex::ReadPlaneSelect, plane); self.sequencer_registers .write(SequencerIndex::PlaneMask, 0x1 << plane); } fn set_registers(&mut self, configuration: &VgaConfiguration) { let emulation_mode = self.get_emulation_mode(); // Set miscellaneous output self.general_registers .write_msr(configuration.miscellaneous_output); // Set the sequencer registers. for (index, value) in configuration.sequencer_registers { self.sequencer_registers.write(*index, *value); } // Unlock the crtc registers. self.unlock_crtc_registers(emulation_mode); // Set the crtc registers. for (index, value) in configuration.crtc_controller_registers { self.crtc_controller_registers .write(emulation_mode, *index, *value); } // Set the grx registers. for (index, value) in configuration.graphics_controller_registers { self.graphics_controller_registers.write(*index, *value); } // Blank the screen so the palette registers are unlocked. self.attribute_controller_registers .blank_screen(emulation_mode); // Set the arx registers. for (index, value) in configuration.attribute_controller_registers { self.attribute_controller_registers .write(emulation_mode, *index, *value); } // Unblank the screen so the palette registers are locked. self.attribute_controller_registers .unblank_screen(emulation_mode); } /// Sets the video card to Mode 40x25. fn set_video_mode_40x25(&mut self) { self.set_registers(&MODE_40X25_CONFIGURATION); self.most_recent_video_mode = Some(VideoMode::Mode40x25); } /// Sets the video card to Mode 40x50. fn set_video_mode_40x50(&mut self) { self.set_registers(&MODE_40X50_CONFIGURATION); self.most_recent_video_mode = Some(VideoMode::Mode40x50); } /// Sets the video card to Mode 80x25. fn set_video_mode_80x25(&mut self) { self.set_registers(&MODE_80X25_CONFIGURATION); self.most_recent_video_mode = Some(VideoMode::Mode80x25); } /// Sets the video card to Mode 640x480x16. fn set_video_mode_640x480x16(&mut self) { self.set_registers(&MODE_640X480X16_CONFIGURATION); self.most_recent_video_mode = Some(VideoMode::Mode640x480x16); } /// Unlocks the CRTC registers by setting bit 7 to 0 `(value & 0x7F)`. /// /// `Protect Registers [0:7]`: Note that the ability to write to Bit 4 of the Overflow Register (CR07) /// is not affected by this bit (i.e., bit 4 of the Overflow Register is always writeable). /// /// 0 = Enable writes to registers `CR[00:07]` /// /// 1 = Disable writes to registers `CR[00:07]` fn unlock_crtc_registers(&mut self, emulation_mode: EmulationMode) { // Setting bit 7 to 1 used to be required for `VGA`, but says it's // ignored in modern hardware. Setting it to 1 just to be safe for older // hardware. More information can be found here // https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display.pdf // under `CR03 - Horizontal Blanking End Register`. let horizontal_blanking_end = self .crtc_controller_registers .read(emulation_mode, CrtcControllerIndex::HorizontalBlankingEnd); self.crtc_controller_registers.write( emulation_mode, CrtcControllerIndex::HorizontalBlankingEnd, horizontal_blanking_end | 0x80, ); let vertical_sync_end = self .crtc_controller_registers .read(emulation_mode, CrtcControllerIndex::VerticalSyncEnd); self.crtc_controller_registers.write( emulation_mode, CrtcControllerIndex::VerticalSyncEnd, vertical_sync_end & 0x7F, ); } }