diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..e150b700f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] + #! ### 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..93089168f1 --- /dev/null +++ b/src/arch/x86_64/kernel/bga.rs @@ -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 = 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; + +#[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 = 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() + } + } + + 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); + } + } +} + +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; + + 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::( + VirtAddr::new(phys_addr), + PhysAddr::new(phys_addr), + page_count, + flags, + ); +} + +pub fn get_framebuffer_info() -> Option<&'static BgaInfo> { + BGA_INFO.get() +} 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..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] @@ -6,3 +16,31 @@ use crate::arch::mm::paging::{BasePageSize, PageSize}; 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, + } +}