Skip to content
Open
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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ virtio-vsock = ["virtio"]
## This is only useful on PCs (x86-64).
vga = []

## Enables the _Bochs Graphics Adapter_ (BGA) driver.
##
## This provides a linear framebuffer for pixel graphics and is primarily useful in QEMU/Bochs environments (x86-64). It does ## not enable any kernel rendering, applications need to implement their own rendering on top of the framebuffer.
bga = ["pci"]
Comment on lines +219 to +222

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also mention that this does not enable any kernel rendering on the device and that applications must be ported to use this framebuffer.


#! ### Performance Features

## Disables putting the CPU to sleep.
Expand Down
165 changes: 165 additions & 0 deletions src/arch/x86_64/kernel/bga.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//! This module contains the implementation of the Bochs Graphics Adapter (BGA) driver.
//!
//! The driver uses the Bochs VBE Extensions, which use two I/O ports to communicate with the
//! emulated VGA card instead of relying on a 16-bit VBE BIOS. The driver initializes the BGA
//! device, sets the desired resolution and bits per pixel (BPP), and maps the framebuffer
//! into the virtual address space. It also provides a function to retrieve the physical
//! address of the framebuffer.

use hermit_sync::OnceCell;
use memory_addresses::{PhysAddr, VirtAddr};
use pci_types::{Bar, CommandRegister};
use x86_64::instructions::port::{Port, PortWriteOnly};

use crate::arch::x86_64::mm::paging::{
self, BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt,
};
use crate::drivers::pci::PciDevice;
use crate::kernel::pci::PciConfigRegion;

pub struct BgaInfo {
pub framebuffer: *mut u8,
pub width: u16,
pub height: u16,
pub bpp: u16,
}

static BGA_INFO: OnceCell<BgaInfo> = OnceCell::new();

unsafe impl Send for BgaInfo {}
unsafe impl Sync for BgaInfo {}

const VBE_DISPI_IOPORT_INDEX: u16 = 0x01ce;
const VBE_DISPI_IOPORT_DATA: u16 = 0x01cf;

pub struct VbeDispiIndex;

#[allow(dead_code)]
impl VbeDispiIndex {
#[doc(alias = "VBE_DISPI_INDEX_ID")]
pub const ID: u16 = 0;
#[doc(alias = "VBE_DISPI_INDEX_XRES")]
pub const XRES: u16 = 1;
#[doc(alias = "VBE_DISPI_INDEX_YRES")]
pub const YRES: u16 = 2;
#[doc(alias = "VBE_DISPI_INDEX_BPP")]
pub const BPP: u16 = 3;
#[doc(alias = "VBE_DISPI_INDEX_ENABLE")]
pub const ENABLE: u16 = 4;
#[doc(alias = "VBE_DISPI_INDEX_BANK")]
pub const BANK: u16 = 5;
#[doc(alias = "VBE_DISPI_INDEX_VIRT_WIDTH")]
pub const VIRT_WIDTH: u16 = 6;
#[doc(alias = "VBE_DISPI_INDEX_VIRT_HEIGHT")]
pub const VIRT_HEIGHT: u16 = 7;
#[doc(alias = "VBE_DISPI_INDEX_X_OFFSET")]
pub const X_OFFSET: u16 = 8;
#[doc(alias = "VBE_DISPI_INDEX_Y_OFFSET")]
pub const Y_OFFSET: u16 = 9;
}

const VBE_DISPI_DISABLED: u16 = 0x00;
const VBE_DISPI_ENABLED: u16 = 0x01;
const VBE_DISPI_LFB_ENABLED: u16 = 0x40;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vgabios docs seem to call this differently. Where is your name from? 🤔

Suggested change
const VBE_DISPI_LFB_ENABLED: u16 = 0x40;
const VBE_DISPI_VBE_ENABLED: u16 = 0x40;

@GloriousAlpaca GloriousAlpaca Jul 7, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually took that name out of the docs to enable the linear framebuffer On OSDev:

When using a linear framebuffer, the BGA exposes all of the video memory in a single linearly addressable section of memory. The address of the framebuffer is not fixed, and must be read from the first PCI base address register (BAR 0 of device 0x1234:0x1111). To enable the linear framebuffer, use the VBE_DISPI_LFB_ENABLED flag (0x40) when enabling the BGA in conjunction with the VBE_DISPI_ENABLED (0x01) flag.

I might be misunderstanding something though.


#[allow(dead_code)]
const VBE_DISPI_NOCLEARMEM: u16 = 0x80;

pub struct VbeDispiId;

#[allow(dead_code)]
impl VbeDispiId {
#[doc(alias = "VBE_DISPI_ID0")]
pub const ID0: u16 = 0xb0c0;
#[doc(alias = "VBE_DISPI_ID1")]
pub const ID1: u16 = 0xb0c1;
#[doc(alias = "VBE_DISPI_ID2")]
pub const ID2: u16 = 0xb0c2;
#[doc(alias = "VBE_DISPI_ID3")]
pub const ID3: u16 = 0xb0c3;
#[doc(alias = "VBE_DISPI_ID4")]
pub const ID4: u16 = 0xb0c4;
#[doc(alias = "VBE_DISPI_ID5")]
pub const ID5: u16 = 0xb0c5;
}

