diff --git a/Cargo.toml b/Cargo.toml index 21a2ead30b..8b963f2acb 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..333d100ec6 --- /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: {bga_version:#06x}"); + 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 8f1c32a125..e5483db1b1 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -15,6 +15,8 @@ use crate::env::{self, is_uhyve}; #[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..71dcbbcf2a 100644 --- a/src/drivers/pci.rs +++ b/src/drivers/pci.rs @@ -459,6 +459,19 @@ 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 +}