Merge pull request #8 from rust-osdev/graphics-refactor

Graphics refactor
This commit is contained in:
RKennedy9064 2020-03-24 16:21:47 -05:00 committed by GitHub
commit c4a0ea5872
23 changed files with 1143 additions and 726 deletions

View file

@ -19,6 +19,12 @@ repository = "https://github.com/rust-osdev/vga"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bitflags = "1.2.1"
conquer-once = { version = "0.2.0", default-features = false } conquer-once = { version = "0.2.0", default-features = false }
font8x8 = { version = "0.2.5", default-features = false, features = ["unicode"] }
spinning_top = { version = "0.1.0", features = ["nightly"] } spinning_top = { version = "0.1.0", features = ["nightly"] }
x86_64 = "0.9.6" x86_64 = "0.9.6"
[dependencies.num-traits]
version = "0.2.11"
default-features = false

View file

@ -2,7 +2,7 @@
## Breaking ## Breaking
- `ScreenCharacter::new` now takes a `TextModeColor` instead of 2 `Color16Bit`. - `ScreenCharacter::new` now takes a `TextModeColor` instead of 2 `Color16`.
## Other ## Other
- Added `ScreenCharacter::get_character`. - Added `ScreenCharacter::get_character`.

View file

@ -11,11 +11,11 @@ this crate to work properly.
## Usage ## Usage
```rust ```rust
use vga::colors::{Color16Bit, TextModeColor}; use vga::colors::{Color16, TextModeColor};
use vga::writers::{ScreenCharacter, TextWriter, Text80x25}; use vga::writers::{ScreenCharacter, TextWriter, Text80x25};
let text_mode = Text80x25::new(); let text_mode = Text80x25::new();
let color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); let color = TextModeColor::new(Color16::Yellow, Color16::Black);
let screen_character = ScreenCharacter::new(b'T', color); let screen_character = ScreenCharacter::new(b'T', color);
text_mode.set_mode(); text_mode.set_mode();

View file

@ -6,7 +6,7 @@ pub const PALETTE_SIZE: usize = 768;
/// Represents a 16 bit color used for vga display. /// Represents a 16 bit color used for vga display.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[repr(u8)] #[repr(u8)]
pub enum Color16Bit { pub enum Color16 {
/// Represents the color `Black (0x0)`. /// Represents the color `Black (0x0)`.
Black = 0x0, Black = 0x0,
/// Represents the color `Blue (0x1)`. /// Represents the color `Blue (0x1)`.
@ -41,6 +41,12 @@ pub enum Color16Bit {
White = 0xF, White = 0xF,
} }
impl From<Color16> for u8 {
fn from(value: Color16) -> u8 {
value as u8
}
}
/// Represents a color for vga text modes. /// Represents a color for vga text modes.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[repr(transparent)] #[repr(transparent)]
@ -49,17 +55,17 @@ pub struct TextModeColor(u8);
impl TextModeColor { impl TextModeColor {
/// Returns a new `TextModeColor` given the specified `foreground` /// Returns a new `TextModeColor` given the specified `foreground`
/// and `background` color. /// and `background` color.
pub const fn new(foreground: Color16Bit, background: Color16Bit) -> TextModeColor { pub const fn new(foreground: Color16, background: Color16) -> TextModeColor {
TextModeColor((background as u8) << 4 | (foreground as u8)) TextModeColor((background as u8) << 4 | (foreground as u8))
} }
/// Sets the background color given the specified `background`; /// Sets the background color given the specified `background`;
pub fn set_background(&mut self, background: Color16Bit) { pub fn set_background(&mut self, background: Color16) {
self.0 = (background as u8) << 4 | (self.0 & 0x0F); self.0 = (background as u8) << 4 | (self.0 & 0x0F);
} }
/// Sets the foreground color given the specified `foreground`. /// Sets the foreground color given the specified `foreground`.
pub fn set_foreground(&mut self, foreground: Color16Bit) { pub fn set_foreground(&mut self, foreground: Color16) {
self.0 = foreground as u8; self.0 = foreground as u8;
} }
} }
@ -122,15 +128,15 @@ mod test {
#[test] #[test]
fn test_set_foreground() { fn test_set_foreground() {
let mut color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); let mut color = TextModeColor::new(Color16::Yellow, Color16::Black);
color.set_foreground(Color16Bit::Red); color.set_foreground(Color16::Red);
assert_eq!(color.0 & 0x0F, Color16Bit::Red as u8); assert_eq!(color.0 & 0x0F, Color16::Red as u8);
} }
#[test] #[test]
fn test_set_background() { fn test_set_background() {
let mut color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); let mut color = TextModeColor::new(Color16::Yellow, Color16::Black);
color.set_background(Color16Bit::DarkGrey); color.set_background(Color16::DarkGrey);
assert_eq!(color.0 >> 4, Color16Bit::DarkGrey as u8); assert_eq!(color.0 >> 4, Color16::DarkGrey as u8);
} }
} }

57
src/drawing/bresenham.rs Normal file
View file

@ -0,0 +1,57 @@
use super::{Octant, Point, SignedNum};
pub(crate) struct Bresenham<T> {
point: Point<T>,
end_x: T,
delta_x: T,
delta_y: T,
error: T,
octant: Octant,
}
impl<T: SignedNum> Bresenham<T> {
#[inline]
pub fn new(start: Point<T>, end: Point<T>) -> Self {
let octant = Octant::new(start, end);
let start = octant.to(start);
let end = octant.to(end);
let delta_x = end.0 - start.0;
let delta_y = end.1 - start.1;
Self {
delta_x,
delta_y,
octant,
point: start,
end_x: end.0,
error: delta_y - delta_x,
}
}
}
impl<T> Iterator for Bresenham<T>
where
T: SignedNum,
{
type Item = Point<T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.point.0 <= self.end_x {
let point = self.octant.from(self.point);
if self.error >= T::zero() {
self.point.1 += T::one();
self.error -= self.delta_x;
}
self.point.0 += T::one();
self.error += self.delta_y;
Some(point)
} else {
None
}
}
}

20
src/drawing/mod.rs Normal file
View file

@ -0,0 +1,20 @@
//! Common functionality for drawing in vga graphics mode.
//! Original implementation here https://github.com/expenses/line_drawing.
use num_traits::{NumAssignOps, NumCast, Signed};
mod bresenham;
mod octant;
pub(crate) use bresenham::Bresenham;
use octant::Octant;
/// A point in 2D space.
pub type Point<T> = (T, T);
pub(crate) trait SignedNum: Signed + Ord + Copy + NumCast + NumAssignOps {
fn cast<T: NumCast>(value: T) -> Self {
NumCast::from(value).unwrap()
}
}
impl<T: Signed + Ord + Copy + NumCast + NumAssignOps> SignedNum for T {}

75
src/drawing/octant.rs Normal file
View file

@ -0,0 +1,75 @@
use super::Point;
use core::ops::{Neg, Sub};
use num_traits::Zero;
/// A simple octant struct for transforming line points.
pub struct Octant {
value: u8,
}
impl Octant {
#[inline]
/// Get the relevant octant from a start and end point.
pub fn new<T>(start: Point<T>, end: Point<T>) -> Self
where
T: Sub<Output = T> + Neg<Output = T> + PartialOrd + Zero,
{
let mut value = 0;
let mut dx = end.0 - start.0;
let mut dy = end.1 - start.1;
if dy < T::zero() {
dx = -dx;
dy = -dy;
value += 4;
}
if dx < T::zero() {
let tmp = dx;
dx = dy;
dy = -tmp;
value += 2;
}
if dx < dy {
value += 1;
}
Self { value }
}
/// Convert a point to its position in the octant.
#[inline]
pub fn to<T>(&self, point: Point<T>) -> Point<T>
where
T: Neg<Output = T>,
{
match self.value {
0 => (point.0, point.1),
1 => (point.1, point.0),
2 => (point.1, -point.0),
3 => (-point.0, point.1),
4 => (-point.0, -point.1),
5 => (-point.1, -point.0),
6 => (-point.1, point.0),
7 => (point.0, -point.1),
_ => unreachable!(),
}
}
/// Convert a point from its position in the octant.
#[inline]
pub fn from<T: Neg<Output = T>>(&self, point: Point<T>) -> Point<T> {
match self.value {
0 => (point.0, point.1),
1 => (point.1, point.0),
2 => (-point.1, point.0),
3 => (-point.0, point.1),
4 => (-point.0, -point.1),
5 => (-point.1, -point.0),
6 => (point.1, -point.0),
7 => (point.0, -point.1),
_ => unreachable!(),
}
}
}

View file

@ -9,6 +9,7 @@
pub mod colors; pub mod colors;
pub mod configurations; pub mod configurations;
pub mod drawing;
pub mod fonts; pub mod fonts;
pub mod registers; pub mod registers;
pub mod vga; pub mod vga;

View file