struct BgaRegisters;

impl BgaRegisters {
pub fn read(index: u16) -> u16 {
let mut index_port: PortWriteOnly<u16> = PortWriteOnly::new(VBE_DISPI_IOPORT_INDEX);
let mut data_port: Port<u16> = Port::new(VBE_DISPI_IOPORT_DATA);
unsafe {
index_port.write(index);
data_port.read()
}
}

pub fn write(index: u16, value: u16) {
let mut index_port: PortWriteOnly<u16> = PortWriteOnly::new(VBE_DISPI_IOPORT_INDEX);
let mut data_port: Port<u16> = Port::new(VBE_DISPI_IOPORT_DATA);
unsafe {
index_port.write(index);
data_port.write(value);
}
}
}

pub fn init_device(adapter: &PciDevice<PciConfigRegion>) {
//To Do: Add support for different resolutions and BPP values
let width: u16 = 640;
let height: u16 = 400;
let bpp: u16 = 32;

let bga_version = BgaRegisters::read(VbeDispiIndex::ID);

if bga_version != VbeDispiId::ID5 {
error!("Unsupported BGA version: {bga_version:#06x}");
return;
}

BgaRegisters::write(VbeDispiIndex::ENABLE, VBE_DISPI_DISABLED);
BgaRegisters::write(VbeDispiIndex::XRES, width);
BgaRegisters::write(VbeDispiIndex::YRES, height);
BgaRegisters::write(VbeDispiIndex::BPP, bpp);
BgaRegisters::write(
VbeDispiIndex::ENABLE,
VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED,
);

adapter.set_command(CommandRegister::MEMORY_ENABLE);

let (phys_addr, size) = match adapter.get_bar(0) {
Some(Bar::Memory32 { address, size, .. }) => (u64::from(address), size as usize),
Some(Bar::Memory64 { address, size, .. }) => (address, size as usize),
_ => return,
};

BGA_INFO
.set(BgaInfo {
framebuffer: core::ptr::with_exposed_provenance_mut(phys_addr as usize),
width,
height,
bpp,
})
.ok();

assert!(
size.is_multiple_of(4096),
"Framebuffer size must be a multiple of 4096 bytes"
);
let page_count = size / 4096;

let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(
VirtAddr::new(phys_addr),
PhysAddr::new(phys_addr),
page_count,
flags,
);
}

pub fn get_framebuffer_info() -> Option<&'static BgaInfo> {
BGA_INFO.get()
}
2 changes: 2 additions & 0 deletions src/arch/x86_64/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::env;
#[cfg(feature = "acpi")]
pub mod acpi;
pub mod apic;
#[cfg(feature = "bga")]
pub mod bga;
pub mod core_local;
pub mod gdt;
pub mod interrupts;
Expand Down
14 changes: 14 additions & 0 deletions src/drivers/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,20 @@ pub(crate) fn init(handlers: &mut InterruptHandlerMap) {
Err(err) => error!("Could not initialize rtl8139 device: {err}"),
}
}

#[cfg(all(target_arch = "x86_64", feature = "bga"))]
for adapter in PCI_DEVICES.finalize().iter().filter(|x| {
let (vendor_id, device_id) = x.id();
vendor_id == 0x1234 && device_id == 0x1111
}) {
info!(
"Found Bochs VGA device with device id {:#x}",
adapter.device_id()
);

crate::kernel::bga::init_device(adapter);
}

PCI_DRIVERS.finalize();
});
}
Expand Down
38 changes: 38 additions & 0 deletions src/syscalls/system.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
#[cfg(all(target_arch = "x86_64", feature = "bga"))]
use core::ffi::c_int;

use crate::arch::mm::paging::{BasePageSize, PageSize};
#[repr(C)]
pub struct FramebufferInfo {
pub framebuffer: *mut u8,
pub width: u32,
pub height: u32,
pub bpp: u32,
}

/// Returns the base page size, in bytes, of the current system.
#[hermit_macro::system]
#[unsafe(no_mangle)]
pub extern "C" fn sys_getpagesize() -> i32 {
BasePageSize::SIZE.try_into().unwrap()
}

/// Returns the framebuffer information for the BGA device, if it has been initialized. Returns 0
/// on success, or -1 if the BGA device has not been initialized.
#[cfg(all(target_arch = "x86_64", feature = "bga"))]
#[hermit_macro::system]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sys_framebuffer(info: *mut FramebufferInfo) -> c_int {
if info.is_null() {
return -1;
};

let bga_info = crate::arch::kernel::bga::get_framebuffer_info();
match bga_info {
Some(bga_info) => {
let info_c = FramebufferInfo {
framebuffer: bga_info.framebuffer,
width: u32::from(bga_info.width),
height: u32::from(bga_info.height),
bpp: u32::from(bga_info.bpp),
};
unsafe {
info.write(info_c);
}
0
}
None => -1,
}
}
Loading