diff --git a/Cargo.lock b/Cargo.lock index 70481734..98db1c14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2281,6 +2281,21 @@ dependencies = [ "yggdrasil-abi", ] +[[package]] +name = "ygg_driver_virtio_gpu" +version = "0.1.0" +dependencies = [ + "bytemuck", + "device-api", + "libk", + "libk-mm", + "libk-util", + "log", + "ygg_driver_pci", + "ygg_driver_virtio_core", + "yggdrasil-abi", +] + [[package]] name = "ygg_driver_virtio_net" version = "0.1.0" @@ -2361,6 +2376,7 @@ dependencies = [ "ygg_driver_pci", "ygg_driver_usb", "ygg_driver_usb_xhci", + "ygg_driver_virtio_gpu", "ygg_driver_virtio_net", "yggdrasil-abi", ] diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 303673d8..d0aec4cc 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -29,6 +29,7 @@ ygg_driver_usb = { path = "driver/bus/usb" } ygg_driver_net_core = { path = "driver/net/core" } ygg_driver_net_loopback = { path = "driver/net/loopback" } ygg_driver_virtio_net = { path = "driver/virtio/net", features = ["pci"] } +ygg_driver_virtio_gpu = { path = "driver/virtio/gpu", features = ["pci"] } ygg_driver_ahci = { path = "driver/block/ahci" } ygg_driver_usb_xhci = { path = "driver/usb/xhci" } ygg_driver_input = { path = "driver/input" } diff --git a/kernel/arch/i686/src/mem/mod.rs b/kernel/arch/i686/src/mem/mod.rs index 9f87e69f..095449dc 100644 --- a/kernel/arch/i686/src/mem/mod.rs +++ b/kernel/arch/i686/src/mem/mod.rs @@ -38,8 +38,12 @@ impl KernelTableManager for KernelTableManagerImpl { _attrs: DeviceMemoryAttributes, ) -> Result, Error> { // TODO page align up - let end = base + count as u64; - assert_eq!(base & 0xFFF, 0); + + let offset = (base & 0xFFF) as usize; + let base = base & !0xFFF; + let end = (base + count as u64 + 0xFFF) & !0xFFF; + + // assert_eq!(base & 0xFFF, 0); if end < fixed::MAX_FIXED_PHYSICAL.into_u64() { // 1:1 let address = Self::virtualize(base); @@ -53,13 +57,16 @@ impl KernelTableManager for KernelTableManagerImpl { let virt = KERNEL_TABLES.lock().map_dynamic_memory(base, page_count)?; Ok(RawDeviceMemoryMapping::from_raw_parts( - virt, virt, page_count, 0, + virt + offset, + virt, + page_count, + 0, )) } } unsafe fn unmap_device_pages(_mapping: &RawDeviceMemoryMapping) { - todo!() + // todo!() } fn virtualize(phys: u64) -> usize { diff --git a/kernel/driver/fs/kernel-fs/src/devfs.rs b/kernel/driver/fs/kernel-fs/src/devfs.rs index c1983f49..b0ff34e5 100644 --- a/kernel/driver/fs/kernel-fs/src/devfs.rs +++ b/kernel/driver/fs/kernel-fs/src/devfs.rs @@ -15,6 +15,13 @@ pub enum CharDeviceType { TtySerial, } +/// Describes the kind of a block device +#[derive(Debug)] +pub enum BlockDeviceType { + /// Framebuffer device + Framebuffer, +} + static DEVFS_ROOT: OneTimeInit = OneTimeInit::new(); /// Sets up the device filesystem @@ -54,6 +61,19 @@ pub fn add_named_block_device>( DEVFS_ROOT.get().add_child(name, node) } +pub fn add_block_device(dev: &'static dyn BlockDevice, kind: BlockDeviceType) -> Result<(), Error> { + static FB_COUNT: AtomicUsize = AtomicUsize::new(0); + + let (count, prefix) = match kind { + BlockDeviceType::Framebuffer => (&FB_COUNT, "fb"), + }; + + let value = count.fetch_add(1, Ordering::AcqRel); + let name = format!("{}{}", prefix, value); + + add_named_block_device(dev, name) +} + pub fn add_block_device_partition>( base_name: S, index: usize, diff --git a/kernel/driver/virtio/core/src/queue.rs b/kernel/driver/virtio/core/src/queue.rs index 78ad9aa9..c5beb1c6 100644 --- a/kernel/driver/virtio/core/src/queue.rs +++ b/kernel/driver/virtio/core/src/queue.rs @@ -215,23 +215,6 @@ impl VirtQueue { let head = self.free_head; let mut last = self.free_head; - for item in input { - assert_ne!(item.len(), 0); - let desc = &mut self.descriptor_table[usize::from(self.free_head)]; - let next = (self.free_head + 1) % self.capacity as u16; - - desc.write(Descriptor { - address: item.as_physical_address().into(), - len: item.len().try_into().unwrap(), - // TODO MAGIC - flags: (1 << 0) | (1 << 1), - next, - }); - - last = self.free_head; - self.free_head = next; - } - for item in output { assert_ne!(item.len(), 0); let desc = &mut self.descriptor_table[usize::from(self.free_head)]; @@ -249,6 +232,23 @@ impl VirtQueue { self.free_head = next; } + for item in input { + assert_ne!(item.len(), 0); + let desc = &mut self.descriptor_table[usize::from(self.free_head)]; + let next = (self.free_head + 1) % self.capacity as u16; + + desc.write(Descriptor { + address: item.as_physical_address().into(), + len: item.len().try_into().unwrap(), + // TODO MAGIC + flags: (1 << 0) | (1 << 1), + next, + }); + + last = self.free_head; + self.free_head = next; + } + { let last_desc = self.descriptor_table[last as usize].assume_init_mut(); diff --git a/kernel/driver/virtio/core/src/transport/pci.rs b/kernel/driver/virtio/core/src/transport/pci.rs index df14d335..2965fa8b 100644 --- a/kernel/driver/virtio/core/src/transport/pci.rs +++ b/kernel/driver/virtio/core/src/transport/pci.rs @@ -58,9 +58,10 @@ impl PciTransport { // Transitional devices MUST have the Transitional PCI // Device ID in the range 0x1000 to 0x103f. // TODO check PCI subsystem ID - if space.rev_id() != 0 { - return Err(Error::InvalidPciConfiguration); - } + // if space.rev_id() != 0 { + // log::error!("VirtIO PCI rev ID != 0"); + // return Err(Error::InvalidPciConfiguration); + // } let mut cmd = PciCommandRegister::from_bits_retain(space.command()); cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO); diff --git a/kernel/driver/virtio/gpu/Cargo.toml b/kernel/driver/virtio/gpu/Cargo.toml new file mode 100644 index 00000000..ecfce4b5 --- /dev/null +++ b/kernel/driver/virtio/gpu/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ygg_driver_virtio_gpu" +version = "0.1.0" +edition = "2021" + +[dependencies] +yggdrasil-abi.workspace = true +libk-util.workspace = true +libk-mm.workspace = true +libk.workspace = true +device-api = { workspace = true, features = ["derive"] } + +ygg_driver_virtio_core = { path = "../core" } +ygg_driver_pci = { path = "../../bus/pci", optional = true } + +log.workspace = true +bytemuck.workspace = true + +[features] +default = [] +pci = ["ygg_driver_pci", "ygg_driver_virtio_core/pci"] diff --git a/kernel/driver/virtio/gpu/src/command.rs b/kernel/driver/virtio/gpu/src/command.rs new file mode 100644 index 00000000..bb979c0b --- /dev/null +++ b/kernel/driver/virtio/gpu/src/command.rs @@ -0,0 +1,286 @@ +use alloc::vec::Vec; +use bytemuck::{Pod, Zeroable}; +use libk::{device::display::PixelFormat, error::Error}; +use libk_mm::{address::PhysicalAddress, PageBox}; +use libk_util::sync::IrqSafeSpinlockGuard; +use ygg_driver_virtio_core::{queue::VirtQueue, transport::Transport}; + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ControlHeader { + pub ty: u32, + pub flags: u32, + pub fence_id: u64, + pub ctx_id: u32, + pub ring_idx: u8, + _0: [u8; 3], +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct Rect { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ScanoutInfo { + pub r: Rect, + pub enabled: u32, + pub flags: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ResourceCreate2d { + pub header: ControlHeader, + pub resource_id: u32, + pub format: u32, + pub width: u32, + pub height: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct SetScanout { + pub header: ControlHeader, + pub r: Rect, + pub scanout_id: u32, + pub resource_id: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct GpuMemEntry { + pub addr: u64, + pub length: u32, + pub _0: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ResourceAttachBacking { + pub header: ControlHeader, + pub resource_id: u32, + pub nr_entries: u32, + pub entry: GpuMemEntry, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct ResourceFlush { + pub header: ControlHeader, + pub r: Rect, + pub resource_id: u32, + pub _0: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct TransferToHost2d { + pub header: ControlHeader, + pub r: Rect, + pub offset: u64, + pub resource_id: u32, + pub _0: u32, +} + +pub struct ControlLock<'a, T: Transport> { + control: IrqSafeSpinlockGuard<'a, VirtQueue>, + transport: IrqSafeSpinlockGuard<'a, T>, +} + +impl<'a, T: Transport> ControlLock<'a, T> { + pub const fn new( + control: IrqSafeSpinlockGuard<'a, VirtQueue>, + transport: IrqSafeSpinlockGuard<'a, T>, + ) -> Self { + Self { control, transport } + } + + fn send_recv<'r, Req: Pod>( + &mut self, + req: &Req, + buffer: &'r mut PageBox<[u8]>, + ) -> Result<(&'r ControlHeader, &'r [u8]), Error> { + let mut request = PageBox::new_slice(0u8, size_of::())?; + request.copy_from_slice(bytemuck::bytes_of(req)); + + let len = self + .control + .add_notify_wait_pop(&[buffer], &[&request], &mut *self.transport) + .inspect_err(|error| { + log::warn!("virtio queue: {error:?}"); + }) + .map_err(|_| Error::InvalidArgument)? as usize; + + if len < size_of::() { + log::warn!("virtio-gpu: invalid device response length: {len}"); + return Err(Error::InvalidArgument); + } + + let header = bytemuck::from_bytes(&buffer[..size_of::()]); + let data = &buffer[size_of::()..len]; + + Ok((header, data)) + } + + fn send_recv_no_data( + &mut self, + req: &Req, + buffer: &mut PageBox<[u8]>, + ) -> Result<(), Error> { + let (response, _) = self.send_recv(req, buffer)?; + if response.ty == 0x1100 { + Ok(()) + } else { + Err(Error::InvalidArgument) + } + } + + pub fn query_scanouts<'r>( + &mut self, + max_scanouts: usize, + buffer: &'r mut PageBox<[u8]>, + ) -> Result<&'r [ScanoutInfo], Error> { + let request = ControlHeader { + ty: 0x0100, + flags: 0, + fence_id: 0, + ctx_id: 0, + ring_idx: 0, + _0: [0; 3], + }; + + let (response, data) = self.send_recv(&request, buffer)?; + if response.ty != 0x1101 { + log::warn!("virtio-gpu: query_scanouts returned {:#x}", response.ty); + return Err(Error::InvalidArgument); + } + let limit = size_of::() * max_scanouts; + + let scanouts = + bytemuck::try_cast_slice(&data[..limit]).map_err(|_| Error::InvalidArgument)?; + + Ok(scanouts) + } + + pub fn create_resource_2d( + &mut self, + buffer: &mut PageBox<[u8]>, + width: u32, + height: u32, + pixel_format: PixelFormat, + ) -> Result { + let format = match pixel_format { + PixelFormat::R8G8B8A8 => 67, + _ => todo!(), + }; + let request = ResourceCreate2d { + header: ControlHeader { + ty: 0x0101, + ..ControlHeader::zeroed() + }, + resource_id: 1, + width, + height, + format, + }; + + self.send_recv_no_data(&request, buffer)?; + + Ok(1) + } + + pub fn attach_backing( + &mut self, + buffer: &mut PageBox<[u8]>, + resource_id: u32, + base: PhysicalAddress, + length: u32, + ) -> Result<(), Error> { + let request = ResourceAttachBacking { + header: ControlHeader { + ty: 0x0106, + ..ControlHeader::zeroed() + }, + resource_id, + nr_entries: 1, + entry: GpuMemEntry { + addr: base.into_u64(), + length, + _0: 0, + }, + }; + + self.send_recv_no_data(&request, buffer) + } + + pub fn set_scanout( + &mut self, + buffer: &mut PageBox<[u8]>, + scanout_id: u32, + resource_id: u32, + width: u32, + height: u32, + ) -> Result<(), Error> { + let request = SetScanout { + header: ControlHeader { + ty: 0x0103, + ..ControlHeader::zeroed() + }, + r: Rect { + x: 0, + y: 0, + width, + height, + }, + scanout_id, + resource_id, + }; + + self.send_recv_no_data(&request, buffer) + } + + pub fn transfer_to_host_2d( + &mut self, + buffer: &mut PageBox<[u8]>, + resource_id: u32, + r: Rect, + ) -> Result<(), Error> { + let request = TransferToHost2d { + header: ControlHeader { + ty: 0x0105, + ..ControlHeader::zeroed() + }, + r, + offset: 0, + resource_id, + _0: 0, + }; + + self.send_recv_no_data(&request, buffer) + } + + pub fn resource_flush( + &mut self, + buffer: &mut PageBox<[u8]>, + resource_id: u32, + r: Rect, + ) -> Result<(), Error> { + let request = ResourceFlush { + header: ControlHeader { + ty: 0x0104, + ..ControlHeader::zeroed() + }, + r, + resource_id, + _0: 0, + }; + + self.send_recv_no_data(&request, buffer) + } +} diff --git a/kernel/driver/virtio/gpu/src/lib.rs b/kernel/driver/virtio/gpu/src/lib.rs new file mode 100644 index 00000000..e3dff74d --- /dev/null +++ b/kernel/driver/virtio/gpu/src/lib.rs @@ -0,0 +1,404 @@ +#![no_std] + +extern crate alloc; + +use core::mem::MaybeUninit; + +use alloc::{boxed::Box, vec::Vec}; +use bytemuck::{Pod, Zeroable}; +use command::{ControlLock, Rect, ScanoutInfo}; +use device_api::Device; +use libk::device::display::{ + register_display_device, DisplayDevice, DisplayDeviceWrapper, DisplayMode, DisplayOwner, + FramebufferInfo, PixelFormat, +}; +use libk_mm::{ + address::{PhysicalAddress, Virtualize}, + device::RawDeviceMemoryMapping, + phys, + table::MapAttributes, + PageBox, PageProvider, L3_PAGE_SIZE, +}; +use libk_util::{ + sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock}, + OneTimeInit, +}; +use ygg_driver_pci::device::PciDeviceInfo; +use ygg_driver_virtio_core::{ + queue::VirtQueue, + transport::{pci::PciTransport, Transport}, + DeviceStatus, +}; +use yggdrasil_abi::error::Error; + +mod command; + +struct Queues { + control: IrqSafeSpinlock, +} + +struct Framebuffer { + scanout_index: usize, + resource_id: u32, + + double: bool, + base: PhysicalAddress, + page_count: usize, + kernel_base: usize, + stride: usize, + size: usize, +} + +struct Config { + scanouts: Vec, + framebuffer: Option, + + owner: DisplayOwner, + response: PageBox<[u8]>, +} + +pub struct VirtioGpu { + transport: IrqSafeSpinlock, + pci_device_info: Option, + + queues: OneTimeInit, + config: IrqSafeRwLock, + + num_scanouts: usize, +} + +impl VirtioGpu { + pub fn new(transport: T, info: Option) -> Result { + // Read num-scanouts from device config + let Some(device_cfg) = transport.device_cfg() else { + log::error!("virtio-gpu must have device-specific configuration section"); + return Err(Error::InvalidArgument); + }; + + let mut num_scanouts = [0; size_of::()]; + num_scanouts.copy_from_slice(&device_cfg[8..12]); + let num_scanouts = u32::from_le_bytes(num_scanouts); + + if num_scanouts < 1 || num_scanouts > 16 { + log::error!("virtio-gpu has invalid num_scanouts: {num_scanouts}"); + return Err(Error::InvalidArgument); + } + + Ok(Self { + transport: IrqSafeSpinlock::new(transport), + pci_device_info: info, + + queues: OneTimeInit::new(), + config: IrqSafeRwLock::new(Config { + scanouts: Vec::new(), + framebuffer: None, + response: PageBox::new_slice(0, 4096)?, + owner: DisplayOwner::None, + }), + + num_scanouts: num_scanouts as usize, + }) + } + + fn begin_init(&self) -> Result { + let mut transport = self.transport.lock(); + let mut status = DeviceStatus::RESET_VALUE; + + log::debug!("Reset device"); + transport.write_device_status(status); + status |= DeviceStatus::ACKNOWLEDGE; + transport.write_device_status(status); + status |= DeviceStatus::DRIVER; + transport.write_device_status(status); + + let _device_features = transport.read_device_features(); + + // TODO select features + + transport.write_driver_features(0); + + status |= DeviceStatus::FEATURES_OK; + transport.write_device_status(status); + + if !transport + .read_device_status() + .contains(DeviceStatus::FEATURES_OK) + { + return Err(Error::InvalidOperation); + } + + Ok(status) + } + + fn finish_init(&self, status: DeviceStatus) { + let mut transport = self.transport.lock(); + + transport.write_device_status(status | DeviceStatus::DRIVER_OK); + } + + fn setup_queues(&'static self) -> Result<(), Error> { + // TODO cursorq + let mut transport = self.transport.lock(); + + let control = VirtQueue::with_max_capacity(&mut *transport, 0, 128, None, true) + .map_err(|_| Error::InvalidArgument)?; + + self.queues.init(Queues { + control: IrqSafeSpinlock::new(control), + }); + + Ok(()) + } + + fn control(&self) -> ControlLock { + let queues = self.queues.get(); + let control = queues.control.lock(); + let transport = self.transport.lock(); + + ControlLock::new(control, transport) + } + + fn setup_display(&'static self) -> Result<(), Error> { + let mut control = self.control(); + let mut config = self.config.write(); + + let scanouts = control.query_scanouts(self.num_scanouts, &mut config.response)?; + for (i, scanout) in scanouts.iter().enumerate() { + log::info!( + "virtio-gpu: [{i}] {}x{} + {},{}", + scanout.r.width, + scanout.r.height, + scanout.r.x, + scanout.r.y + ); + } + config.scanouts = scanouts.into(); + + Ok(()) + } + + fn setup_mode(&self, index: usize) -> Result<(), Error> { + let mut config = self.config.write(); + + if config.framebuffer.is_some() { + // TODO destroy old resource 2d and free the old framebuffer + todo!() + } + + /* + 5.7.6.3 Device Operation: Using pageflip + It is possible to create multiple framebuffers, flip between them using + VIRTIO_GPU_CMD_SET_SCANOUT and VIRTIO_GPU_CMD_RESOURCE_FLUSH, and update + the invisible framebuffer using VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D. + */ + + let scanout = config.scanouts.get(index).ok_or(Error::DoesNotExist)?; + let w = scanout.r.width; + let h = scanout.r.height; + let stride = w as usize * size_of::(); + let size = stride * h as usize; + + let page_count = size.div_ceil(L3_PAGE_SIZE); + let base = phys::alloc_pages_contiguous(page_count)?; + let kernel_base = base.virtualize(); + + let mut control = self.control(); + + let resource_id = + control.create_resource_2d(&mut config.response, w, h, PixelFormat::R8G8B8A8)?; + control.attach_backing( + &mut config.response, + resource_id, + base, + size.try_into().unwrap(), + )?; + control.set_scanout(&mut config.response, index as u32, resource_id, w, h)?; + + config.framebuffer = Some(Framebuffer { + scanout_index: index, + double: false, + + resource_id, + size, + page_count, + stride, + base, + kernel_base, + }); + + Ok(()) + } + + fn flush(&self) -> Result<(), Error> { + let mut config = self.config.write(); + + let framebuffer = config.framebuffer.as_ref().ok_or(Error::DoesNotExist)?; + let r = config.scanouts[framebuffer.scanout_index].r; + + let mut control = self.control(); + + if framebuffer.double { + // Flip the buffer + todo!() + } else { + let resource_id = framebuffer.resource_id; + + control.transfer_to_host_2d(&mut config.response, resource_id, r)?; + control.resource_flush(&mut config.response, resource_id, r)?; + + Ok(()) + } + } +} + +impl Device for VirtioGpu { + unsafe fn init(&'static self) -> Result<(), Error> { + let status = self.begin_init()?; + self.setup_queues()?; + self.finish_init(status); + + // Set up some initial mode + self.setup_display()?; + + self.setup_mode(0)?; + + register_display_device(Box::leak(Box::new(DisplayDeviceWrapper::new(self)))); + + Ok(()) + } + + unsafe fn init_irq(&'static self) -> Result<(), Error> { + Ok(()) + } + + fn display_name(&self) -> &'static str { + "VirtIO GPU Device" + } +} + +impl PageProvider for VirtioGpu { + fn get_page(&self, offset: u64) -> Result { + // TODO check that the page is mapped by framebuffer owner + let config = self.config.read(); + let framebuffer = config.framebuffer.as_ref().ok_or(Error::DoesNotExist)?; + if offset as usize + L3_PAGE_SIZE > framebuffer.page_count * L3_PAGE_SIZE { + log::warn!( + "virtio-gpu: offset {:#x} outside of framebuffer bounds {:#x}", + offset, + framebuffer.size + ); + return Err(Error::InvalidMemoryOperation); + } + let phys = framebuffer.base.add(offset as usize); + + Ok(phys) + } + + fn clone_page( + &self, + offset: u64, + src_phys: PhysicalAddress, + src_attrs: MapAttributes, + ) -> Result { + todo!() + } + + fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> { + Ok(()) + } +} + +impl DisplayDevice for VirtioGpu { + fn lock(&self, owner: DisplayOwner) -> Result<(), Error> { + let mut config = self.config.write(); + if config.owner == owner { + return Ok(()); + } + match (config.owner, owner) { + (DisplayOwner::None | DisplayOwner::Kernel, DisplayOwner::Process(_)) => { + config.owner = owner; + Ok(()) + } + (DisplayOwner::None, DisplayOwner::Kernel) => { + config.owner = owner; + Ok(()) + } + (_, _) => Err(Error::WouldBlock), + } + } + + fn unlock(&self, owner: DisplayOwner) -> Result<(), Error> { + let mut config = self.config.write(); + if config.owner == owner { + config.owner = DisplayOwner::None; + Ok(()) + } else { + Err(Error::InvalidArgument) + } + } + + fn synchronize(&self) -> Result<(), Error> { + self.flush() + } + + fn set_display_mode(&self, index: u32, double_buffer: bool) -> Result<(), Error> { + todo!() + } + + fn active_framebuffers( + &self, + output: &mut [MaybeUninit], + ) -> Result { + let config = self.config.read(); + + if let Some(framebuffer) = config.framebuffer.as_ref() { + if output.len() < 1 { + return Err(1); + } + + output[0].write(FramebufferInfo { + base: framebuffer.base, + kernel_base: Some(framebuffer.kernel_base), + stride: framebuffer.stride, + size: framebuffer.size, + }); + + Ok(1) + } else { + Ok(0) + } + } + + fn active_display_mode(&self) -> Option { + let config = self.config.read(); + let framebuffer = config.framebuffer.as_ref()?; + let scanout = &config.scanouts[framebuffer.scanout_index]; + + Some(DisplayMode { + index: framebuffer.scanout_index as _, + width: scanout.r.width, + height: scanout.r.height, + pixel_format: PixelFormat::R8G8B8A8, + frames_per_second: 0, + }) + } + + fn query_display_modes(&self, modes: &mut [MaybeUninit]) -> Result { + todo!() + } +} + +pub fn probe(info: &PciDeviceInfo) -> Result<&'static dyn Device, Error> { + let space = &info.config_space; + + let transport = PciTransport::from_config_space(space) + .inspect_err(|error| { + log::error!("Couldn't set up PCI virtio transport: {error:?}"); + }) + .map_err(|_| Error::InvalidArgument)?; + let device = VirtioGpu::new(transport, Some(info.clone()))?; + + let device = Box::leak(Box::new(device)); + + Ok(device) +} diff --git a/kernel/libk/src/device/display.rs b/kernel/libk/src/device/display.rs new file mode 100644 index 00000000..a2b625e2 --- /dev/null +++ b/kernel/libk/src/device/display.rs @@ -0,0 +1,223 @@ +use core::{ + mem::MaybeUninit, + ops::Deref, + sync::atomic::{AtomicU32, Ordering}, +}; + +use alloc::{boxed::Box, collections::BTreeMap}; +use async_trait::async_trait; +use device_api::Device; +use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider}; +use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit}; +use yggdrasil_abi::{ + error::Error, + io::{DeviceRequest, Framebuffer}, + process::ProcessId, +}; + +use crate::{task::thread::Thread, vfs::block::BlockDevice}; + +/// Represents the current lock holder for a display device +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DisplayOwner { + /// Display is not locked + None, + /// Kernel is using the display + Kernel, + /// Userspace process is using the display + Process(ProcessId), +} + +/// Resolution of the display device +#[derive(Clone, Copy, Debug)] +pub struct DisplayDimensions { + /// Width of the display in pixels + pub width: u32, + /// Height of the display in pixels + pub height: u32, +} + +/// Describes how pixels are laid out in the framebuffer used by a display +#[derive(Clone, Copy, Debug)] +#[allow(missing_docs)] +pub enum PixelFormat { + B8G8R8A8, + B8G8R8X8, + A8R8G8B8, + X8R8G8B8, + R8G8B8A8, + X8B8G8R8, + A8B8G8R8, + R8G8B8X8, +} + +/// Describes a display mode +#[derive(Clone, Copy, Debug)] +pub struct DisplayMode { + /// Index for setting this display mode + pub index: u32, + /// Width in pixels + pub width: u32, + /// Height in pixels + pub height: u32, + /// Max frames per second supported by this mode, [u32::MAX] if unlimited. + pub frames_per_second: u32, + /// Pixel format of this mode + pub pixel_format: PixelFormat, +} + +/// Describes display device framebuffer layout +#[derive(Clone, Copy, Debug)] +pub struct FramebufferInfo { + /// Base address + pub base: PhysicalAddress, + /// Kernel-accessible virtual base address + pub kernel_base: Option, + /// Stride between the start of one row and the start of the next one. + pub stride: usize, + /// Overall size of the framebuffer + pub size: usize, +} + +/// Abstract display device interface +pub trait DisplayDevice: Device + PageProvider { + /// Returns the active display mode + fn active_display_mode(&self) -> Option; + /// Returns the list of available display modes + fn query_display_modes(&self, modes: &mut [MaybeUninit]) -> Result; + + /// Requests the device to set its display mode to the one described by `index`. If + /// double buffering is supported, `double_buffer` is used to indicate a request to + /// set up multiple frame buffers + fn set_display_mode(&self, index: u32, double_buffer: bool) -> Result<(), Error>; + + /// Reads the currently active framebuffer information. Will return an error with the + /// framebuffer count if `output` is too small to contain all the framebuffer infos. + fn active_framebuffers( + &self, + output: &mut [MaybeUninit], + ) -> Result; + + /// Returns the currently active framebuffer, if there is exactly one. Returns an error + /// otherwise. + fn active_framebuffer(&self) -> Result { + let mut buffer = [MaybeUninit::uninit()]; + if self.active_framebuffers(&mut buffer)? == 1 { + Ok(unsafe { buffer[0].assume_init() }) + } else { + // No active framebuffer + Err(0) + } + } + + /// Indicates to the driver that a frame was finished and can be sent to the device. + fn synchronize(&self) -> Result<(), Error>; + + /// Locks the display to only be accessible by a single process. + fn lock(&self, owner: DisplayOwner) -> Result<(), Error>; + /// Unlocks the display. `owner` describes who this request comes from. + fn unlock(&self, owner: DisplayOwner) -> Result<(), Error>; +} + +/// Convenience wrapper for [DisplayDevice] trait implementors to auto-implement [BlockDevice] as +/// well. +pub struct DisplayDeviceWrapper { + inner: &'static dyn DisplayDevice, +} + +static DEVICES: IrqSafeRwLock> = + IrqSafeRwLock::new(BTreeMap::new()); +static DEVICE_CALLBACK: OneTimeInit = OneTimeInit::new(); +static LAST_DEVICE_ID: AtomicU32 = AtomicU32::new(1); + +pub fn set_display_registration_callback(cb: fn(&'static DisplayDeviceWrapper)) { + DEVICE_CALLBACK.init(cb); +} + +/// Adds the display to the global display list +pub fn register_display_device(device: &'static DisplayDeviceWrapper) -> u32 { + let id = LAST_DEVICE_ID.fetch_add(1, Ordering::Relaxed); + { + let mut devices = DEVICES.write(); + devices.insert(id, device); + } + if let Some(callback) = DEVICE_CALLBACK.try_get() { + callback(device); + } + id +} + +/// Removes the device by its ID +pub fn remove_device(id: u32) -> Option<&'static DisplayDeviceWrapper> { + let mut devices = DEVICES.write(); + devices.remove(&id) +} + +impl DisplayDeviceWrapper { + /// Wraps the [DisplayDevice] in [DisplayDeviceWrapper]. + pub const fn new(inner: &'static dyn DisplayDevice) -> Self { + Self { inner } + } +} + +#[async_trait] +impl BlockDevice for DisplayDeviceWrapper { + async fn read(&self, _pos: u64, _buf: &mut [u8]) -> Result { + Err(Error::InvalidOperation) + } + + async fn write(&self, _pos: u64, _buf: &[u8]) -> Result { + Err(Error::InvalidOperation) + } + + fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> { + let process = Thread::current().process_id(); + + match req { + DeviceRequest::AcquireDevice => self.lock(DisplayOwner::Process(process)), + DeviceRequest::ReleaseDevice => self.unlock(DisplayOwner::Process(process)), + DeviceRequest::GetActiveFramebuffer(framebuffer) => { + let mode = self.active_display_mode().ok_or(Error::DoesNotExist)?; + let info = self.active_framebuffer().map_err(|_| Error::DoesNotExist)?; + + framebuffer.write(Framebuffer { + width: mode.width, + height: mode.height, + size: info.size, + stride: info.stride, + }); + + Ok(()) + } + DeviceRequest::FlushDisplay => self.synchronize(), + _ => Err(Error::InvalidOperation), + } + } +} + +impl PageProvider for DisplayDeviceWrapper { + fn clone_page( + &self, + offset: u64, + src_phys: PhysicalAddress, + src_attrs: MapAttributes, + ) -> Result { + self.inner.clone_page(offset, src_phys, src_attrs) + } + + fn get_page(&self, offset: u64) -> Result { + self.inner.get_page(offset) + } + + fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> { + self.inner.release_page(offset, phys) + } +} + +impl Deref for DisplayDeviceWrapper { + type Target = dyn DisplayDevice; + + fn deref(&self) -> &Self::Target { + self.inner + } +} diff --git a/kernel/libk/src/device/mod.rs b/kernel/libk/src/device/mod.rs new file mode 100644 index 00000000..32a853a0 --- /dev/null +++ b/kernel/libk/src/device/mod.rs @@ -0,0 +1,3 @@ +pub mod display; + +pub use libk_device::*; diff --git a/kernel/libk/src/lib.rs b/kernel/libk/src/lib.rs index 7b1d417e..346f2d1c 100644 --- a/kernel/libk/src/lib.rs +++ b/kernel/libk/src/lib.rs @@ -36,6 +36,7 @@ pub use yggdrasil_abi::error; pub mod task; pub mod arch; +pub mod device; pub mod module; pub mod random; pub mod vfs; @@ -43,10 +44,6 @@ pub mod vfs; #[cfg(any(target_os = "none", rust_analyzer))] pub mod panic; -pub mod device { - pub use libk_device::*; -} - #[repr(C)] pub struct AlignedTo { pub align: [Align; 0], diff --git a/kernel/libk/src/vfs/shared_memory.rs b/kernel/libk/src/vfs/shared_memory.rs index e852a9a7..a8222739 100644 --- a/kernel/libk/src/vfs/shared_memory.rs +++ b/kernel/libk/src/vfs/shared_memory.rs @@ -16,8 +16,7 @@ pub struct SharedMemory { impl SharedMemory { /// Creates a new buffer of shared memory pub fn new(size: usize) -> Result { - assert_eq!(size & 0xFFF, 0); - let page_count = size / 0x1000; + let page_count = size.div_ceil(0x1000); let pages = (0..page_count) .map(|_| PageBox::new_uninit()) diff --git a/kernel/src/arch/aarch64/mod.rs b/kernel/src/arch/aarch64/mod.rs index 113058f7..40b979ec 100644 --- a/kernel/src/arch/aarch64/mod.rs +++ b/kernel/src/arch/aarch64/mod.rs @@ -17,7 +17,11 @@ use kernel_arch_aarch64::{ }, ArchitectureImpl, PerCpuData, }; -use libk::{arch::Cpu, device::external_interrupt_controller}; +use kernel_fs::devfs::{self, BlockDeviceType}; +use libk::{ + arch::Cpu, + device::{display::set_display_registration_callback, external_interrupt_controller}, +}; use libk_mm::{ address::PhysicalAddress, phys::PhysicalMemoryRegion, @@ -226,6 +230,12 @@ impl AArch64 { Cpu::init_local(None, per_cpu); if is_bsp { + ygg_driver_pci::register_vendor_driver( + "Virtio PCI GPU Device", + 0x1AF4, + 0x1050, + ygg_driver_virtio_gpu::probe, + ); ygg_driver_pci::register_vendor_driver( "Virtio PCI Network Device", 0x1AF4, @@ -272,6 +282,11 @@ impl AArch64 { debug::init(); + set_display_registration_callback(|device| { + log::info!("Display registered: {:?}", device.display_name()); + devfs::add_block_device(device, BlockDeviceType::Framebuffer).ok(); + }); + log::info!( "Yggdrasil v{} ({})", env!("CARGO_PKG_VERSION"), diff --git a/kernel/src/arch/i686/mod.rs b/kernel/src/arch/i686/mod.rs index 61f25727..b923c7bc 100644 --- a/kernel/src/arch/i686/mod.rs +++ b/kernel/src/arch/i686/mod.rs @@ -12,6 +12,7 @@ use kernel_arch_x86::cpuid::{self, CpuFeatures, EcxFeatures, EdxFeatures}; use kernel_fs::devfs::{self, CharDeviceType}; use libk::{ arch::Cpu, + device::display::set_display_registration_callback, task::runtime, vfs::{Terminal, TerminalInput}, }; @@ -207,6 +208,13 @@ impl I686 { Some(0x30), ygg_driver_usb_xhci::probe, ); + // TODO: colors is broken: insn fstp %st(0) hangs in release mode + // ygg_driver_pci::register_vendor_driver( + // "Virtio PCI GPU Device", + // 0x1AF4, + // 0x1050, + // ygg_driver_virtio_gpu::probe, + // ); ygg_driver_pci::register_vendor_driver( "Virtio PCI Network Device", 0x1AF4, @@ -214,6 +222,11 @@ impl I686 { ygg_driver_virtio_net::probe, ); + // set_display_registration_callback(|device| { + // log::info!("Display registered: {:?}", device.display_name()); + // devfs::add_block_device(device, BlockDeviceType::Framebuffer).ok(); + // }); + I8259.init().expect("Could not initialize i8259 PIC"); register_external_interrupt_controller(&I8259); I8253.init().expect("Could not initialize i8253 Timer"); diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 76f27655..086fea00 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -1,5 +1,10 @@ //! x86-64 architecture implementation -use core::{mem::size_of, ops::DerefMut, ptr::null_mut, sync::atomic::Ordering}; +use core::{ + mem::size_of, + ops::{Deref, DerefMut}, + ptr::null_mut, + sync::atomic::{AtomicU32, Ordering}, +}; use ::acpi::{mcfg::Mcfg, AcpiTables, InterruptModel}; use abi::error::Error; @@ -18,8 +23,16 @@ use kernel_arch_x86_64::{ }, PerCpuData, }; -use kernel_fs::devfs; -use libk::{arch::Cpu, device::register_external_interrupt_controller}; +use kernel_fs::devfs::{self, BlockDeviceType}; +use libk::{ + arch::Cpu, + device::{ + display::{ + register_display_device, set_display_registration_callback, DisplayDeviceWrapper, + }, + register_external_interrupt_controller, + }, +}; use libk_device::register_monotonic_timestamp_provider; use libk_mm::{ address::{PhysicalAddress, Virtualize}, @@ -64,7 +77,7 @@ pub struct X86_64 { acpi: OneTimeInit>, // Display - framebuffer: OneTimeInit, + gop_framebuffer: OneTimeInit, fbconsole: OneTimeInit, } @@ -75,7 +88,7 @@ pub static PLATFORM: X86_64 = X86_64 { boot_data: OneTimeInit::new(), acpi: OneTimeInit::new(), - framebuffer: OneTimeInit::new(), + gop_framebuffer: OneTimeInit::new(), fbconsole: OneTimeInit::new(), }; @@ -302,6 +315,12 @@ impl X86_64 { Some(0x30), ygg_driver_usb_xhci::probe, ); + ygg_driver_pci::register_vendor_driver( + "Virtio PCI GPU Device", + 0x1AF4, + 0x1050, + ygg_driver_virtio_gpu::probe, + ); ygg_driver_pci::register_vendor_driver( "Virtio PCI Network Device", 0x1AF4, @@ -327,6 +346,10 @@ impl X86_64 { ))); debug::add_sink(com1_3.port_a(), LogLevel::Debug); + set_display_registration_callback(|device| { + log::info!("Display registered: {:?}", device.display_name()); + devfs::add_block_device(device, BlockDeviceType::Framebuffer).ok(); + }); self.init_framebuffer()?; debug::init(); @@ -402,33 +425,53 @@ impl X86_64 { } unsafe fn init_framebuffer(&'static self) -> Result<(), Error> { - match self.boot_data.get() { - &BootData::YBoot(data) => { - let info = &data.opt_framebuffer; - - self.framebuffer.init(LinearFramebuffer::from_physical_bits( - PhysicalAddress::from_u64(info.res_address), - info.res_size as usize, - info.res_stride as usize, - info.res_width, - info.res_height, - )?); - } - } - - self.fbconsole.init(FramebufferConsole::from_framebuffer( - self.framebuffer.get(), - None, - )?); - debug::add_sink(self.fbconsole.get(), LogLevel::Info); - - // self.tty.init(CombinedTerminal::new(self.fbconsole.get())); - - devfs::add_named_block_device(self.framebuffer.get(), "fb0")?; - // devfs::add_char_device(self.tty.get(), CharDeviceType::TtyRegular)?; - console::add_console_autoflush(self.fbconsole.get()); - + // TODO use boot fb temporarily and then switch to whatever proper GPU is detected Ok(()) + // let display = match self.boot_data.get() { + // &BootData::YBoot(data) => { + // let info = &data.opt_framebuffer; + + // let framebuffer = LinearFramebuffer::from_physical_bits( + // PhysicalAddress::from_u64(info.res_address), + // info.res_size as usize, + // info.res_stride as usize, + // info.res_width, + // info.res_height, + // )?; + // let framebuffer = self.gop_framebuffer.init(framebuffer); + + // Some(framebuffer) + // } + // }; + + // let Some(display) = display else { + // return Ok(()); + // }; + + // // Register boot display + // let display = Box::leak(Box::new(DisplayDeviceWrapper::new(display))); + // register_display_device(display); + + // let fbconsole = self.fbconsole.init(FramebufferConsole::from_framebuffer( + // (*display).deref(), + // None, + // )?); + + // debug::add_sink(fbconsole, LogLevel::Info); + + // // self.fbconsole.init(FramebufferConsole::from_framebuffer( + // // self.framebuffer.get(), + // // None, + // // )?); + // // debug::add_sink(self.fbconsole.get(), LogLevel::Info); + + // // // self.tty.init(CombinedTerminal::new(self.fbconsole.get())); + + // // devfs::add_named_block_device(self.framebuffer.get(), "fb0")?; + // // // devfs::add_char_device(self.tty.get(), CharDeviceType::TtyRegular)?; + // // console::add_console_autoflush(self.fbconsole.get()); + + // Ok(()) } fn init_initrd(initrd_start: PhysicalAddress, initrd_end: PhysicalAddress) { diff --git a/kernel/src/device/display/device.rs b/kernel/src/device/display/device.rs new file mode 100644 index 00000000..45e64bcb --- /dev/null +++ b/kernel/src/device/display/device.rs @@ -0,0 +1,87 @@ +//! Display device structures and interfaces +use core::{ + mem::MaybeUninit, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use abi::{error::Error, io::DeviceRequest, process::ProcessId}; +use alloc::{boxed::Box, collections::btree_map::BTreeMap, sync::Arc, vec::Vec}; +use async_trait::async_trait; +use device_api::Device; +use libk::{ + device::display::{DisplayDevice, DisplayOwner}, + task::process::Process, + vfs::block::BlockDevice, +}; +use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider}; +use libk_util::sync::spin_rwlock::IrqSafeRwLock; + +/// Convenience wrapper for accessing display framebuffers from the kernel side +pub struct KernelFramebufferAccess<'a> { + display: &'a dyn DisplayDevice, + base: usize, + stride: usize, + size: usize, +} + +impl<'a> KernelFramebufferAccess<'a> { + /// Attempts to acquire kernel access to a framebuffer of a display device + pub fn acquire(display: &'a dyn DisplayDevice) -> Result { + // Lock the display by the kernel + display.lock(DisplayOwner::Kernel)?; + + match display.active_framebuffer() { + Ok(info) if let Some(kernel_base) = info.kernel_base => Ok(Self { + display, + base: kernel_base, + size: info.size / size_of::(), + stride: info.stride / size_of::(), + }), + _ => { + // Release the lock, couldn't get the framebuffer + display.unlock(DisplayOwner::Kernel).ok(); + Err(Error::InvalidOperation) + } + } + } + + /// Updates a pixel in the framebuffer + #[inline] + #[optimize(speed)] + pub fn set_pixel(&mut self, x: u32, y: u32, pixel: u32) { + let p = y as usize * self.stride + x as usize; + if p >= self.size { + return; + } + self[p] = pixel; + } +} + +impl Deref for KernelFramebufferAccess<'_> { + type Target = [u32]; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { + core::slice::from_raw_parts(core::ptr::with_exposed_provenance(self.base), self.size) + } + } +} + +impl DerefMut for KernelFramebufferAccess<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + core::slice::from_raw_parts_mut( + core::ptr::with_exposed_provenance_mut(self.base), + self.size, + ) + } + } +} + +impl Drop for KernelFramebufferAccess<'_> { + fn drop(&mut self) { + self.display.unlock(DisplayOwner::Kernel).ok(); + } +} diff --git a/kernel/src/device/display/fb_console.rs b/kernel/src/device/display/fb_console.rs index 50ad2fab..b818f355 100644 --- a/kernel/src/device/display/fb_console.rs +++ b/kernel/src/device/display/fb_console.rs @@ -1,19 +1,22 @@ //! Framebuffer console driver +use core::mem::MaybeUninit; + use abi::error::Error; +use libk::device::display::DisplayDevice; use libk_util::sync::IrqSafeSpinlock; use crate::debug::DebugSink; use super::{ console::{Attributes, ConsoleBuffer, ConsoleState, DisplayConsole}, + device::KernelFramebufferAccess, font::PcScreenFont, linear_fb::LinearFramebuffer, - DisplayDevice, }; struct Inner { - framebuffer: &'static LinearFramebuffer, + display: &'static dyn DisplayDevice, font: PcScreenFont<'static>, char_width: u32, char_height: u32, @@ -56,6 +59,9 @@ impl DisplayConsole for FramebufferConsole { fn flush(&self, state: &mut ConsoleState) { let mut inner = self.inner.lock(); + let Ok(mut framebuffer) = KernelFramebufferAccess::acquire(inner.display) else { + return; + }; let font = inner.font; let cw = inner.char_width; let ch = inner.char_height; @@ -70,13 +76,13 @@ impl DisplayConsole for FramebufferConsole { let cursor_col = state.cursor_col; let cursor_row = state.cursor_row; - inner.fill_rect( - old_cursor_col * cw, - old_cursor_row * ch, - cw, - ch, - state.bg_color.as_rgba(false), - ); + // inner.fill_rect( + // old_cursor_col * cw, + // old_cursor_row * ch, + // cw, + // ch, + // state.bg_color.as_rgba(false), + // ); while let Some((row_idx, row)) = iter.next_dirty() { if row_idx >= inner.height { @@ -93,28 +99,27 @@ impl DisplayConsole for FramebufferConsole { if row_idx == cursor_row && col_idx == cursor_col as usize { core::mem::swap(&mut fg, &mut bg); } - inner.draw_glyph( - font, - DrawGlyph { - sx: (col_idx as u32) * cw, - sy: row_idx * ch, - c: glyph, - fg, - bg, - bytes_per_line, - }, - ); + + let glyph = DrawGlyph { + sx: (col_idx as u32) * cw, + sy: row_idx * ch, + c: glyph, + fg, + bg, + bytes_per_line, + }; + draw_glyph(&mut framebuffer, font, glyph); } } - // Place cursor - inner.fill_rect( - cursor_col * cw, - cursor_row * ch, - cw, - ch, - state.fg_color.as_rgba(false), - ); + // // Place cursor + // inner.fill_rect( + // cursor_col * cw, + // cursor_row * ch, + // cw, + // ch, + // state.fg_color.as_rgba(false), + // ); inner.cursor_col = cursor_col; inner.cursor_row = cursor_row; @@ -122,22 +127,31 @@ impl DisplayConsole for FramebufferConsole { } impl FramebufferConsole { - /// Constructs an instance of console from its framebuffer reference + /// Constructs an instance of console from its framebuffer reference. pub fn from_framebuffer( - framebuffer: &'static LinearFramebuffer, + // framebuffer: &'static LinearFramebuffer, + display: &'static dyn DisplayDevice, font: Option>, ) -> Result { + let mut framebuffer = [MaybeUninit::uninit()]; + let mode = display + .active_display_mode() + .ok_or(Error::InvalidOperation)?; + display.active_framebuffers(&mut framebuffer).unwrap(); + + let framebuffer = unsafe { framebuffer[0].assume_init() }; + let font = font.unwrap_or_default(); let char_width = font.width(); let char_height = font.height(); - let dim = framebuffer.dimensions(); - let buffer = ConsoleBuffer::new(dim.height / char_height)?; + + let buffer = ConsoleBuffer::new(mode.height / char_height)?; let inner = Inner { - framebuffer, + display, font, - width: dim.width / char_width, - height: dim.height / char_height, + width: mode.width / char_width, + height: mode.height / char_height, char_width, char_height, cursor_row: 0, @@ -151,48 +165,88 @@ impl FramebufferConsole { } } -impl Inner { - #[optimize(speed)] - fn draw_glyph(&mut self, font: PcScreenFont<'static>, g: DrawGlyph) { - let Some(mut fb) = (unsafe { self.framebuffer.lock() }) else { - return; - }; - - let mut c = g.c as u32; - if c >= font.len() { - c = b'?' as u32; - } - - let mut glyph = font.raw_glyph_data(c); - - let mut y = 0; - - while y < font.height() { - let mut mask = 1 << (font.width() - 1); - let mut x = 0; - - while x < font.width() { - let v = if glyph[0] & mask != 0 { g.fg } else { g.bg }; - let v = v | 0xFF000000; - fb[g.sy + y][(g.sx + x) as usize] = v; - mask >>= 1; - x += 1; - } - - glyph = &glyph[g.bytes_per_line..]; - y += 1; - } +fn draw_glyph( + framebuffer: &mut KernelFramebufferAccess, + font: PcScreenFont<'static>, + g: DrawGlyph, +) { + let mut c = g.c as u32; + if c >= font.len() { + c = b'?' as u32; } - #[optimize(speed)] - fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, val: u32) { - let Some(mut fb) = (unsafe { self.framebuffer.lock() }) else { - return; - }; + let mut glyph = font.raw_glyph_data(c); - for i in 0..h { - let row = &mut fb[i + y]; - row[x as usize..(x + w) as usize].fill(val); + let mut y = 0; + + while y < font.height() { + let mut mask = 1 << (font.width() - 1); + let mut x = 0; + + while x < font.width() { + let v = if glyph[0] & mask != 0 { g.fg } else { g.bg }; + let v = v | 0xFF000000; + framebuffer.set_pixel(g.sx + x, g.sy + y, v); + // framebuffer.set_pixel(g.sy + y, g.sx + x, v); + mask >>= 1; + x += 1; } + + glyph = &glyph[g.bytes_per_line..]; + y += 1; } } + +// impl Inner { +// #[optimize(speed)] +// fn draw_glyph(&mut self, font: PcScreenFont<'static>, g: DrawGlyph) { +// let Ok(framebuffer) = KernelFramebufferAccess::acquire(self.display) else { +// return; +// }; +// +// // let Some(mut fb) = (unsafe { self.framebuffer.lock() }) else { +// // return; +// // }; +// +// // let mut c = g.c as u32; +// // if c >= font.len() { +// // c = b'?' as u32; +// // } +// +// // let mut glyph = font.raw_glyph_data(c); +// +// // let mut y = 0; +// +// // while y < font.height() { +// // let mut mask = 1 << (font.width() - 1); +// // let mut x = 0; +// +// // while x < font.width() { +// // let v = if glyph[0] & mask != 0 { g.fg } else { g.bg }; +// // let v = v | 0xFF000000; +// // fb[g.sy + y][(g.sx + x) as usize] = v; +// // mask >>= 1; +// // x += 1; +// // } +// +// // glyph = &glyph[g.bytes_per_line..]; +// // y += 1; +// // } +// } +// +// #[optimize(speed)] +// fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, val: u32) { +// if self.display.lock(DisplayOwner::Kernel).is_err() { +// return; +// } +// +// // let Some(mut fb) = (unsafe { self.framebuffer.lock() }) else { +// // return; +// // }; +// +// // for i in 0..h { +// // let row = &mut fb[i + y]; +// // row[x as usize..(x + w) as usize].fill(val); +// // } +// } +// } diff --git a/kernel/src/device/display/linear_fb.rs b/kernel/src/device/display/linear_fb.rs index 3b975e4b..40b64597 100644 --- a/kernel/src/device/display/linear_fb.rs +++ b/kernel/src/device/display/linear_fb.rs @@ -1,10 +1,20 @@ //! Abstract linear framebuffer device implementation -use core::ops::{Index, IndexMut}; +use core::{ + mem::MaybeUninit, + ops::{Index, IndexMut}, +}; use abi::{error::Error, io::DeviceRequest, process::ProcessId}; +use alloc::sync::Arc; use device_api::Device; -use libk::{task::thread::Thread, vfs::block::BlockDevice}; +use libk::{ + device::display::{ + DisplayDevice, DisplayDimensions, DisplayMode, DisplayOwner, FramebufferInfo, PixelFormat, + }, + task::{process::Process, thread::Thread}, + vfs::block::BlockDevice, +}; use libk_mm::{ address::PhysicalAddress, device::{DeviceMemoryAttributes, DeviceMemoryCaching, RawDeviceMemoryMapping}, @@ -15,26 +25,25 @@ use libk_util::sync::IrqSafeSpinlock; use crate::arch::L3; -use super::{DisplayDevice, DisplayDimensions}; - struct Inner { - base: usize, + mapping: RawDeviceMemoryMapping, stride: usize, - holder: Option, + owner: DisplayOwner, } -#[doc(hidden)] -pub struct FramebufferAccess { - dimensions: DisplayDimensions, - base: usize, - stride: usize, -} +// #[doc(hidden)] +// pub struct FramebufferAccess { +// dimensions: DisplayDimensions, +// base: usize, +// stride: usize, +// } /// Linear framebuffer wrapper pub struct LinearFramebuffer { inner: IrqSafeSpinlock, phys_base: PhysicalAddress, dimensions: DisplayDimensions, + mode: DisplayMode, size: usize, } @@ -51,7 +60,7 @@ impl LinearFramebuffer { width: u32, height: u32, ) -> Result { - let base = unsafe { + let mapping = unsafe { RawDeviceMemoryMapping::map( phys_base.into(), size, @@ -59,13 +68,19 @@ impl LinearFramebuffer { caching: DeviceMemoryCaching::Cacheable, }, ) - }? - .leak(); + }?; let inner = Inner { - base, + mapping, stride, - holder: None, + owner: DisplayOwner::None, + }; + let mode = DisplayMode { + index: 0, + width, + height, + frames_per_second: u32::MAX, + pixel_format: PixelFormat::R8G8B8A8, }; let res = Self { @@ -73,58 +88,13 @@ impl LinearFramebuffer { dimensions: DisplayDimensions { width, height }, phys_base, size, + mode, }; - // Clear the screen - res.lock().unwrap().fill_rows(0, height, 0); + // TODO clear the screen Ok(res) } - - /// Temporary function to provide framebuffer access - /// - /// # Safety - /// - /// Unsafe: access is not synchronized - // TODO doesn't actually lock - pub unsafe fn lock(&self) -> Option { - let inner = self.inner.lock(); - - if inner.holder.is_some() { - None - } else { - Some(FramebufferAccess { - dimensions: self.dimensions, - base: inner.base, - stride: inner.stride, - }) - } - } -} - -impl BlockDevice for LinearFramebuffer { - fn size(&self) -> Result { - Ok(self.size as _) - } - - fn is_readable(&self) -> bool { - false - } - - fn is_writable(&self) -> bool { - false - } - - fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> { - let thread = Thread::current(); - match req { - DeviceRequest::AcquireDevice => { - self.inner.lock().holder.replace(thread.process_id()); - Ok(()) - } - _ => todo!(), - } - } } impl PageProvider for LinearFramebuffer { @@ -164,79 +134,142 @@ impl Device for LinearFramebuffer { } impl DisplayDevice for LinearFramebuffer { - fn dimensions(&self) -> DisplayDimensions { - self.dimensions + fn synchronize(&self) -> Result<(), Error> { + Ok(()) + } + + fn set_display_mode(&self, _index: u32, _double_buffer: bool) -> Result<(), Error> { + Err(Error::InvalidOperation) + } + + fn active_display_mode(&self) -> Option { + Some(self.mode) + } + + fn query_display_modes(&self, modes: &mut [MaybeUninit]) -> Result { + if modes.len() < 1 { + return Err(Error::BufferTooSmall); + } + + modes[0].write(self.mode); + Ok(1) + } + + fn lock(&self, owner: DisplayOwner) -> Result<(), Error> { + let mut inner = self.inner.lock(); + if inner.owner == owner { + return Ok(()); + } + match (inner.owner, owner) { + (DisplayOwner::None | DisplayOwner::Kernel, DisplayOwner::Process(_)) => { + inner.owner = owner; + Ok(()) + } + (DisplayOwner::None, DisplayOwner::Kernel) => { + inner.owner = owner; + Ok(()) + } + (_, _) => Err(Error::WouldBlock), + } + } + + fn unlock(&self, owner: DisplayOwner) -> Result<(), Error> { + let mut inner = self.inner.lock(); + if inner.owner == owner { + inner.owner = DisplayOwner::None; + Ok(()) + } else { + Err(Error::InvalidArgument) + } + } + + fn active_framebuffers( + &self, + output: &mut [MaybeUninit], + ) -> Result { + if output.len() < 1 { + return Err(1); + } + + let inner = self.inner.lock(); + output[0].write(FramebufferInfo { + base: self.phys_base, + kernel_base: Some(inner.mapping.address), + size: self.size, + stride: inner.stride, + }); + Ok(1) } } -impl FramebufferAccess { - /// Copies `count` rows starting from `src_row` to `dst_row` - #[optimize(speed)] - pub fn copy_rows(&mut self, src_row: u32, dst_row: u32, count: u32) { - use core::ffi::c_void; - extern "C" { - fn memmove(dst: *mut c_void, src: *const c_void, len: usize) -> *mut c_void; - } - - if src_row == dst_row { - return; - } - - let src_end_row = core::cmp::min(self.dimensions.height, src_row + count); - let dst_end_row = core::cmp::min(self.dimensions.height, dst_row + count); - - if dst_end_row <= dst_row || src_end_row <= dst_row { - return; - } - let count = core::cmp::min(src_end_row - src_row, dst_end_row - dst_row) as usize; - - let src_base_addr = self.base + self.stride * src_row as usize; - let dst_base_addr = self.base + self.stride * dst_row as usize; - - unsafe { - memmove( - dst_base_addr as *mut c_void, - src_base_addr as *mut c_void, - self.stride * count, - ); - } - } - - /// Fills the specified number of pixel rows with given pixel value - pub fn fill_rows(&mut self, start_row: u32, count: u32, value: u32) { - use core::ffi::c_void; - extern "C" { - fn memset(s: *mut c_void, c: u32, len: usize) -> *mut c_void; - } - - let end_row = core::cmp::min(self.dimensions.height, start_row + count); - if end_row <= start_row { - return; - } - - let count = (end_row - start_row) as usize; - let base_addr = self.base + self.stride * start_row as usize; - - unsafe { - memset(base_addr as *mut c_void, value, self.stride * count); - } - } -} - -impl Index for FramebufferAccess { - type Output = [u32]; - - fn index(&self, index: u32) -> &Self::Output { - assert!(index < self.dimensions.height); - let row_addr = self.base + self.stride * index as usize; - unsafe { core::slice::from_raw_parts(row_addr as *const u32, self.dimensions.width as _) } - } -} - -impl IndexMut for FramebufferAccess { - fn index_mut(&mut self, index: u32) -> &mut Self::Output { - assert!(index < self.dimensions.height); - let row_addr = self.base + self.stride * index as usize; - unsafe { core::slice::from_raw_parts_mut(row_addr as *mut u32, self.dimensions.width as _) } - } -} +// impl FramebufferAccess { +// /// Copies `count` rows starting from `src_row` to `dst_row` +// #[optimize(speed)] +// pub fn copy_rows(&mut self, src_row: u32, dst_row: u32, count: u32) { +// use core::ffi::c_void; +// extern "C" { +// fn memmove(dst: *mut c_void, src: *const c_void, len: usize) -> *mut c_void; +// } +// +// if src_row == dst_row { +// return; +// } +// +// let src_end_row = core::cmp::min(self.dimensions.height, src_row + count); +// let dst_end_row = core::cmp::min(self.dimensions.height, dst_row + count); +// +// if dst_end_row <= dst_row || src_end_row <= dst_row { +// return; +// } +// let count = core::cmp::min(src_end_row - src_row, dst_end_row - dst_row) as usize; +// +// let src_base_addr = self.base + self.stride * src_row as usize; +// let dst_base_addr = self.base + self.stride * dst_row as usize; +// +// unsafe { +// memmove( +// dst_base_addr as *mut c_void, +// src_base_addr as *mut c_void, +// self.stride * count, +// ); +// } +// } +// +// /// Fills the specified number of pixel rows with given pixel value +// pub fn fill_rows(&mut self, start_row: u32, count: u32, value: u32) { +// use core::ffi::c_void; +// extern "C" { +// fn memset(s: *mut c_void, c: u32, len: usize) -> *mut c_void; +// } +// +// let end_row = core::cmp::min(self.dimensions.height, start_row + count); +// if end_row <= start_row { +// return; +// } +// +// let count = (end_row - start_row) as usize; +// let base_addr = self.base + self.stride * start_row as usize; +// +// unsafe { +// memset(base_addr as *mut c_void, value, self.stride * count); +// } +// } +// } +// +// impl Index for FramebufferAccess { +// type Output = [u32]; +// +// fn index(&self, index: u32) -> &Self::Output { +// assert!(index < self.dimensions.height); +// let row_addr = self.base + self.stride * index as usize; +// unsafe { core::slice::from_raw_parts(row_addr as *const u32, self.dimensions.width as _) } +// } +// } +// +// impl IndexMut for FramebufferAccess { +// fn index_mut(&mut self, index: u32) -> &mut Self::Output { +// assert!(index < self.dimensions.height); +// let row_addr = self.base + self.stride * index as usize; +// unsafe { core::slice::from_raw_parts_mut(row_addr as *mut u32, self.dimensions.width as _) } +// } +// } diff --git a/kernel/src/device/display/mod.rs b/kernel/src/device/display/mod.rs index 02df0cca..f70d884e 100644 --- a/kernel/src/device/display/mod.rs +++ b/kernel/src/device/display/mod.rs @@ -1,25 +1,19 @@ //! Display device interfaces +use core::{mem::MaybeUninit, ops::Deref}; + +use abi::error::Error; +use alloc::{boxed::Box, sync::Arc}; +use async_trait::async_trait; +use libk::{task::process::Process, vfs::block::BlockDevice}; +use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider}; + use super::Device; pub mod console; +pub mod device; pub mod font; #[cfg(feature = "fb_console")] pub mod fb_console; pub mod linear_fb; - -/// Resolution of the display device -#[derive(Clone, Copy, Debug)] -pub struct DisplayDimensions { - /// Width of the display in pixels - pub width: u32, - /// Height of the display in pixels - pub height: u32, -} - -/// Abstract display device interface -pub trait DisplayDevice: Device { - /// Returns the dimensions of the display in its current mode - fn dimensions(&self) -> DisplayDimensions; -} diff --git a/lib/abi/src/io/device.rs b/lib/abi/src/io/device.rs index 0a647619..1f01e85f 100644 --- a/lib/abi/src/io/device.rs +++ b/lib/abi/src/io/device.rs @@ -4,6 +4,15 @@ use crate::generated::ProcessGroupId; use super::terminal; +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Framebuffer { + pub width: u32, + pub height: u32, + pub stride: usize, + pub size: usize, +} + // TODO make this accept references when "reading" something /// Describes device-specific requests #[derive(Clone, Debug)] @@ -20,8 +29,14 @@ pub enum DeviceRequest { GetTerminalSize(MaybeUninit), /// Sets a foreground process group ID for the terminal SetTerminalGroup(ProcessGroupId), + /// "Acquires" ownership of the device, preventing others from accessing it AcquireDevice, /// "Releases" ownership of the device ReleaseDevice, + + /// Retrieves display's currently active framebuffer info + GetActiveFramebuffer(MaybeUninit), + /// Tells the display driver to send the framebuffer data to the display + FlushDisplay, } diff --git a/lib/abi/src/io/mod.rs b/lib/abi/src/io/mod.rs index 8ef08893..53454d14 100644 --- a/lib/abi/src/io/mod.rs +++ b/lib/abi/src/io/mod.rs @@ -10,7 +10,7 @@ pub use crate::generated::{ PollControl, RawFd, TimerOptions, UnmountOptions, UserId, }; pub use channel::{ChannelPublisherId, MessageDestination, ReceivedMessageMetadata, SentMessage}; -pub use device::DeviceRequest; +pub use device::{DeviceRequest, Framebuffer}; pub use file::{FileControl, FileMetadataUpdate, FileMetadataUpdateMode, SeekFrom}; pub use filesystem::FilesystemControl; pub use input::{KeyboardKey, KeyboardKeyCode, KeyboardKeyEvent}; diff --git a/lib/runtime/src/io.rs b/lib/runtime/src/io.rs index 50eea59c..70fbcb37 100644 --- a/lib/runtime/src/io.rs +++ b/lib/runtime/src/io.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] pub mod device { - pub use abi::io::{DeviceRequest, MountOptions, UnmountOptions}; + pub use abi::io::{DeviceRequest, Framebuffer, MountOptions, UnmountOptions}; } pub mod terminal { diff --git a/userspace/arch/aarch64/rc.d/99-colors b/userspace/arch/aarch64/rc.d/99-colors new file mode 100755 index 00000000..fcbfee7f --- /dev/null +++ b/userspace/arch/aarch64/rc.d/99-colors @@ -0,0 +1,3 @@ +#!/bin/sh + +/sbin/service start /bin/colors diff --git a/userspace/colors/src/display.rs b/userspace/colors/src/display.rs index e5ea6811..816cb4ac 100644 --- a/userspace/colors/src/display.rs +++ b/userspace/colors/src/display.rs @@ -1,5 +1,6 @@ use std::{ - fs::OpenOptions, + fs::{File, OpenOptions}, + mem::MaybeUninit, os::yggdrasil::io::{ device::{DeviceRequest, FdDeviceRequest}, mapping::FileMapping, @@ -15,6 +16,8 @@ pub struct Display<'a> { width: usize, height: usize, + stride: usize, + size: usize, } pub struct Point(pub T, pub T); @@ -23,14 +26,21 @@ impl Display<'_> { pub fn open() -> Result { let file = OpenOptions::new().open("/dev/fb0")?; - unsafe { + let framebuffer = unsafe { file.device_request(&mut DeviceRequest::AcquireDevice)?; - } - let width = 640; - let height = 480; + let mut request = DeviceRequest::GetActiveFramebuffer(MaybeUninit::uninit()); + file.device_request(&mut request)?; + let DeviceRequest::GetActiveFramebuffer(framebuffer) = request else { + unreachable!() + }; + framebuffer.assume_init() + }; - let mut mapping = FileMapping::new(file, 0, width * height * 4)?; + let width = framebuffer.width as usize; + let height = framebuffer.height as usize; + + let mut mapping = FileMapping::new(file, 0, framebuffer.size)?; let data = unsafe { std::slice::from_raw_parts_mut(mapping.as_mut_ptr() as *mut u32, width * height) }; @@ -41,9 +51,19 @@ impl Display<'_> { width, height, + stride: framebuffer.stride / size_of::(), + size: framebuffer.size / size_of::(), }) } + pub fn flush(&mut self) { + unsafe { + self.mapping + .device_request(&mut DeviceRequest::FlushDisplay) + .ok() + }; + } + pub fn width(&self) -> usize { self.width } @@ -86,5 +106,7 @@ impl Display<'_> { dst_chunk.copy_from_slice(src_chunk); } + + self.flush(); } } diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index 5d227ed9..5de118db 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -165,8 +165,8 @@ fn run_aarch64( kernel: kernel_bin, initrd: Some(initrd), }) - .with_memory_megabytes(config.machine.aarch64.memory) - .disable_display(); + .with_memory_megabytes(config.machine.aarch64.memory); + // .disable_display(); for device in devices { qemu.with_device(device);