@ -1,510 +0,0 @@
//! Common registers used in vga programming.
use crate::colors::PALETTE_SIZE;
use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly};
const ST00_READ_ADDRESS: u16 = 0x3C2;
const ST01_READ_CGA_ADDRESS: u16 = 0x3DA;
const ST01_READ_MDA_ADDRESS: u16 = 0x3BA;
const FCR_READ_ADDRESS: u16 = 0x3CA;
const FCR_CGA_WRITE_ADDRESS: u16 = 0x3DA;
const FCR_MDA_WRITE_ADDRESS: u16 = 0x3BA;
const MSR_READ_ADDRESS: u16 = 0x3CC;
const MSR_WRITE_ADDRESS: u16 = 0x3C2;
const SRX_INDEX_ADDRESS: u16 = 0x3C4;
const SRX_DATA_ADDRESS: u16 = 0x3C5;
const GRX_INDEX_ADDRESS: u16 = 0x3CE;
const GRX_DATA_ADDRESS: u16 = 0x3CF;
const ARX_INDEX_ADDRESS: u16 = 0x3C0;
const ARX_DATA_ADDRESS: u16 = 0x3C1;
const CRX_INDEX_CGA_ADDRESS: u16 = 0x3D4;
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;
/// Represents a vga emulation mode.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum EmulationMode {
/// Represents a monochrome emulation mode.
Mda = 0x0,
/// Respresents a color emulation mode.
Cga = 0x1,
}
impl From<u8> for EmulationMode {
fn from(value: u8) -> EmulationMode {
match value {
0x0 => EmulationMode::Mda,
0x1 => EmulationMode::Cga,
_ => panic!("{} is an invalid emulation mode", value),
}
}
}
/// Represents an index for the seqeuncer registers.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum SequencerIndex {
/// Represents the `Sequencer Reset` register index.
SequencerReset = 0x0,
/// Represents the `Clocking Mode` register index.
ClockingMode = 0x1,
/// Represents the Plane/Map mask register index.
PlaneMask = 0x2,
/// Represents the `Character Font` register index.
CharacterFont = 0x3,
/// Represents the `Memory Mode` register index.
MemoryMode = 0x4,
/// Represents the `Horizontal Character Counter Reset` register index.
CounterReset = 0x7,
}
impl From<SequencerIndex> for u8 {
fn from(value: SequencerIndex) -> u8 {
value as u8
}
}
/// Represents an index for the graphics controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum GraphicsControllerIndex {
/// Represents the `Set/Reset` register index.
SetReset = 0x0,
/// Represents the `Enable Set/Reset` register index.
EnableSetReset = 0x1,
/// Represents the `Color Compare` register index.
ColorCompare = 0x2,
/// Represents the `Data Rotate` register index.
DataRotate = 0x3,
/// Represents the `Read Plane Select` register index.
ReadPlaneSelect = 0x4,
/// Represents the `Graphics Mode` register index.
GraphicsMode = 0x5,
/// Represents the `Miscellaneous` register index.
Miscellaneous = 0x6,
/// Represents the `Color Don't Care` register index.
ColorDontCare = 0x7,
/// Represents the `Bit Mask` register index.
BitMask = 0x8,
/// Represents the `Address Mapping` register index.
AddressMapping = 0x10,
/// Represents the `Page Selector` register index.
PageSelector = 0x11,
/// Represents the `Software Flags` register index.
SoftwareFlags = 0x18,
}
impl From<GraphicsControllerIndex> for u8 {
fn from(value: GraphicsControllerIndex) -> u8 {
value as u8
}
}
/// Represents an index for the attribute controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum AttributeControllerIndex {
/// Represents the `Palette 0` register index.
PaletteRegister0 = 0x00,
/// Represents the `Palette 1` register index.
PaletteRegister1 = 0x01,
/// Represents the `Palette 2` register index.
PaletteRegister2 = 0x02,
/// Represents the `Palette 3` register index.
PaletteRegister3 = 0x03,
/// Represents the `Palette 4` register index.
PaletteRegister4 = 0x04,
/// Represents the `Palette 5` register index.
PaletteRegister5 = 0x05,
/// Represents the `Palette 6` register index.
PaletteRegister6 = 0x06,
/// Represents the `Palette 7` register index.
PaletteRegister7 = 0x07,
/// Represents the `Palette 8` register index.
PaletteRegister8 = 0x08,
/// Represents the `Palette 9` register index.
PaletteRegister9 = 0x09,
/// Represents the `Palette A` register index.
PaletteRegisterA = 0x0A,
/// Represents the `Palette B` register index.
PaletteRegisterB = 0x0B,
/// Represents the `Palette C` register index.
PaletteRegisterC = 0x0C,
/// Represents the `Palette D` register index.
PaletteRegisterD = 0x0D,
/// Represents the `Palette E` register index.
PaletteRegisterE = 0x0E,
/// Represents the `Palette F` register index.
PaletteRegisterF = 0x0F,
/// Represents the `Mode Control` register index.
ModeControl = 0x10,
/// Represents the `Overscan Color` register index.
OverscanColor = 0x11,
/// Represents the `Memory Plane Enable` register index.
MemoryPlaneEnable = 0x12,
/// Represents the `Horizontal Pixel Panning` register index.
HorizontalPixelPanning = 0x13,
/// Represents the `Color Select` register index.
ColorSelect = 0x14,
}
impl From<AttributeControllerIndex> for u8 {
fn from(value: AttributeControllerIndex) -> u8 {
value as u8
}
}
/// Represents an index for the crtc controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum CrtcControllerIndex {
/// Represents the `Horizontal Total` register index.
HorizontalTotal = 0x00,
/// Represents the `Horizontal Display Enable End` register index.
HorizontalDisplayEnableEnd = 0x01,
/// Represents the `Horizontal Blanking Start` register index.
HorizontalBlankingStart = 0x02,
/// Represents the `Horizontal Blanking End` register index.
HorizontalBlankingEnd = 0x03,
/// Represents the `Horizontal Sync Start` register index.
HorizontalSyncStart = 0x04,
/// Represents the `Horizontal Sync End` register index.
HorizontalSyncEnd = 0x05,
/// Represents the `Vertical Total` register index.
VeritcalTotal = 0x06,
/// Represents the `Overflow` register index.
Overflow = 0x07,
/// Represents the `Preset Row Scan` register index.
PresetRowScan = 0x08,
/// Represents the `Maximum Scan Line` register index.
MaximumScanLine = 0x09,
/// Represents the `Text Cursor Start` register index.
TextCursorStart = 0x0A,
/// Represents the `Text Cursor End` register index.
TextCursorEnd = 0x0B,
/// Represents the `Start Address High` register index.
StartAddressHigh = 0x0C,
/// Represents the `Start Address Low` register index.
StartAddressLow = 0x0D,
/// Represents the `Text Cursor Location High` register index.
TextCursorLocationHigh = 0x0E,
/// Represents the `Text Cursor Location Low` register index.
TextCursorLocationLow = 0x0F,
/// Represents the `Vertical Sync Start` register index.
VerticalSyncStart = 0x10,
/// Represents the `Vertical Sync End` register index.
VerticalSyncEnd = 0x11,
/// Represents the `Vertical Display Enable End` register index
VerticalDisplayEnableEnd = 0x12,
/// Represents the `Offset` register index.
Offset = 0x13,
/// Represents the `Underline Location` register index.
UnderlineLocation = 0x14,
/// Represents the `Vertical Blanking Start` register index.
VerticalBlankingStart = 0x15,
/// Represents the `Vertical Blanking End` register index.
VerticalBlankingEnd = 0x16,
/// Represents the `Mode Control` register index.
ModeControl = 0x17,
/// Represents the `Line Compare` register index.
LineCompare = 0x18,
/// Represents the `Memory Read Latch Data` register index.
MemoryReadLatchData = 0x22,
/// Represents the `Toggle State Of Attribute Controller` register index.
ToggleStateOfAttributeController = 0x24,
}
impl From<CrtcControllerIndex> for u8 {
fn from(value: CrtcControllerIndex) -> u8 {
value as u8
}
}
#[derive(Debug)]
pub(crate) struct GeneralRegisters {
st00_read: PortReadOnly<u8>,
st01_read_cga: PortReadOnly<u8>,
st01_read_mda: PortReadOnly<u8>,
fcr_read: PortReadOnly<u8>,
fcr_write_cga: PortWriteOnly<u8>,
fcr_write_mda: PortWriteOnly<u8>,
msr_read: PortReadOnly<u8>,
msr_write: PortWriteOnly<u8>,
}
impl GeneralRegisters {
pub fn new() -> GeneralRegisters {
GeneralRegisters {
st00_read: PortReadOnly::new(ST00_READ_ADDRESS),
st01_read_cga: PortReadOnly::new(ST01_READ_CGA_ADDRESS),
st01_read_mda: PortReadOnly::new(ST01_READ_MDA_ADDRESS),
fcr_read: PortReadOnly::new(FCR_READ_ADDRESS),
fcr_write_cga: PortWriteOnly::new(FCR_CGA_WRITE_ADDRESS),
fcr_write_mda: PortWriteOnly::new(FCR_MDA_WRITE_ADDRESS),
msr_read: PortReadOnly::new(MSR_READ_ADDRESS),
msr_write: PortWriteOnly::new(MSR_WRITE_ADDRESS),
}
}
pub fn read_msr(&mut self) -> u8 {
unsafe { self.msr_read.read() }
}
pub fn write_msr(&mut self, value: u8) {
unsafe {
self.msr_write.write(value);
}
}
}
#[derive(Debug)]
pub(crate) struct SequencerRegisters {
srx_index: Port<u8>,
srx_data: Port<u8>,
}
impl SequencerRegisters {
pub fn new() -> SequencerRegisters {
SequencerRegisters {
srx_index: Port::new(SRX_INDEX_ADDRESS),
srx_data: Port::new(SRX_DATA_ADDRESS),
}
}
pub fn read(&mut self, index: SequencerIndex) -> u8 {
self.set_index(index);
unsafe { self.srx_data.read() }
}
pub fn write(&mut self, index: SequencerIndex, value: u8) {
self.set_index(index);
unsafe {
self.srx_data.write(value);
}
}
fn set_index(&mut self, index: SequencerIndex) {
unsafe {
self.srx_index.write(u8::from(index));
}
}
}
#[derive(Debug)]
pub(crate) struct GraphicsControllerRegisters {
grx_index: Port<u8>,
grx_data: Port<u8>,
}
impl GraphicsControllerRegisters {
pub fn new() -> GraphicsControllerRegisters {
GraphicsControllerRegisters {
grx_index: Port::new(GRX_INDEX_ADDRESS),
grx_data: Port::new(GRX_DATA_ADDRESS),
}
}
pub fn read(&mut self, index: GraphicsControllerIndex) -> u8 {
self.set_index(index);
unsafe { self.grx_data.read() }
}
pub fn write(&mut self, index: GraphicsControllerIndex, value: u8) {
self.set_index(index);
unsafe {
self.grx_data.write(value);
}
}
fn set_index(&mut self, index: GraphicsControllerIndex) {
unsafe {
self.grx_index.write(u8::from(index));
}
}
}
#[derive(Debug)]
pub(crate) struct AttributeControllerRegisters {
arx_index: Port<u8>,
arx_data: Port<u8>,
st01_read_cga: Port<u8>,
st01_read_mda: Port<u8>,
}
impl AttributeControllerRegisters {
pub fn new() -> AttributeControllerRegisters {
AttributeControllerRegisters {
arx_index: Port::new(ARX_INDEX_ADDRESS),
arx_data: Port::new(ARX_DATA_ADDRESS),
st01_read_cga: Port::new(ST01_READ_CGA_ADDRESS),
st01_read_mda: Port::new(ST01_READ_MDA_ADDRESS),
}
}
pub fn read(&mut self, emulation_mode: EmulationMode, index: AttributeControllerIndex) -> u8 {
self.toggle_index(emulation_mode);
self.set_index(index);
unsafe { self.arx_data.read() }
}
pub fn write(
&mut self,
emulation_mode: EmulationMode,
index: AttributeControllerIndex,
value: u8,
) {
self.toggle_index(emulation_mode);
self.set_index(index);
unsafe {
self.arx_index.write(value);
}
}
fn set_index(&mut self, index: AttributeControllerIndex) {
unsafe {
self.arx_index.write(u8::from(index));
}
}
fn toggle_index(&mut self, emulation_mode: EmulationMode) {
let st01_read = match emulation_mode {
EmulationMode::Cga => &mut self.st01_read_cga,
EmulationMode::Mda => &mut self.st01_read_mda,
};
unsafe {
st01_read.read();
}
}
/// Video Enable. Note that In the VGA standard, this is called the "Palette Address Source" bit.
/// Clearing this bit will cause the VGA display data to become all 00 index values. For the default
/// palette, this will cause a black screen. The video timing signals continue. Another control bit will
/// turn video off and stop the data fetches.
///
/// 0 = Disable. Attribute controller color registers (AR[00:0F]) can be accessed by the CPU.
///
/// 1 = Enable. Attribute controller color registers (AR[00:0F]) are inaccessible by the CPU.
pub fn blank_screen(&mut self, emulation_mode: EmulationMode) {
self.toggle_index(emulation_mode);
let arx_index_value = unsafe { self.arx_index.read() };
unsafe {
self.arx_index.write(arx_index_value & 0xDF);
}
}
/// Video Enable. Note that In the VGA standard, this is called the "Palette Address Source" bit.
/// Clearing this bit will cause the VGA display data to become all 00 index values. For the default
/// palette, this will cause a black screen. The video timing signals continue. Another control bit will
/// turn video off and stop the data fetches.
///
/// 0 = Disable. Attribute controller color registers (AR[00:0F]) can be accessed by the CPU.
///
/// 1 = Enable. Attribute controller color registers (AR[00:0F]) are inaccessible by the CPU.
pub fn unblank_screen(&mut self, emulation_mode: EmulationMode) {
self.toggle_index(emulation_mode);
let arx_index_value = unsafe { self.arx_index.read() };
unsafe {
self.arx_index.write(arx_index_value | 0x20);
}
}
}
#[derive(Debug)]
pub(crate) struct CrtcControllerRegisters {
crx_index_cga: Port<u8>,
crx_index_mda: Port<u8>,
crx_data_cga: Port<u8>,
crx_data_mda: Port<u8>,
}
impl CrtcControllerRegisters {
pub fn new() -> CrtcControllerRegisters {
CrtcControllerRegisters {
crx_index_cga: Port::new(CRX_INDEX_CGA_ADDRESS),
crx_index_mda: Port::new(CRX_INDEX_MDA_ADDRESS),
crx_data_cga: Port::new(CRX_DATA_CGA_ADDRESS),
crx_data_mda: Port::new(CRX_DATA_MDA_ADDRESS),
}
}
pub fn read(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex) -> u8 {
self.set_index(emulation_mode, index);
unsafe { self.get_data_port(emulation_mode).read() }
}
pub fn write(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex, value: u8) {
self.set_index(emulation_mode, index);
unsafe {
self.get_data_port(emulation_mode).write(value);
}
}
fn set_index(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex) {
unsafe {
self.get_index_port(emulation_mode).write(u8::from(index));
}
}
fn get_data_port(&mut self, emulation_mode: EmulationMode) -> &mut Port<u8> {
match emulation_mode {
EmulationMode::Cga => &mut self.crx_data_cga,
EmulationMode::Mda => &mut self.crx_data_mda,
}
}
fn get_index_port(&mut self, emulation_mode: EmulationMode) -> &mut Port<u8> {
match emulation_mode {
EmulationMode::Cga => &mut self.crx_index_cga,
EmulationMode::Mda => &mut self.crx_index_mda,
}
}
}
#[derive(Debug)]
pub(crate) struct ColorPaletteRegisters {
data_port: Port<u8>,
index_read_port: Port<u8>,
index_write_port: Port<u8>,
}
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 byte in palette.iter_mut().take(PALETTE_SIZE) {
unsafe {
*byte = self.data_port.read();
}
}
}
}

