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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ 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 +223

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.

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.

I find "enable any kernel rendering" a little bit too unspecific. Maybe something like:

"Any rendering has to be done by the application; the kernel itself does not output anything on the 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 {}
Comment on lines +29 to +30

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.

Do we really need these impls? I guess Send is ok, since the framebuffer is not thread bound, but the struct isn't Sync in my view.


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;
}
Comment on lines +35 to +59

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.

I think, an enum might be a good alternative here.


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.

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.

I find the LFB term also in the vgabios library: https://github.com/qemu/vgabios/blob/19ea12c230ded95928ecaef0db47a82231c2e485/vbe.h#L310

Looks to me like the VBE_ENABLED variant in that library is either a mistake or a relic from the older VBE mode.


#[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;
}
Comment on lines +71 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This could also be a candidate for an enum.


struct BgaRegisters;

impl BgaRegisters {
pub fn read(index: u16) -> u16 {

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.

When you use an enum for the index, as suggested above, you can make the interface here more robust by only allowing the enum's values. This automatically makes the safety requirements of the port I/O write/read trivial to fulfill.

Same for the write fn.

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);
Comment thread
jounathaen marked this conversation as resolved.

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) {
Comment thread
jounathaen marked this conversation as resolved.
Some(Bar::Memory32 { address, size, .. }) => (u64::from(address), size as usize),
Some(Bar::Memory64 { address, size, .. }) => (address, size as usize),
_ => return,
};

BGA_INFO

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] The BGA_INFO.set() call does not handle the case where the OnceCell is already initialized. This could lead to a silent failure if the driver is initialized multiple times.

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 is actually a kind of valid point, but the solution here is to unwrap() the set, to capture cases where the initialization is executed twice.

.set(BgaInfo {
framebuffer: core::ptr::with_exposed_provenance_mut(phys_addr as usize),
Comment thread
jounathaen marked this conversation as resolved.
width,
height,
bpp,
})
.ok();

assert!(
Comment thread
jounathaen marked this conversation as resolved.
size.is_multiple_of(4096),
"Framebuffer size must be a multiple of 4096 bytes"
);
let page_count = size / 4096;

let mut flags = PageTableEntryFlags::empty();
Comment thread
jounathaen marked this conversation as resolved.
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,
}
Comment on lines +5 to +11

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 be config gated for bga and I'd suggest moving it down, close to the actual framebuffer fn.


/// 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 {

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.

Suggested change
pub unsafe extern "C" fn sys_framebuffer(info: *mut FramebufferInfo) -> c_int {
pub unsafe extern "C" fn sys_get_framebuffer_info(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);
Comment thread
jounathaen marked this conversation as resolved.
}
0
}
None => -1,
}
}
Loading