-
Notifications
You must be signed in to change notification settings - Fork 129
feat: Bochs VBE linear framebuffer support #2523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 {} | ||||||
|
|
||||||
| 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; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
|
|
||||||
| #[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<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); | ||||||
|
|
||||||
| 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::<BasePageSize>( | ||||||
| VirtAddr::new(phys_addr), | ||||||
| PhysAddr::new(phys_addr), | ||||||
| page_count, | ||||||
| flags, | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| pub fn get_framebuffer_info() -> Option<&'static BgaInfo> { | ||||||
| BGA_INFO.get() | ||||||
| } | ||||||
| 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, | ||
| } | ||
|
|
||
| /// 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 { | ||
| 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, | ||
| } | ||
| } |
There was a problem hiding this comment.
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.