View file

@ -0,0 +1,150 @@
use super::{
EmulationMode, ARX_DATA_ADDRESS, ARX_INDEX_ADDRESS, ST01_READ_CGA_ADDRESS,
ST01_READ_MDA_ADDRESS,
};
use x86_64::instructions::port::Port;
/// Represents an index for the attribute controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum AttributeControllerIndex {
/// Represents the `Palette 0` register index.
PaletteRegister0 = 0x00,
/// Represents the `Palette 1` register index.
PaletteRegister1 = 0x01,
/// Represents the `Palette 2` register index.
PaletteRegister2 = 0x02,
/// Represents the `Palette 3` register index.
PaletteRegister3 = 0x03,
/// Represents the `Palette 4` register index.
PaletteRegister4 = 0x04,
/// Represents the `Palette 5` register index.
PaletteRegister5 = 0x05,
/// Represents the `Palette 6` register index.
PaletteRegister6 = 0x06,
/// Represents the `Palette 7` register index.
PaletteRegister7 = 0x07,
/// Represents the `Palette 8` register index.
PaletteRegister8 = 0x08,
/// Represents the `Palette 9` register index.
PaletteRegister9 = 0x09,
/// Represents the `Palette A` register index.
PaletteRegisterA = 0x0A,
/// Represents the `Palette B` register index.
PaletteRegisterB = 0x0B,
/// Represents the `Palette C` register index.
PaletteRegisterC = 0x0C,
/// Represents the `Palette D` register index.
PaletteRegisterD = 0x0D,
/// Represents the `Palette E` register index.
PaletteRegisterE = 0x0E,
/// Represents the `Palette F` register index.
PaletteRegisterF = 0x0F,
/// Represents the `Mode Control` register index.
ModeControl = 0x10,
/// Represents the `Overscan Color` register index.
OverscanColor = 0x11,
/// Represents the `Memory Plane Enable` register index.
MemoryPlaneEnable = 0x12,
/// Represents the `Horizontal Pixel Panning` register index.
HorizontalPixelPanning = 0x13,
/// Represents the `Color Select` register index.
ColorSelect = 0x14,
}
impl From<AttributeControllerIndex> for u8 {
fn from(value: AttributeControllerIndex) -> u8 {
value as u8
}
}
/// Represents the attribute controller registers on vga hardware.
#[derive(Debug)]
pub struct AttributeControllerRegisters {
arx_index: Port<u8>,
arx_data: Port<u8>,
st01_read_cga: Port<u8>,
st01_read_mda: Port<u8>,
}
impl AttributeControllerRegisters {
pub(crate) fn new() -> AttributeControllerRegisters {
AttributeControllerRegisters {
arx_index: Port::new(ARX_INDEX_ADDRESS),
arx_data: Port::new(ARX_DATA_ADDRESS),
st01_read_cga: Port::new(ST01_READ_CGA_ADDRESS),
st01_read_mda: Port::new(ST01_READ_MDA_ADDRESS),
}
}
/// Reads the current value of the attribute controller, as specified
/// by `emulation_mode` and `index`.
pub fn read(&mut self, emulation_mode: EmulationMode, index: AttributeControllerIndex) -> u8 {
self.toggle_index(emulation_mode);
self.set_index(index);
unsafe { self.arx_data.read() }
}
/// Writes the `value` to the attribute controller, as specified
/// `emulation_mode` and `index`.
pub fn write(
&mut self,
emulation_mode: EmulationMode,
index: AttributeControllerIndex,
value: u8,
) {
self.toggle_index(emulation_mode);
self.set_index(index);
unsafe {
self.arx_index.write(value);
}
}
/// Video Enable. Note that In the VGA standard, this is called the "Palette Address Source" bit.
/// Clearing this bit will cause the VGA display data to become all 00 index values. For the default
/// palette, this will cause a black screen. The video timing signals continue. Another control bit will
/// turn video off and stop the data fetches.
///
/// 0 = Disable. Attribute controller color registers (AR[00:0F]) can be accessed by the CPU.
///
/// 1 = Enable. Attribute controller color registers (AR[00:0F]) are inaccessible by the CPU.
pub fn blank_screen(&mut self, emulation_mode: EmulationMode) {
self.toggle_index(emulation_mode);
let arx_index_value = unsafe { self.arx_index.read() };
unsafe {
self.arx_index.write(arx_index_value & 0xDF);
}
}
/// Video Enable. Note that In the VGA standard, this is called the "Palette Address Source" bit.
/// Clearing this bit will cause the VGA display data to become all 00 index values. For the default
/// palette, this will cause a black screen. The video timing signals continue. Another control bit will
/// turn video off and stop the data fetches.
///
/// 0 = Disable. Attribute controller color registers (AR[00:0F]) can be accessed by the CPU.
///
/// 1 = Enable. Attribute controller color registers (AR[00:0F]) are inaccessible by the CPU.
pub fn unblank_screen(&mut self, emulation_mode: EmulationMode) {
self.toggle_index(emulation_mode);
let arx_index_value = unsafe { self.arx_index.read() };
unsafe {
self.arx_index.write(arx_index_value | 0x20);
}
}
fn set_index(&mut self, index: AttributeControllerIndex) {
unsafe {
self.arx_index.write(u8::from(index));
}
}
fn toggle_index(&mut self, emulation_mode: EmulationMode) {
let st01_read = match emulation_mode {
EmulationMode::Cga => &mut self.st01_read_cga,
EmulationMode::Mda => &mut self.st01_read_mda,
};
unsafe {
st01_read.read();
}
}
}

