Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite VGA text driver #637

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ name = "measure_startup_time"
harness = false

[features]
default = ["pci", "pci-ids", "acpi", "fsgsbase", "smp", "tcp", "dhcpv4", "fuse"]
default = ["pci", "pci-ids", "acpi", "fsgsbase", "smp", "tcp", "dhcpv4", "fuse", "vga"]
acpi = []
dhcpv4 = [
"smoltcp",
Expand All @@ -64,7 +64,7 @@ tcp = ["smoltcp", "smoltcp/socket-tcp"]
udp = ["smoltcp", "smoltcp/socket-udp"]
dns = ["smoltcp", "smoltcp/socket-dns"]
trace = []
vga = []
vga = ["vga-text-mode"]
common-os = []
nostd = []
semihosting = ["dep:semihosting"]
Expand Down Expand Up @@ -133,6 +133,7 @@ features = [
free-list = { version = "0.3", features = ["x86_64"] }
multiboot = "0.8"
uart_16550 = "0.3"
vga-text-mode = { path = "../../vga-text-mode", optional = true }
x86 = { version = "0.52", default-features = false }
x86_64 = "0.15"

Expand Down
9 changes: 5 additions & 4 deletions src/arch/x86_64/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,14 @@ pub fn message_output_init() {

pub fn output_message_buf(buf: &[u8]) {
// Output messages to the serial port and VGA screen in unikernel mode.

use alloc::string::String;
COM1.lock().as_mut().unwrap().send(buf);

#[cfg(feature = "vga")]
for &byte in buf {
// vga::write_byte() checks if VGA support has been initialized,
// so we don't need any additional if clause around it.
vga::write_byte(byte);
{
let s = String::from_utf8_lossy(buf);
vga::print(&s);
}
}

Expand Down
158 changes: 37 additions & 121 deletions src/arch/x86_64/kernel/vga.rs
Original file line number Diff line number Diff line change
@@ -1,134 +1,50 @@
use x86::io::*;
use core::alloc::Layout;

use core::fmt::Write;
use hermit_sync::{InterruptOnceCell, InterruptSpinMutex};
use vga_text_mode::{ VgaScreen, FrontBuffer, text_buffer::TextBuffer};
use x86_64::instructions::port::Port;

use crate::arch::x86_64::mm::paging::{BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt};
use crate::arch::x86_64::mm::{paging, PhysAddr, VirtAddr};

const CRT_CONTROLLER_ADDRESS_PORT: u16 = 0x3D4;
const CRT_CONTROLLER_DATA_PORT: u16 = 0x3D5;
const CURSOR_START_REGISTER: u8 = 0x0A;
const CURSOR_DISABLE: u8 = 0x20;

const ATTRIBUTE_BLACK: u8 = 0x00;
const ATTRIBUTE_LIGHTGREY: u8 = 0x07;
const COLS: usize = 80;
const ROWS: usize = 25;
const VGA_BUFFER_ADDRESS: u64 = 0xB8000;

static mut VGA_SCREEN: VgaScreen = VgaScreen::new();

#[derive(Clone, Copy)]
#[repr(C, packed)]
struct VgaCharacter {
character: u8,
attribute: u8,
}

impl VgaCharacter {
const fn new(character: u8, attribute: u8) -> Self {
Self {
character,
attribute,
}
}
}

struct VgaScreen {
buffer: *mut [[VgaCharacter; COLS]; ROWS],
current_col: usize,
current_row: usize,
is_initialized: bool,
}

impl VgaScreen {
const fn new() -> Self {
Self {
buffer: VGA_BUFFER_ADDRESS as *mut _,
current_col: 0,
current_row: 0,
is_initialized: false,
}
}

fn init(&mut self) {
// Identity map the VGA buffer. We only need the first page.
let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(
VirtAddr(VGA_BUFFER_ADDRESS),
PhysAddr(VGA_BUFFER_ADDRESS),
1,
flags,
);
static VGA_SCREEN: InterruptOnceCell<InterruptSpinMutex<VgaScreen<'static>>> = InterruptOnceCell::new();

// Disable the cursor.
unsafe {
outb(CRT_CONTROLLER_ADDRESS_PORT, CURSOR_START_REGISTER);
outb(CRT_CONTROLLER_DATA_PORT, CURSOR_DISABLE);
}

// Clear the screen.
for r in 0..ROWS {
self.clear_row(r);
}

// Initialization done!
self.is_initialized = true;
}
pub fn init() {
let layout = Layout::new::<TextBuffer>();
let virt_addr = FrontBuffer::PHYS_ADDR;

// Identity map the VGA buffer. We only need the first page.
let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(
VirtAddr(virt_addr as _),
PhysAddr(FrontBuffer::PHYS_ADDR as u64),
1,
flags,
);

// Disable the cursor.
unsafe {
const CRT_CONTROLLER_ADDRESS_PORT: u16 = 0x3D4;
const CRT_CONTROLLER_DATA_PORT: u16 = 0x3D5;
const CURSOR_START_REGISTER: u8 = 0x0A;
const CURSOR_DISABLE: u8 = 0x20;

#[inline]
fn clear_row(&mut self, row: usize) {
// Overwrite this row by a bogus character in black.
for c in 0..COLS {
unsafe {
(*self.buffer)[row][c] = VgaCharacter::new(0, ATTRIBUTE_BLACK);
}
}
Port::new(CRT_CONTROLLER_ADDRESS_PORT).write(CURSOR_START_REGISTER);
Port::new(CRT_CONTROLLER_DATA_PORT).write(CURSOR_DISABLE);
}

fn write_byte(&mut self, byte: u8) {
if !self.is_initialized {
return;
}

// Move to the next row if we have a newline character or hit the end of a column.
if byte == b'\n' || self.current_col == COLS {
self.current_col = 0;
self.current_row += 1;
}

// Check if we have hit the end of the screen rows.
if self.current_row == ROWS {
// Shift all rows up by one line, removing the oldest visible screen row.
for r in 1..ROWS {
for c in 0..COLS {
unsafe {
(*self.buffer)[r - 1][c] = (*self.buffer)[r][c];
}
}
}
let front_buffer = unsafe { &mut *(virt_addr as *mut TextBuffer) };
let front_buffer = FrontBuffer::new(front_buffer);

// Clear the last screen row and write to it next time.
self.clear_row(ROWS - 1);
self.current_row = ROWS - 1;
}

if byte != b'\n' {
// Put our character into the VGA screen buffer and advance the column counter.
unsafe {
(*self.buffer)[self.current_row][self.current_col] =
VgaCharacter::new(byte, ATTRIBUTE_LIGHTGREY);
}
self.current_col += 1;
}
}
}

pub fn init() {
unsafe { VGA_SCREEN.init() };
VGA_SCREEN
.set(InterruptSpinMutex::new(VgaScreen::new(front_buffer)))
.unwrap();
}

pub fn write_byte(byte: u8) {
unsafe {
VGA_SCREEN.write_byte(byte);
pub fn print(s: &str) {
if let Some(vga_screen) = VGA_SCREEN.get() {
vga_screen.lock().write_str(s).unwrap();
}
}
4 changes: 2 additions & 2 deletions xtask/src/ci/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ impl Qemu {
let qemu = env::var_os("QEMU").unwrap_or_else(|| format!("qemu-system-{arch}").into());

let qemu = cmd!(sh, "{qemu}")
.args(&["-display", "none"])
.args(&["-serial", "stdio"])
// .args(&["-display", "none"])
// .args(&["-serial", "stdio"])
.args(&["-kernel", format!("hermit-loader-{arch}").as_ref()])
.args(self.machine_args())
.args(self.cpu_args())
Expand Down
Loading