From 55c43547fa99d55d1fa92afa9a45947a5acb7731 Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Tue, 23 Jun 2026 13:26:27 +0200 Subject: [PATCH 1/3] add Bochs VBE linear framebuffer support --- Cargo.toml | 4 ++ src/arch/x86_64/kernel/bga.rs | 91 +++++++++++++++++++++++++++++++++++ src/arch/x86_64/kernel/mod.rs | 2 + src/drivers/pci.rs | 14 ++++++ src/syscalls/system.rs | 15 ++++++ 5 files changed, 126 insertions(+) create mode 100644 src/arch/x86_64/kernel/bga.rs diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..1aae110e1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -216,6 +216,10 @@ 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). +bga = ["pci"] #! ### Performance Features ## Disables putting the CPU to sleep. diff --git a/src/arch/x86_64/kernel/bga.rs b/src/arch/x86_64/kernel/bga.rs new file mode 100644 index 0000000000..ba0c9b3541 --- /dev/null +++ b/src/arch/x86_64/kernel/bga.rs @@ -0,0 +1,91 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +use memory_addresses::{PhysAddr, VirtAddr}; +use pci_types::{Bar, CommandRegister}; +use x86_64::instructions::port::Port; + +use crate::arch::x86_64::mm::paging; +use crate::arch::x86_64::mm::paging::{BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt}; +use crate::drivers::pci::PciDevice; +use crate::kernel::pci::PciConfigRegion; + +static FRAMEBUFFER_PHYS: AtomicU64 = AtomicU64::new(0); + +const VBE_DISPI_IOPORT_INDEX: u16 = 0x01ce; +const VBE_DISPI_IOPORT_DATA: u16 = 0x01cf; + +const VBE_DISPI_INDEX_ID: u16 = 0; +const VBE_DISPI_INDEX_XRES: u16 = 1; +const VBE_DISPI_INDEX_YRES: u16 = 2; +const VBE_DISPI_INDEX_BPP: u16 = 3; +const VBE_DISPI_INDEX_ENABLE: u16 = 4; + +const VBE_DISPI_DISABLED: u16 = 0x00; +const VBE_DISPI_ENABLED: u16 = 0x01; +const VBE_DISPI_LFB_ENABLED: u16 = 0x40; +const VBE_DISPI_ID5: u16 = 0xb0c5; + +pub fn init_device(adapter: &PciDevice) { + //To Do: Detect Resolution automatically + let width: u16 = 640; + let height: u16 = 400; + let bpp: u16 = 32; + + unsafe { + let mut index_port: Port = Port::new(VBE_DISPI_IOPORT_INDEX); + let mut data_port: Port = Port::new(VBE_DISPI_IOPORT_DATA); + + index_port.write(VBE_DISPI_INDEX_ID); + let bga_version = data_port.read(); + + if bga_version != VBE_DISPI_ID5 { + error!("Unsupported BGA version: {:#06x}", bga_version); + return; + } + + index_port.write(VBE_DISPI_INDEX_ENABLE); + data_port.write(VBE_DISPI_DISABLED); + + index_port.write(VBE_DISPI_INDEX_XRES); + data_port.write(width); + + index_port.write(VBE_DISPI_INDEX_YRES); + data_port.write(height); + + index_port.write(VBE_DISPI_INDEX_BPP); + data_port.write(bpp); + + index_port.write(VBE_DISPI_INDEX_ENABLE); + data_port.write(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, + }; + + FRAMEBUFFER_PHYS.store(phys_addr, Ordering::Release); + + assert!( + size % 4096 == 0, + "Framebuffer size must be a multiple of 4096 bytes" + ); + let page_count = size / 4096; + + let mut flags = PageTableEntryFlags::empty(); + flags.device().writable().execute_disable(); + flags.insert(PageTableEntryFlags::USER_ACCESSIBLE); + paging::map::( + VirtAddr::new(phys_addr), + PhysAddr::new(phys_addr), + page_count, + flags, + ); +} + +pub fn get_framebuffer_address() -> u64 { + FRAMEBUFFER_PHYS.load(Ordering::Acquire) +} diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index e0696d93a6..7d44ae9979 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -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; diff --git a/src/drivers/pci.rs b/src/drivers/pci.rs index a59aaacfe7..924a110ec4 100644 --- a/src/drivers/pci.rs +++ b/src/drivers/pci.rs @@ -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(); }); } diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index de963e3fed..dd78628d79 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -6,3 +6,18 @@ use crate::arch::mm::paging::{BasePageSize, PageSize}; pub extern "C" fn sys_getpagesize() -> i32 { BasePageSize::SIZE.try_into().unwrap() } + +/// Returns the address of the framebuffer, if available. Returns 0 if no framebuffer is available. +#[cfg(all(target_arch = "x86_64", feature = "bga"))] +#[hermit_macro::system] +#[unsafe(no_mangle)] +pub extern "C" fn sys_get_framebuffer() -> u64 { + crate::kernel::bga::get_framebuffer_address() +} + +#[cfg(not(all(target_arch = "x86_64", feature = "bga")))] +#[hermit_macro::system] +#[unsafe(no_mangle)] +pub extern "C" fn sys_get_framebuffer() -> u64 { + 0 +} From 0a53b2dad520f3a28f6e924ac5af5d46dffc187d Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Sun, 5 Jul 2026 19:19:18 +0200 Subject: [PATCH 2/3] style: use variable in format string --- src/arch/x86_64/kernel/bga.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/x86_64/kernel/bga.rs b/src/arch/x86_64/kernel/bga.rs index ba0c9b3541..333d100ec6 100644 --- a/src/arch/x86_64/kernel/bga.rs +++ b/src/arch/x86_64/kernel/bga.rs @@ -39,7 +39,7 @@ pub fn init_device(adapter: &PciDevice) { let bga_version = data_port.read(); if bga_version != VBE_DISPI_ID5 { - error!("Unsupported BGA version: {:#06x}", bga_version); + error!("Unsupported BGA version: {bga_version:#06x}"); return; } From b2a540c11f73fcd55454a9ed5eb0487de21352f6 Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Tue, 14 Jul 2026 17:42:27 +0200 Subject: [PATCH 3/3] Refactor BGA based on review feedback --- Cargo.toml | 3 +- src/arch/x86_64/kernel/bga.rs | 158 +++++++++++++++++++++++++--------- src/syscalls/system.rs | 41 +++++++-- 3 files changed, 150 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1aae110e1b..e150b700f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -218,8 +218,9 @@ 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). +## 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"] + #! ### Performance Features ## Disables putting the CPU to sleep. diff --git a/src/arch/x86_64/kernel/bga.rs b/src/arch/x86_64/kernel/bga.rs index 333d100ec6..93089168f1 100644 --- a/src/arch/x86_64/kernel/bga.rs +++ b/src/arch/x86_64/kernel/bga.rs @@ -1,64 +1,132 @@ -use core::sync::atomic::{AtomicU64, Ordering}; - +//! 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; +use x86_64::instructions::port::{Port, PortWriteOnly}; -use crate::arch::x86_64::mm::paging; -use crate::arch::x86_64::mm::paging::{BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt}; +use crate::arch::x86_64::mm::paging::{ + self, BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt, +}; use crate::drivers::pci::PciDevice; use crate::kernel::pci::PciConfigRegion; -static FRAMEBUFFER_PHYS: AtomicU64 = AtomicU64::new(0); +pub struct BgaInfo { + pub framebuffer: *mut u8, + pub width: u16, + pub height: u16, + pub bpp: u16, +} + +static BGA_INFO: OnceCell = 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; -const VBE_DISPI_INDEX_ID: u16 = 0; -const VBE_DISPI_INDEX_XRES: u16 = 1; -const VBE_DISPI_INDEX_YRES: u16 = 2; -const VBE_DISPI_INDEX_BPP: u16 = 3; -const VBE_DISPI_INDEX_ENABLE: u16 = 4; +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; -const VBE_DISPI_ID5: u16 = 0xb0c5; -pub fn init_device(adapter: &PciDevice) { - //To Do: Detect Resolution automatically - let width: u16 = 640; - let height: u16 = 400; - let bpp: u16 = 32; - - unsafe { - let mut index_port: Port = Port::new(VBE_DISPI_IOPORT_INDEX); - let mut data_port: Port = Port::new(VBE_DISPI_IOPORT_DATA); +#[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; +} - index_port.write(VBE_DISPI_INDEX_ID); - let bga_version = data_port.read(); +struct BgaRegisters; - if bga_version != VBE_DISPI_ID5 { - error!("Unsupported BGA version: {bga_version:#06x}"); - return; +impl BgaRegisters { + pub fn read(index: u16) -> u16 { + let mut index_port: PortWriteOnly = PortWriteOnly::new(VBE_DISPI_IOPORT_INDEX); + let mut data_port: Port = Port::new(VBE_DISPI_IOPORT_DATA); + unsafe { + index_port.write(index); + data_port.read() } + } - index_port.write(VBE_DISPI_INDEX_ENABLE); - data_port.write(VBE_DISPI_DISABLED); - - index_port.write(VBE_DISPI_INDEX_XRES); - data_port.write(width); + pub fn write(index: u16, value: u16) { + let mut index_port: PortWriteOnly = PortWriteOnly::new(VBE_DISPI_IOPORT_INDEX); + let mut data_port: Port = Port::new(VBE_DISPI_IOPORT_DATA); + unsafe { + index_port.write(index); + data_port.write(value); + } + } +} - index_port.write(VBE_DISPI_INDEX_YRES); - data_port.write(height); +pub fn init_device(adapter: &PciDevice) { + //To Do: Add support for different resolutions and BPP values + let width: u16 = 640; + let height: u16 = 400; + let bpp: u16 = 32; - index_port.write(VBE_DISPI_INDEX_BPP); - data_port.write(bpp); + let bga_version = BgaRegisters::read(VbeDispiIndex::ID); - index_port.write(VBE_DISPI_INDEX_ENABLE); - data_port.write(VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED); + 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) { @@ -67,17 +135,23 @@ pub fn init_device(adapter: &PciDevice) { _ => return, }; - FRAMEBUFFER_PHYS.store(phys_addr, Ordering::Release); + BGA_INFO + .set(BgaInfo { + framebuffer: core::ptr::with_exposed_provenance_mut(phys_addr as usize), + width, + height, + bpp, + }) + .ok(); assert!( - size % 4096 == 0, + 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(); - flags.insert(PageTableEntryFlags::USER_ACCESSIBLE); paging::map::( VirtAddr::new(phys_addr), PhysAddr::new(phys_addr), @@ -86,6 +160,6 @@ pub fn init_device(adapter: &PciDevice) { ); } -pub fn get_framebuffer_address() -> u64 { - FRAMEBUFFER_PHYS.load(Ordering::Acquire) +pub fn get_framebuffer_info() -> Option<&'static BgaInfo> { + BGA_INFO.get() } diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index dd78628d79..06d619d4d6 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -1,4 +1,14 @@ +#[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] @@ -7,17 +17,30 @@ pub extern "C" fn sys_getpagesize() -> i32 { BasePageSize::SIZE.try_into().unwrap() } -/// Returns the address of the framebuffer, if available. Returns 0 if no framebuffer is available. +/// 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 extern "C" fn sys_get_framebuffer() -> u64 { - crate::kernel::bga::get_framebuffer_address() -} +pub unsafe extern "C" fn sys_framebuffer(info: *mut FramebufferInfo) -> c_int { + if info.is_null() { + return -1; + }; -#[cfg(not(all(target_arch = "x86_64", feature = "bga")))] -#[hermit_macro::system] -#[unsafe(no_mangle)] -pub extern "C" fn sys_get_framebuffer() -> u64 { - 0 + 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, + } }