View file

@ -0,0 +1,49 @@
use super::{
COLOR_PALETTE_DATA_ADDRESS, COLOR_PALETTE_INDEX_READ_ADDRESS,
COLOR_PALETTE_INDEX_WRITE_ADDRESSS, PALETTE_SIZE,
};
use x86_64::instructions::port::Port;
/// Represents the color palette registers on vga hardware.
#[derive(Debug)]
pub struct ColorPaletteRegisters {
data_port: Port<u8>,
index_read_port: Port<u8>,
index_write_port: Port<u8>,
}
impl ColorPaletteRegisters {
pub(crate) 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),
}
}
/// Loads a 256 color palette, as specified by `palette`, with every 3
/// bytes representing a color.
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);
}
}
}
/// Reads the current 256 color palette into `palette`, with every 3
/// bytes representing a color.
pub fn read_palette(&mut self, palette: &mut [u8; PALETTE_SIZE]) {
unsafe {
self.index_read_port.write(0);
}
for byte in palette.iter_mut().take(PALETTE_SIZE) {
unsafe {
*byte = self.data_port.read();
}
}
}
}

View file

@ -0,0 +1,127 @@
use super::{
EmulationMode, CRX_DATA_CGA_ADDRESS, CRX_DATA_MDA_ADDRESS, CRX_INDEX_CGA_ADDRESS,
CRX_INDEX_MDA_ADDRESS,
};
use x86_64::instructions::port::Port;
/// Represents an index for the crtc controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum CrtcControllerIndex {
/// Represents the `Horizontal Total` register index.
HorizontalTotal = 0x00,
/// Represents the `Horizontal Display Enable End` register index.
HorizontalDisplayEnableEnd = 0x01,
/// Represents the `Horizontal Blanking Start` register index.
HorizontalBlankingStart = 0x02,
/// Represents the `Horizontal Blanking End` register index.
HorizontalBlankingEnd = 0x03,
/// Represents the `Horizontal Sync Start` register index.
HorizontalSyncStart = 0x04,
/// Represents the `Horizontal Sync End` register index.
HorizontalSyncEnd = 0x05,
/// Represents the `Vertical Total` register index.
VeritcalTotal = 0x06,
/// Represents the `Overflow` register index.
Overflow = 0x07,
/// Represents the `Preset Row Scan` register index.
PresetRowScan = 0x08,
/// Represents the `Maximum Scan Line` register index.
MaximumScanLine = 0x09,
/// Represents the `Text Cursor Start` register index.
TextCursorStart = 0x0A,
/// Represents the `Text Cursor End` register index.
TextCursorEnd = 0x0B,
/// Represents the `Start Address High` register index.
StartAddressHigh = 0x0C,
/// Represents the `Start Address Low` register index.
StartAddressLow = 0x0D,
/// Represents the `Text Cursor Location High` register index.
TextCursorLocationHigh = 0x0E,
/// Represents the `Text Cursor Location Low` register index.
TextCursorLocationLow = 0x0F,
/// Represents the `Vertical Sync Start` register index.
VerticalSyncStart = 0x10,
/// Represents the `Vertical Sync End` register index.
VerticalSyncEnd = 0x11,
/// Represents the `Vertical Display Enable End` register index
VerticalDisplayEnableEnd = 0x12,
/// Represents the `Offset` register index.
Offset = 0x13,
/// Represents the `Underline Location` register index.
UnderlineLocation = 0x14,
/// Represents the `Vertical Blanking Start` register index.
VerticalBlankingStart = 0x15,
/// Represents the `Vertical Blanking End` register index.
VerticalBlankingEnd = 0x16,
/// Represents the `Mode Control` register index.
ModeControl = 0x17,
/// Represents the `Line Compare` register index.
LineCompare = 0x18,
/// Represents the `Memory Read Latch Data` register index.
MemoryReadLatchData = 0x22,
/// Represents the `Toggle State Of Attribute Controller` register index.
ToggleStateOfAttributeController = 0x24,
}
impl From<CrtcControllerIndex> for u8 {
fn from(value: CrtcControllerIndex) -> u8 {
value as u8
}
}
/// Represents the crtc controller registers on vga hardware.
#[derive(Debug)]
pub struct CrtcControllerRegisters {
crx_index_cga: Port<u8>,
crx_index_mda: Port<u8>,
crx_data_cga: Port<u8>,
crx_data_mda: Port<u8>,
}
impl CrtcControllerRegisters {
pub(crate) fn new() -> CrtcControllerRegisters {
CrtcControllerRegisters {
crx_index_cga: Port::new(CRX_INDEX_CGA_ADDRESS),
crx_index_mda: Port::new(CRX_INDEX_MDA_ADDRESS),
crx_data_cga: Port::new(CRX_DATA_CGA_ADDRESS),
crx_data_mda: Port::new(CRX_DATA_MDA_ADDRESS),
}
}
/// Reads the current value from the crtc controller, as specified
/// by `emulation_mode` and `index`.
pub fn read(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex) -> u8 {
self.set_index(emulation_mode, index);
unsafe { self.get_data_port(emulation_mode).read() }
}
/// Writes the `value` to the crtc_controller, as specified
/// by `emulation_mode` and `index`.
pub fn write(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex, value: u8) {
self.set_index(emulation_mode, index);
unsafe {
self.get_data_port(emulation_mode).write(value);
}
}
fn set_index(&mut self, emulation_mode: EmulationMode, index: CrtcControllerIndex) {
unsafe {
self.get_index_port(emulation_mode).write(u8::from(index));
}
}
fn get_data_port(&mut self, emulation_mode: EmulationMode) -> &mut Port<u8> {
match emulation_mode {
EmulationMode::Cga => &mut self.crx_data_cga,
EmulationMode::Mda => &mut self.crx_data_mda,
}
}
fn get_index_port(&mut self, emulation_mode: EmulationMode) -> &mut Port<u8> {
match emulation_mode {
EmulationMode::Cga => &mut self.crx_index_cga,
EmulationMode::Mda => &mut self.crx_index_mda,
}
}
}

45
src/registers/general.rs Normal file
View file

@ -0,0 +1,45 @@
use super::{
FCR_CGA_WRITE_ADDRESS, FCR_MDA_WRITE_ADDRESS, FCR_READ_ADDRESS, MSR_READ_ADDRESS,
MSR_WRITE_ADDRESS, ST00_READ_ADDRESS, ST01_READ_CGA_ADDRESS, ST01_READ_MDA_ADDRESS,
};
use x86_64::instructions::port::{PortReadOnly, PortWriteOnly};
/// Represents the general registers on vga hardware.
#[derive(Debug)]
pub struct GeneralRegisters {
st00_read: PortReadOnly<u8>,
st01_read_cga: PortReadOnly<u8>,
st01_read_mda: PortReadOnly<u8>,
fcr_read: PortReadOnly<u8>,
fcr_write_cga: PortWriteOnly<u8>,
fcr_write_mda: PortWriteOnly<u8>,
msr_read: PortReadOnly<u8>,
msr_write: PortWriteOnly<u8>,
}
impl GeneralRegisters {
pub(crate) fn new() -> GeneralRegisters {
GeneralRegisters {
st00_read: PortReadOnly::new(ST00_READ_ADDRESS),
st01_read_cga: PortReadOnly::new(ST01_READ_CGA_ADDRESS),
st01_read_mda: PortReadOnly::new(ST01_READ_MDA_ADDRESS),
fcr_read: PortReadOnly::new(FCR_READ_ADDRESS),
fcr_write_cga: PortWriteOnly::new(FCR_CGA_WRITE_ADDRESS),
fcr_write_mda: PortWriteOnly::new(FCR_MDA_WRITE_ADDRESS),
msr_read: PortReadOnly::new(MSR_READ_ADDRESS),
msr_write: PortWriteOnly::new(MSR_WRITE_ADDRESS),
}
}
/// Reads the current value from the miscellaneous output register.
pub fn read_msr(&mut self) -> u8 {
unsafe { self.msr_read.read() }
}
/// Writes the `value` to the miscellaneous output register.
pub fn write_msr(&mut self, value: u8) {
unsafe {
self.msr_write.write(value);
}
}
}

View file

