diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..cac4ee499c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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). +## Any rendering has to be done by the application; the kernel itself does not output anything on 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..92b157f8d0 --- /dev/null +++ b/src/arch/x86_64/kernel/bga.rs @@ -0,0 +1,161 @@ +//! 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::kernel::pci::PciConfigRegion; +use crate::arch::x86_64::mm::paging::{ + self, BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt, +}; +use crate::drivers::pci::PciDevice; + +#[derive(Debug)] +pub struct BgaInfo { + pub framebuffer: usize, + pub width: u16, + pub height: u16, + pub bpp: u16, +} + +static BGA_INFO: OnceCell = OnceCell::new(); + +const VBE_DISPI_IOPORT_INDEX: u16 = 0x01ce; +const VBE_DISPI_IOPORT_DATA: u16 = 0x01cf; + +#[allow(dead_code)] +#[repr(u16)] +pub enum VbeDispiIndex { + #[doc(alias = "VBE_DISPI_INDEX_ID")] + Id = 0, + #[doc(alias = "VBE_DISPI_INDEX_XRES")] + Xres = 1, + #[doc(alias = "VBE_DISPI_INDEX_YRES")] + Yres = 2, + #[doc(alias = "VBE_DISPI_INDEX_BPP")] + Bpp = 3, + #[doc(alias = "VBE_DISPI_INDEX_ENABLE")] + Enable = 4, + #[doc(alias = "VBE_DISPI_INDEX_BANK")] + Bank = 5, + #[doc(alias = "VBE_DISPI_INDEX_VIRT_WIDTH")] + VirtWidth = 6, + #[doc(alias = "VBE_DISPI_INDEX_VIRT_HEIGHT")] + VirtHeight = 7, + #[doc(alias = "VBE_DISPI_INDEX_X_OFFSET")] + XOffset = 8, + #[doc(alias = "VBE_DISPI_INDEX_Y_OFFSET")] + YOffset = 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; + +#[allow(dead_code)] +#[repr(u16)] +pub enum VbeDispiId { + #[doc(alias = "VBE_DISPI_ID0")] + Id0 = 0xb0c0, + #[doc(alias = "VBE_DISPI_ID1")] + Id1 = 0xb0c1, + #[doc(alias = "VBE_DISPI_ID2")] + Id2 = 0xb0c2, + #[doc(alias = "VBE_DISPI_ID3")] + Id3 = 0xb0c3, + #[doc(alias = "VBE_DISPI_ID4")] + Id4 = 0xb0c4, + #[doc(alias = "VBE_DISPI_ID5")] + Id5 = 0xb0c5, +} + +struct BgaRegisters; + +impl BgaRegisters { + pub fn read(index: VbeDispiIndex) -> 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 as u16); + data_port.read() + } + } + + pub fn write(index: VbeDispiIndex, 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 as u16); + 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 as u16 { + 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: phys_addr as usize, + width, + height, + bpp, + }) + .unwrap(); + + 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..5dbc8ca3a0 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::arch::kernel::bga::init_device(adapter); + } + PCI_DRIVERS.finalize(); }); } diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index de963e3fed..5a1d898cf9 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -1,3 +1,6 @@ +#[cfg(all(target_arch = "x86_64", feature = "bga"))] +use core::ffi::c_int; + use crate::arch::mm::paging::{BasePageSize, PageSize}; /// Returns the base page size, in bytes, of the current system. @@ -6,3 +9,40 @@ use crate::arch::mm::paging::{BasePageSize, PageSize}; pub extern "C" fn sys_getpagesize() -> i32 { BasePageSize::SIZE.try_into().unwrap() } + +#[cfg(all(target_arch = "x86_64", feature = "bga"))] +#[repr(C)] +pub struct FramebufferInfo { + pub framebuffer: *mut u8, + pub width: u32, + pub height: u32, + pub bpp: u32, +} + +/// 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_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: core::ptr::with_exposed_provenance_mut(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, + } +}