@ -0,0 +1,210 @@
use super::{Color16, GRX_DATA_ADDRESS, GRX_INDEX_ADDRESS};
use core::convert::TryFrom;
use x86_64::instructions::port::Port;
/// Represents a plane for the `GraphicsControllerIndex::ReadPlaneSelect` register.
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum ReadPlane {
/// 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 TryFrom<u8> for ReadPlane {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(ReadPlane::Plane0),
1 => Ok(ReadPlane::Plane1),
2 => Ok(ReadPlane::Plane2),
3 => Ok(ReadPlane::Plane3),
_ => Err("ReadPlane only accepts values between 0-3!"),
}
}
}
impl From<ReadPlane> for u8 {
fn from(value: ReadPlane) -> u8 {
value as u8
}
}
/// Represents an index for the graphics controller registers.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum GraphicsControllerIndex {
/// Represents the `Set/Reset` register index.
SetReset = 0x0,
/// Represents the `Enable Set/Reset` register index.
EnableSetReset = 0x1,
/// Represents the `Color Compare` register index.
ColorCompare = 0x2,
/// Represents the `Data Rotate` register index.
DataRotate = 0x3,
/// Represents the `Read Plane Select` register index.
ReadPlaneSelect = 0x4,
/// Represents the `Graphics Mode` register index.
GraphicsMode = 0x5,
/// Represents the `Miscellaneous` register index.
Miscellaneous = 0x6,
/// Represents the `Color Don't Care` register index.
ColorDontCare = 0x7,
/// Represents the `Bit Mask` register index.
BitMask = 0x8,
/// Represents the `Address Mapping` register index.
AddressMapping = 0x10,
/// Represents the `Page Selector` register index.
PageSelector = 0x11,
/// Represents the `Software Flags` register index.
SoftwareFlags = 0x18,
}
impl From<GraphicsControllerIndex> for u8 {
fn from(value: GraphicsControllerIndex) -> u8 {
value as u8
}
}
/// Represents a write mode for vga hardware.
#[derive(Debug)]
#[repr(u8)]
pub enum WriteMode {
/// Represents `WriteMode` 0.
///
/// During a CPU write to the frame buffer, the
/// addressed byte in each of the 4 memory planes is written with the
/// CPU write data after it has been rotated by the number of counts
/// specified in the `GraphicsControllerIndex::DataRotate` register. If, however, the bit(s)
/// in the `GraphicsControllerIndex::EnableSetReset` register corresponding to one or
/// more of the memory planes is set to 1, then those memory planes
/// will be written to with the data stored in the corresponding bits in
/// the `GraphicsControllerIndex::SetReset` register.
Mode0 = 0x0,
/// Represents `WriteMode` 1.
///
/// During a CPU write to the frame buffer, the
/// addressed byte in each of the 4 memory planes is written to with
/// the data stored in the memory read latches. (The memory read
/// latches stores an unaltered copy of the data last read from any
/// location in the frame buffer.)
Mode1 = 0x1,
/// Represents `WriteMode` 2.
///
/// During a CPU write to the frame buffer, the least
/// significant 4 data bits of the CPU write data is treated as the color
/// value for the pixels in the addressed byte in all 4 memory planes.
/// The 8 bits of the `GraphicsControllerIndex::BitMask` register are used to selectively
/// enable or disable the ability to write to the corresponding bit in
/// each of the 4 memory planes that correspond to a given pixel. A
/// setting of 0 in a bit in the Bit Mask Register at a given bit position
/// causes the bits in the corresponding bit positions in the addressed
/// byte in all 4 memory planes to be written with value of their
/// counterparts in the memory read latches. A setting of 1 in a Bit
/// Mask Register at a given bit position causes the bits in the
/// corresponding bit positions in the addressed byte in all 4 memory
/// planes to be written with the 4 bits taken from the CPU write data
/// to thereby cause the pixel corresponding to these bits to be set to
/// the color value.
Mode2 = 0x2,
/// Represents `WriteMode` 3.
///
/// During a CPU write to the frame buffer, the CPU
/// write data is logically ANDed with the contents of the `GraphicsControllerIndex::BitMask`
/// register. The result of this ANDing is treated as the bit
/// mask used in writing the contents of the `GraphicsControllerIndex::SetReset` register
/// are written to addressed byte in all 4 memory planes.
Mode3 = 0x3,
}
impl From<WriteMode> for u8 {
fn from(value: WriteMode) -> u8 {
value as u8
}
}
/// Represents the graphics controller registers on vga hardware.
#[derive(Debug)]
pub struct GraphicsControllerRegisters {
grx_index: Port<u8>,
grx_data: Port<u8>,
}
impl GraphicsControllerRegisters {
pub(crate) fn new() -> GraphicsControllerRegisters {
GraphicsControllerRegisters {
grx_index: Port::new(GRX_INDEX_ADDRESS),
grx_data: Port::new(GRX_DATA_ADDRESS),
}
}
/// Reads the current value from the graphics controller, as specified
/// by `index`.
pub fn read(&mut self, index: GraphicsControllerIndex) -> u8 {
self.set_index(index);
unsafe { self.grx_data.read() }
}
/// Writes the `value` to the graphics controller, as specified
/// by `index.
pub fn write(&mut self, index: GraphicsControllerIndex, value: u8) {
self.set_index(index);
unsafe {
self.grx_data.write(value);
}
}
/// Sets the read plane of the graphics controller, as specified by `read_plane`.
pub fn write_read_plane(&mut self, read_plane: ReadPlane) {
let read_plane = u8::from(read_plane) & 0x3;
self.write(GraphicsControllerIndex::ReadPlaneSelect, read_plane);
}
/// Sets the value to use for `GraphicsControllerIndex::SetReset`,
/// as spcified by `color`.
pub fn write_set_reset(&mut self, color: Color16) {
let original_value = self.read(GraphicsControllerIndex::SetReset) & 0xF0;
self.write(
GraphicsControllerIndex::SetReset,
original_value | u8::from(color),
);
}
/// Sets which bits are effected by `GraphicsControllerIndex::SetReset`,
/// as specified by `bit_mask`.
pub fn write_enable_set_reset(&mut self, bit_mask: u8) {
let original_value = self.read(GraphicsControllerIndex::EnableSetReset) & 0xF0;
self.write(
GraphicsControllerIndex::EnableSetReset,
original_value | bit_mask,
);
}
/// Sets which mode the vga writes in, as specified by `write_mode`.
pub fn set_write_mode(&mut self, write_mode: WriteMode) {
let original_value = self.read(GraphicsControllerIndex::GraphicsMode) & 0xFC;
self.write(
GraphicsControllerIndex::GraphicsMode,
original_value | u8::from(write_mode),
);
}
/// Sets which bits are effected by certain operations, as specified
/// by `bit_mask`.
pub fn set_bit_mask(&mut self, bit_mask: u8) {
self.write(GraphicsControllerIndex::BitMask, bit_mask);
}
fn set_index(&mut self, index: GraphicsControllerIndex) {
unsafe {
self.grx_index.write(u8::from(index));
}
}
}

64
src/registers/mod.rs Normal file
View file

@ -0,0 +1,64 @@
//! Common registers used in vga programming.
mod attribute_controller;
mod color_palette;
mod crtc_controller;
mod general;
mod graphics_controller;
mod sequencer;
use crate::colors::{Color16, PALETTE_SIZE};
pub use attribute_controller::{AttributeControllerIndex, AttributeControllerRegisters};
pub use color_palette::ColorPaletteRegisters;
pub use crtc_controller::{CrtcControllerIndex, CrtcControllerRegisters};
pub use general::GeneralRegisters;
pub use graphics_controller::{GraphicsControllerIndex, GraphicsControllerRegisters, WriteMode};
pub use sequencer::{PlaneMask, SequencerIndex, SequencerRegisters};
const ST00_READ_ADDRESS: u16 = 0x3C2;
const ST01_READ_CGA_ADDRESS: u16 = 0x3DA;
const ST01_READ_MDA_ADDRESS: u16 = 0x3BA;
const FCR_READ_ADDRESS: u16 = 0x3CA;
const FCR_CGA_WRITE_ADDRESS: u16 = 0x3DA;
const FCR_MDA_WRITE_ADDRESS: u16 = 0x3BA;
const MSR_READ_ADDRESS: u16 = 0x3CC;
const MSR_WRITE_ADDRESS: u16 = 0x3C2;
const SRX_INDEX_ADDRESS: u16 = 0x3C4;
const SRX_DATA_ADDRESS: u16 = 0x3C5;
const GRX_INDEX_ADDRESS: u16 = 0x3CE;
const GRX_DATA_ADDRESS: u16 = 0x3CF;
const ARX_INDEX_ADDRESS: u16 = 0x3C0;
const ARX_DATA_ADDRESS: u16 = 0x3C1;
const CRX_INDEX_CGA_ADDRESS: u16 = 0x3D4;
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;
/// Represents a vga emulation mode.
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum EmulationMode {
/// Represents a monochrome emulation mode.
Mda = 0x0,
/// Respresents a color emulation mode.
Cga = 0x1,
}
impl From<u8> for EmulationMode {
fn from(value: u8) -> EmulationMode {
match value {
0x0 => EmulationMode::Mda,
0x1 => EmulationMode::Cga,
_ => panic!("{} is an invalid emulation mode", value),
}
}
}

111
src/registers/sequencer.rs Normal file
View file

@ -0,0 +1,111 @@
use super::{SRX_DATA_ADDRESS, SRX_INDEX_ADDRESS};
use bitflags::bitflags;
use core::convert::TryFrom;
use x86_64::instructions::port::Port;
bitflags! {
/// Represents the plane masks of the `SequencerIndex::PlaneMask` register.
pub struct PlaneMask: u8 {
/// Represents none of the plane masks of vga memory.
const NONE = 0b0000_0000;
/// Represents `Plane0` of vga memory.
const PLANE0 = 0b0000_0001;
/// Represents `Plane1` of vga memory.
const PLANE1 = 0b0000_0010;
/// Represents `Plane2` of vga memory.
const PLANE2 = 0b0000_0100;
/// Represents `Plane3` of vga memory.
const PLANE3 = 0b0000_1000;
/// Represents all of the plane masks of vga memory.
const ALL_PLANES = Self::PLANE0.bits() | Self::PLANE1.bits() | Self::PLANE2.bits() | Self::PLANE3.bits();
}
}
impl TryFrom<u8> for PlaneMask {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(PlaneMask::PLANE0),
1 => Ok(PlaneMask::PLANE1),
2 => Ok(PlaneMask::PLANE2),
3 => Ok(PlaneMask::PLANE3),
_ => Err("PlaneMask only accepts values between 0-3!"),
}
}
}
impl From<PlaneMask> for u8 {
fn from(value: PlaneMask) -> u8 {
value.bits()
}
}
/// Represents an index for the seqeuncer registers.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum SequencerIndex {
/// Represents the `Sequencer Reset` register index.
SequencerReset = 0x0,
/// Represents the `Clocking Mode` register index.
ClockingMode = 0x1,
/// Represents the `Plane/Map` mask register index.
PlaneMask = 0x2,
/// Represents the `Character Font` register index.
CharacterFont = 0x3,
/// Represents the `Memory Mode` register index.
MemoryMode = 0x4,
/// Represents the `Horizontal Character Counter Reset` register index.
CounterReset = 0x7,
}
impl From<SequencerIndex> for u8 {
fn from(value: SequencerIndex) -> u8 {
value as u8
}
}
/// Represents the sequencer registers on vga hardware.
#[derive(Debug)]
pub struct SequencerRegisters {
srx_index: Port<u8>,
srx_data: Port<u8>,
}
impl SequencerRegisters {
pub(crate) fn new() -> SequencerRegisters {
SequencerRegisters {
srx_index: Port::new(SRX_INDEX_ADDRESS),
srx_data: Port::new(SRX_DATA_ADDRESS),
}
}
/// Reads the current value from the sequencer, as specified by `index`.
pub fn read(&mut self, index: SequencerIndex) -> u8 {
self.set_index(index);
unsafe { self.srx_data.read() }
}
/// Writes the `value` to the sequencer, as specified by `index`.
pub fn write(&mut self, index: SequencerIndex, value: u8) {
self.set_index(index);
unsafe {
self.srx_data.write(value);
}
}
/// Sets the plane mask of the sequencer controller, as specified by `plane_mask`.
pub fn set_plane_mask(&mut self, plane_mask: PlaneMask) {
let original_value = self.read(SequencerIndex::PlaneMask);
self.write(
SequencerIndex::PlaneMask,
original_value | u8::from(plane_mask),
);
}
fn set_index(&mut self, index: SequencerIndex) {
unsafe {
self.srx_index.write(u8::from(index));
}
}
}

View file

@ -1,16 +1,15 @@
//! Provides access to the vga graphics card. //! Provides access to the vga graphics card.
use super::{ use super::{
colors::PALETTE_SIZE,
configurations::{ configurations::{
VgaConfiguration, MODE_40X25_CONFIGURATION, MODE_40X50_CONFIGURATION, VgaConfiguration, MODE_40X25_CONFIGURATION, MODE_40X50_CONFIGURATION,
MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION, MODE_640X480X16_CONFIGURATION, MODE_80X25_CONFIGURATION,
}, },
fonts::VgaFont, fonts::VgaFont,
registers::{ registers::{
AttributeControllerIndex, AttributeControllerRegisters, ColorPaletteRegisters, AttributeControllerRegisters, ColorPaletteRegisters, CrtcControllerIndex,
CrtcControllerIndex, CrtcControllerRegisters, EmulationMode, GeneralRegisters, CrtcControllerRegisters, EmulationMode, GeneralRegisters, GraphicsControllerIndex,
GraphicsControllerIndex, GraphicsControllerRegisters, SequencerIndex, SequencerRegisters, GraphicsControllerRegisters, PlaneMask, SequencerIndex, SequencerRegisters,
}, },
}; };
use conquer_once::spin::Lazy; use conquer_once::spin::Lazy;
@ -49,27 +48,6 @@ impl From<FrameBuffer> for 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<Plane> for u8 {
fn from(value: Plane) -> u8 {
value as u8
}
}
/// Represents a specified vga video mode. /// Represents a specified vga video mode.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum VideoMode { pub enum VideoMode {
@ -86,12 +64,18 @@ pub enum VideoMode {
/// Represents a vga graphics card with it's common registers, /// Represents a vga graphics card with it's common registers,
/// as well as the most recent video mode. /// as well as the most recent video mode.
pub struct Vga { pub struct Vga {
general_registers: GeneralRegisters, /// Represents the general registers on vga hardware.
sequencer_registers: SequencerRegisters, pub general_registers: GeneralRegisters,
graphics_controller_registers: GraphicsControllerRegisters, /// Represents the sequencer registers on vga hardware.
attribute_controller_registers: AttributeControllerRegisters, pub sequencer_registers: SequencerRegisters,
crtc_controller_registers: CrtcControllerRegisters, /// Represents the graphics controller registers on vga hardware.
color_palette_registers: ColorPaletteRegisters, pub graphics_controller_registers: GraphicsControllerRegisters,
/// Represents the attribute controller registers on vga hardware.
pub attribute_controller_registers: AttributeControllerRegisters,
/// Represents the crtc controller registers on vga hardware.
pub crtc_controller_registers: CrtcControllerRegisters,
/// Represents the color palette registers on vga hardware.
pub color_palette_registers: ColorPaletteRegisters,
most_recent_video_mode: Option<VideoMode>, most_recent_video_mode: Option<VideoMode>,
} }
@ -134,74 +118,11 @@ impl Vga {
self.most_recent_video_mode 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. /// Returns the current `EmulationMode` as determined by the miscellaneous output register.
pub fn get_emulation_mode(&mut self) -> EmulationMode { pub fn get_emulation_mode(&mut self) -> EmulationMode {
EmulationMode::from(self.general_registers.read_msr() & 0x1) 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`. /// Loads a vga text mode font as specified by `vga_font`.
pub fn load_font(&mut self, vga_font: &VgaFont) { pub fn load_font(&mut self, vga_font: &VgaFont) {
// Save registers // Save registers
@ -226,7 +147,7 @@ impl Vga {
); );
// Write font to plane // Write font to plane
self.set_plane(Plane::Plane2); self.sequencer_registers.set_plane_mask(PlaneMask::PLANE2);
let frame_buffer = u32::from(self.get_frame_buffer()) as *mut u8; let frame_buffer = u32::from(self.get_frame_buffer()) as *mut u8;
@ -286,18 +207,6 @@ impl Vga {
) )
} }
/// 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) { fn set_registers(&mut self, configuration: &VgaConfiguration) {
let emulation_mode = self.get_emulation_mode(); let emulation_mode = self.get_emulation_mode();

View file

@ -1,13 +1,17 @@
use super::GraphicsWriter;
use crate::{ use crate::{
colors::{Color16Bit, DEFAULT_PALETTE}, colors::{Color16, DEFAULT_PALETTE},
vga::{Plane, Vga, VideoMode, VGA}, drawing::{Bresenham, Point},
registers::{PlaneMask, WriteMode},
vga::{Vga, VideoMode, VGA},
}; };
use font8x8::UnicodeFonts;
use spinning_top::SpinlockGuard; use spinning_top::SpinlockGuard;
const WIDTH: usize = 640; const WIDTH: usize = 640;
const HEIGHT: usize = 480; const HEIGHT: usize = 480;
const ALL_PLANES_SCREEN_SIZE: usize = (WIDTH * HEIGHT) / 8;
static PLANES: &[Plane] = &[Plane::Plane0, Plane::Plane1, Plane::Plane2, Plane::Plane3]; const WIDTH_IN_BYTES: usize = WIDTH / 8;
/// A basic interface for interacting with vga graphics mode 640x480x16 /// A basic interface for interacting with vga graphics mode 640x480x16
/// ///
@ -16,69 +20,95 @@ static PLANES: &[Plane] = &[Plane::Plane0, Plane::Plane1, Plane::Plane2, Plane::
/// Basic usage: /// Basic usage:
/// ///
/// ```no_run /// ```no_run
/// use vga::writers::Graphics640x480x16; /// use vga::colors::Color16;
/// use vga::writers::{GraphicsWriter, Graphics640x480x16};
/// ///
/// let graphics_mode = Graphics640x480x16::new(); /// let graphics_mode = Graphics640x480x16::new();
/// ///
/// graphics_mode.set_mode(); /// graphics_mode.set_mode();
/// graphics_mode.clear_screen(); /// graphics_mode.clear_screen(Color16::Black);
/// ``` /// ```
#[derive(Default)] #[derive(Default)]
pub struct Graphics640x480x16; pub struct Graphics640x480x16;
impl GraphicsWriter<Color16> for Graphics640x480x16 {
fn clear_screen(&self, color: Color16) {
self.set_write_mode_2();
let (_vga, frame_buffer) = self.get_frame_buffer();
for offset in 0..ALL_PLANES_SCREEN_SIZE {
unsafe {
frame_buffer.add(offset).write_volatile(u8::from(color));
}
}
}
fn draw_line(&self, start: Point<isize>, end: Point<isize>, color: Color16) {
self.set_write_mode_0(color);
for (x, y) in Bresenham::new(start, end) {
self._set_pixel(x as usize, y as usize, color);
}
}
fn draw_character(&self, x: usize, y: usize, character: char, color: Color16) {
self.set_write_mode_2();
let character = match font8x8::BASIC_FONTS.get(character) {
Some(character) => character,
// Default to a filled block if the character isn't found
None => font8x8::unicode::BLOCK_UNICODE[8].byte_array(),
};
for (row, byte) in character.iter().enumerate() {
for bit in 0..8 {
match *byte & 1 << bit {
0 => (),
_ => self._set_pixel(x + bit, y + row, color),
}
}
}
}
fn set_pixel(&self, x: usize, y: usize, color: Color16) {
self.set_write_mode_2();
self._set_pixel(x, y, color);
}
fn set_mode(&self) {
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.color_palette_registers.load_palette(&DEFAULT_PALETTE);
}
}
impl Graphics640x480x16 { impl Graphics640x480x16 {
/// Creates a new `Graphics640x480x16`. /// Creates a new `Graphics640x480x16`.
pub fn new() -> Graphics640x480x16 { pub fn new() -> Graphics640x480x16 {
Graphics640x480x16 {} Graphics640x480x16 {}
} }
/// Clears the screen by setting all pixels to `Color16Bit::Black`. /// Sets the vga to 'WriteMode::Mode0`. This also sets `GraphicsControllerIndex::SetReset`
pub fn clear_screen(&self) { /// to the specified `color`, `GraphicsControllerIndex::EnableSetReset` to `0xFF` and
// TODO: Clear the screen by using 4-plane mode instead of slow `set_pixel`. /// `SequencerIndex::PlaneMask` to `PlaneMask::ALL_PLANES`.
for x in 0..WIDTH { pub fn set_write_mode_0(&self, color: Color16) {
for y in 0..HEIGHT { let (mut vga, _frame_buffer) = self.get_frame_buffer();
self.set_pixel(x, y, Color16Bit::Yellow); vga.graphics_controller_registers.write_set_reset(color);
} vga.graphics_controller_registers
} .write_enable_set_reset(0xF);
vga.graphics_controller_registers
.set_write_mode(WriteMode::Mode0);
} }
/// Sets the given pixel at `(x, y)` to the given `color`. /// Sets the vga to `WriteMode::Mode2`. This also sets the `GraphicsControllerIndex::BitMask`
/// /// to `0xFF` and `SequencerIndex::PlaneMask` to `PlaneMask::ALL_PLANES`.
/// Panics if `x >= 640` or `y >= 480`. pub fn set_write_mode_2(&self) {
pub fn set_pixel(&self, x: usize, y: usize, color: Color16Bit) { let (mut vga, _frame_buffer) = self.get_frame_buffer();
assert!(x < WIDTH, "x >= {}", WIDTH); vga.graphics_controller_registers
assert!(y < HEIGHT, "y >= {}", HEIGHT); .set_write_mode(WriteMode::Mode2);
let (mut vga, frame_buffer) = self.get_frame_buffer(); vga.graphics_controller_registers.set_bit_mask(0xFF);
let offset = x / 8 + (WIDTH / 8) * y; vga.sequencer_registers
.set_plane_mask(PlaneMask::ALL_PLANES);
// Store the current value for masking.
let x = x & 7;
let mask = 0x80 >> (x & 7);
let mut plane_mask = 0x01;
for plane in PLANES {
vga.set_plane(*plane);
let current_value = unsafe { frame_buffer.add(offset).read_volatile() };
let new_value = if plane_mask & color as u8 != 0 {
current_value | mask
} else {
current_value & !mask
};
unsafe {
frame_buffer.add(offset).write_volatile(new_value);
}
plane_mask <<= 1;
}
}
/// Sets the graphics device to `VideoMode::Mode640x480x16`.
pub fn set_mode(&self) {
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 /// Returns the start of the `FrameBuffer` as `*mut u8` as
@ -89,4 +119,16 @@ impl Graphics640x480x16 {
let frame_buffer = vga.get_frame_buffer(); let frame_buffer = vga.get_frame_buffer();
(vga, u32::from(frame_buffer) as *mut u8) (vga, u32::from(frame_buffer) as *mut u8)
} }
#[inline]
fn _set_pixel(&self, x: usize, y: usize, color: Color16) {
let (mut vga, frame_buffer) = self.get_frame_buffer();
let offset = x / 8 + y * WIDTH_IN_BYTES;
let pixel_mask = 0x80 >> (x & 0x07);
vga.graphics_controller_registers.set_bit_mask(pixel_mask);
unsafe {
frame_buffer.add(offset).read_volatile();
frame_buffer.add(offset).write_volatile(u8::from(color));
}
}
} }

View file

@ -5,7 +5,8 @@ mod text_40x50;
mod text_80x25; mod text_80x25;
use super::{ use super::{
colors::{Color16Bit, TextModeColor}, colors::{Color16, TextModeColor},
drawing::Point,
registers::CrtcControllerIndex, registers::CrtcControllerIndex,
vga::{Vga, VGA}, vga::{Vga, VGA},
}; };
@ -43,17 +44,21 @@ impl ScreenCharacter {
static BLANK_CHARACTER: ScreenCharacter = ScreenCharacter { static BLANK_CHARACTER: ScreenCharacter = ScreenCharacter {
character: b' ', character: b' ',
color: TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black), color: TextModeColor::new(Color16::Yellow, Color16::Black),
}; };
/// A helper trait used to interact with various vga text modes. /// A helper trait used to interact with various vga screens.
pub trait TextWriter { pub trait Screen {
/// Returns the width of the `TextWriter`. /// Returns the width of the `Screen`.
fn get_width(&self) -> usize; fn get_width(&self) -> usize;
/// Returns the height of the `Screen`.
/// Returns the height of the `TextWriter`.
fn get_height(&self) -> usize; fn get_height(&self) -> usize;
/// Returns the size of the `Screen`.
fn get_size(&self) -> usize;
}
/// A helper trait used to interact with various vga text modes.
pub trait TextWriter: Screen {
/// Sets the graphics device to a video mode as determined by /// Sets the graphics device to a video mode as determined by
/// the `TextWriter` implementation. /// the `TextWriter` implementation.
fn set_mode(&self); fn set_mode(&self);
@ -68,8 +73,8 @@ pub trait TextWriter {
} }
/// Clears the screen by setting all cells to `b' '` with /// Clears the screen by setting all cells to `b' '` with
/// a background color of `Color16Bit::Black` and a foreground /// a background color of `Color16::Black` and a foreground
/// color of `Color16Bit::Yellow`. /// color of `Color16::Yellow`.
fn clear_screen(&self) { fn clear_screen(&self) {
let (_vga, frame_buffer) = self.get_frame_buffer(); let (_vga, frame_buffer) = self.get_frame_buffer();
let screen_size = self.get_width() * self.get_height(); let screen_size = self.get_width() * self.get_height();
@ -84,9 +89,10 @@ pub trait TextWriter {
fn disable_cursor(&self) { fn disable_cursor(&self) {
let (mut vga, _frame_buffer) = self.get_frame_buffer(); let (mut vga, _frame_buffer) = self.get_frame_buffer();
let emulation_mode = vga.get_emulation_mode(); let emulation_mode = vga.get_emulation_mode();
let cursor_start = let cursor_start = vga
vga.read_crtc_controller(emulation_mode, CrtcControllerIndex::TextCursorStart); .crtc_controller_registers
vga.write_crtc_controller( .read(emulation_mode, CrtcControllerIndex::TextCursorStart);
vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorStart, CrtcControllerIndex::TextCursorStart,
cursor_start | 0x20, cursor_start | 0x20,
@ -97,9 +103,10 @@ pub trait TextWriter {
fn enable_cursor(&self) { fn enable_cursor(&self) {
let (mut vga, _frame_buffer) = self.get_frame_buffer(); let (mut vga, _frame_buffer) = self.get_frame_buffer();
let emulation_mode = vga.get_emulation_mode(); let emulation_mode = vga.get_emulation_mode();
let cursor_start = let cursor_start = vga
vga.read_crtc_controller(emulation_mode, CrtcControllerIndex::TextCursorStart); .crtc_controller_registers
vga.write_crtc_controller( .read(emulation_mode, CrtcControllerIndex::TextCursorStart);
vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorStart, CrtcControllerIndex::TextCursorStart,
cursor_start & 0xDF, cursor_start & 0xDF,
@ -122,16 +129,20 @@ pub trait TextWriter {
fn set_cursor(&self, scan_line_start: u8, scan_line_end: u8) { fn set_cursor(&self, scan_line_start: u8, scan_line_end: u8) {
let (mut vga, _frame_buffer) = self.get_frame_buffer(); let (mut vga, _frame_buffer) = self.get_frame_buffer();
let emulation_mode = vga.get_emulation_mode(); let emulation_mode = vga.get_emulation_mode();
let cursor_start = let cursor_start = vga
vga.read_crtc_controller(emulation_mode, CrtcControllerIndex::TextCursorStart) & 0xC0; .crtc_controller_registers
let cursor_end = .read(emulation_mode, CrtcControllerIndex::TextCursorStart)
vga.read_crtc_controller(emulation_mode, CrtcControllerIndex::TextCursorEnd) & 0xE0; & 0xC0;
vga.write_crtc_controller( let cursor_end = vga
.crtc_controller_registers
.read(emulation_mode, CrtcControllerIndex::TextCursorEnd)
& 0xE0;
vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorStart, CrtcControllerIndex::TextCursorStart,
cursor_start | scan_line_start, cursor_start | scan_line_start,
); );
vga.write_crtc_controller( vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorEnd, CrtcControllerIndex::TextCursorEnd,
cursor_end | scan_line_end, cursor_end | scan_line_end,
@ -146,12 +157,12 @@ pub trait TextWriter {
let emulation_mode = vga.get_emulation_mode(); let emulation_mode = vga.get_emulation_mode();
let cursor_start = offset & 0xFF; let cursor_start = offset & 0xFF;
let cursor_end = (offset >> 8) & 0xFF; let cursor_end = (offset >> 8) & 0xFF;
vga.write_crtc_controller( vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorLocationLow, CrtcControllerIndex::TextCursorLocationLow,
cursor_start as u8, cursor_start as u8,
); );
vga.write_crtc_controller( vga.crtc_controller_registers.write(
emulation_mode, emulation_mode,
CrtcControllerIndex::TextCursorLocationHigh, CrtcControllerIndex::TextCursorLocationHigh,
cursor_end as u8, cursor_end as u8,
@ -167,3 +178,22 @@ pub trait TextWriter {
} }
} }
} }
/// A helper trait used to interact with various vga graphics modes.
pub trait GraphicsWriter<Color> {
/// Clears the screen by setting all pixels to the specified `color`.
fn clear_screen(&self, color: Color);
/// /// Draws a line from `start` to `end` with the specified `color`.
fn draw_line(&self, start: Point<isize>, end: Point<isize>, color: Color);
/// Draws a character at the given `(x, y)` coordinant to the specified `color`.
fn draw_character(&self, x: usize, y: usize, character: char, color: Color);
/// Sets the given pixel at `(x, y)` to the given `color`.
///
/// **Note:** This method is provided for convenience, but has terrible
/// performance since it needs to ensure the correct `WriteMode` per pixel
/// drawn. If you need to draw more then one pixel, consider using a method
/// such as `draw_line`.
fn set_pixel(&self, x: usize, y: usize, color: Color);
/// Sets the graphics device to a `VideoMode`.
fn set_mode(&self);
}

View file

@ -1,4 +1,4 @@
use super::TextWriter; use super::{Screen, TextWriter};
use crate::{ use crate::{
colors::DEFAULT_PALETTE, colors::DEFAULT_PALETTE,
fonts::TEXT_8X16_FONT, fonts::TEXT_8X16_FONT,
@ -15,11 +15,11 @@ const HEIGHT: usize = 25;
/// Basic usage: /// Basic usage:
/// ///
/// ```no_run /// ```no_run
/// use vga::colors::{Color16Bit, TextModeColor}; /// use vga::colors::{Color16, TextModeColor};
/// use vga::writers::{ScreenCharacter, TextWriter, Text40x25}; /// use vga::writers::{ScreenCharacter, TextWriter, Text40x25};
/// ///
/// let text_mode = Text40x25::new(); /// let text_mode = Text40x25::new();
/// let color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); /// let color = TextModeColor::new(Color16::Yellow, Color16::Black);
/// let screen_character = ScreenCharacter::new(b'T', color); /// let screen_character = ScreenCharacter::new(b'T', color);
/// ///
/// text_mode.set_mode(); /// text_mode.set_mode();
@ -29,7 +29,7 @@ const HEIGHT: usize = 25;
#[derive(Default)] #[derive(Default)]
pub struct Text40x25; pub struct Text40x25;
impl TextWriter for Text40x25 { impl Screen for Text40x25 {
fn get_width(&self) -> usize { fn get_width(&self) -> usize {
WIDTH WIDTH
} }
@ -38,6 +38,12 @@ impl TextWriter for Text40x25 {
HEIGHT HEIGHT
} }
fn get_size(&self) -> usize {
WIDTH * HEIGHT
}
}
impl TextWriter for Text40x25 {
/// Sets the graphics device to `VideoMode::Mode40x25`. /// Sets the graphics device to `VideoMode::Mode40x25`.
fn set_mode(&self) { fn set_mode(&self) {
let mut vga = VGA.lock(); let mut vga = VGA.lock();
@ -45,7 +51,7 @@ impl TextWriter for Text40x25 {
// Some bios mess up the palette when switching modes, // Some bios mess up the palette when switching modes,
// so explicitly set it. // so explicitly set it.
vga.load_palette(&DEFAULT_PALETTE); vga.color_palette_registers.load_palette(&DEFAULT_PALETTE);
vga.load_font(&TEXT_8X16_FONT); vga.load_font(&TEXT_8X16_FONT);
} }
} }

View file

@ -1,4 +1,4 @@
use super::TextWriter; use super::{Screen, TextWriter};
use crate::{ use crate::{
colors::DEFAULT_PALETTE, colors::DEFAULT_PALETTE,
fonts::TEXT_8X8_FONT, fonts::TEXT_8X8_FONT,
@ -15,11 +15,11 @@ const HEIGHT: usize = 50;
/// Basic usage: /// Basic usage:
/// ///
/// ```no_run /// ```no_run
/// use vga::colors::{Color16Bit, TextModeColor}; /// use vga::colors::{Color16, TextModeColor};
/// use vga::writers::{ScreenCharacter, TextWriter, Text40x50}; /// use vga::writers::{ScreenCharacter, TextWriter, Text40x50};
/// ///
/// let text_mode = Text40x50::new(); /// let text_mode = Text40x50::new();
/// let color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); /// let color = TextModeColor::new(Color16::Yellow, Color16::Black);
/// let screen_character = ScreenCharacter::new(b'T', color); /// let screen_character = ScreenCharacter::new(b'T', color);
/// ///
/// text_mode.set_mode(); /// text_mode.set_mode();
@ -29,7 +29,7 @@ const HEIGHT: usize = 50;
#[derive(Default)] #[derive(Default)]
pub struct Text40x50; pub struct Text40x50;
impl TextWriter for Text40x50 { impl Screen for Text40x50 {
fn get_width(&self) -> usize { fn get_width(&self) -> usize {
WIDTH WIDTH
} }
@ -38,6 +38,12 @@ impl TextWriter for Text40x50 {
HEIGHT HEIGHT
} }
fn get_size(&self) -> usize {
WIDTH * HEIGHT
}
}
impl TextWriter for Text40x50 {
/// Sets the graphics device to `VideoMode::Mode40x50`. /// Sets the graphics device to `VideoMode::Mode40x50`.
fn set_mode(&self) { fn set_mode(&self) {
let mut vga = VGA.lock(); let mut vga = VGA.lock();
@ -45,7 +51,7 @@ impl TextWriter for Text40x50 {
// Some bios mess up the palette when switching modes, // Some bios mess up the palette when switching modes,
// so explicitly set it. // so explicitly set it.
vga.load_palette(&DEFAULT_PALETTE); vga.color_palette_registers.load_palette(&DEFAULT_PALETTE);
vga.load_font(&TEXT_8X8_FONT); vga.load_font(&TEXT_8X8_FONT);
} }
} }

View file

@ -1,4 +1,4 @@
use super::TextWriter; use super::{Screen, TextWriter};
use crate::{ use crate::{
colors::DEFAULT_PALETTE, colors::DEFAULT_PALETTE,
fonts::TEXT_8X16_FONT, fonts::TEXT_8X16_FONT,
@ -15,11 +15,11 @@ const HEIGHT: usize = 25;
/// Basic usage: /// Basic usage:
/// ///
/// ```no_run /// ```no_run
/// use vga::colors::{Color16Bit, TextModeColor}; /// use vga::colors::{Color16, TextModeColor};
/// use vga::writers::{ScreenCharacter, TextWriter, Text80x25}; /// use vga::writers::{ScreenCharacter, TextWriter, Text80x25};
/// ///
/// let text_mode = Text80x25::new(); /// let text_mode = Text80x25::new();
/// let color = TextModeColor::new(Color16Bit::Yellow, Color16Bit::Black); /// let color = TextModeColor::new(Color16::Yellow, Color16::Black);
/// let screen_character = ScreenCharacter::new(b'T', color); /// let screen_character = ScreenCharacter::new(b'T', color);
/// ///
/// text_mode.set_mode(); /// text_mode.set_mode();
@ -29,7 +29,7 @@ const HEIGHT: usize = 25;
#[derive(Default)] #[derive(Default)]
pub struct Text80x25; pub struct Text80x25;
impl TextWriter for Text80x25 { impl Screen for Text80x25 {
fn get_width(&self) -> usize { fn get_width(&self) -> usize {
WIDTH WIDTH
} }
@ -38,13 +38,19 @@ impl TextWriter for Text80x25 {
HEIGHT HEIGHT
} }
fn get_size(&self) -> usize {
WIDTH * HEIGHT
}
}
impl TextWriter for Text80x25 {
fn set_mode(&self) { fn set_mode(&self) {
let mut vga = VGA.lock(); let mut vga = VGA.lock();
vga.set_video_mode(VideoMode::Mode80x25); vga.set_video_mode(VideoMode::Mode80x25);
// Some bios mess up the palette when switching modes, // Some bios mess up the palette when switching modes,
// so explicitly set it. // so explicitly set it.
vga.load_palette(&DEFAULT_PALETTE); vga.color_palette_registers.load_palette(&DEFAULT_PALETTE);
vga.load_font(&TEXT_8X16_FONT); vga.load_font(&TEXT_8X16_FONT);
} }
} }

View file

@ -83,8 +83,8 @@ fn load_palette() {
let mut palette = [0u8; PALETTE_SIZE]; let mut palette = [0u8; PALETTE_SIZE];
let mut vga = VGA.lock(); let mut vga = VGA.lock();
vga.load_palette(&DEFAULT_PALETTE); vga.color_palette_registers.load_palette(&DEFAULT_PALETTE);
vga.read_palette(&mut palette); vga.color_palette_registers.read_palette(&mut palette);
for i in 0..PALETTE_SIZE { for i in 0..PALETTE_SIZE {
assert_eq!(palette[i], DEFAULT_PALETTE[i]); assert_eq!(palette[i], DEFAULT_PALETTE[i]);
@ -95,23 +95,30 @@ fn load_palette() {
fn check_registers(vga: &mut Vga, configuration: &VgaConfiguration) { fn check_registers(vga: &mut Vga, configuration: &VgaConfiguration) {
let emulation_mode = vga.get_emulation_mode(); let emulation_mode = vga.get_emulation_mode();
assert_eq!(vga.read_msr(), configuration.miscellaneous_output); assert_eq!(
vga.general_registers.read_msr(),
configuration.miscellaneous_output
);
for (index, value) in configuration.sequencer_registers { for (index, value) in configuration.sequencer_registers {
assert_eq!(vga.read_sequencer(*index), *value); assert_eq!(vga.sequencer_registers.read(*index), *value);
} }
for (index, value) in configuration.crtc_controller_registers { for (index, value) in configuration.crtc_controller_registers {
assert_eq!(vga.read_crtc_controller(emulation_mode, *index), *value); assert_eq!(
vga.crtc_controller_registers.read(emulation_mode, *index),
*value
);
} }
for (index, value) in configuration.graphics_controller_registers { for (index, value) in configuration.graphics_controller_registers {
assert_eq!(vga.read_graphics_controller(*index), *value); assert_eq!(vga.graphics_controller_registers.read(*index), *value);
} }
for (index, value) in configuration.attribute_controller_registers { for (index, value) in configuration.attribute_controller_registers {
assert_eq!( assert_eq!(
vga.read_attribute_controller(emulation_mode, *index), vga.attribute_controller_registers
.read(emulation_mode, *index),
*value *value
); );
} }