display: basic virtio-gpu support, better display API

This commit is contained in:
Mark Poliakov 2024-12-06 18:03:18 +02:00
parent 278c63d961
commit 6bd3d387bf
26 changed files with 1555 additions and 298 deletions

16
Cargo.lock generated
View File

@ -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",
]

View File

@ -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" }

View File

@ -38,8 +38,12 @@ impl KernelTableManager for KernelTableManagerImpl {
_attrs: DeviceMemoryAttributes,
) -> Result<RawDeviceMemoryMapping<Self>, 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<Self>) {
todo!()
// todo!()
}
fn virtualize(phys: u64) -> usize {

View File

@ -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<NodeRef> = OneTimeInit::new();
/// Sets up the device filesystem
@ -54,6 +61,19 @@ pub fn add_named_block_device<S: Into<String>>(
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<S: Into<String>>(
base_name: S,
index: usize,

View File

@ -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();

View File

@ -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);

View File

@ -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"]

View File

@ -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::<Req>())?;
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::<ControlHeader>() {
log::warn!("virtio-gpu: invalid device response length: {len}");
return Err(Error::InvalidArgument);
}
let header = bytemuck::from_bytes(&buffer[..size_of::<ControlHeader>()]);
let data = &buffer[size_of::<ControlHeader>()..len];
Ok((header, data))
}
fn send_recv_no_data<Req: Pod>(
&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::<ScanoutInfo>() * 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<u32, Error> {
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)
}
}

View File

@ -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<VirtQueue>,
}
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<ScanoutInfo>,
framebuffer: Option<Framebuffer>,
owner: DisplayOwner,
response: PageBox<[u8]>,
}
pub struct VirtioGpu<T: Transport> {
transport: IrqSafeSpinlock<T>,
pci_device_info: Option<PciDeviceInfo>,
queues: OneTimeInit<Queues>,
config: IrqSafeRwLock<Config>,
num_scanouts: usize,
}
impl<T: Transport> VirtioGpu<T> {
pub fn new(transport: T, info: Option<PciDeviceInfo>) -> Result<Self, Error> {
// 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::<u32>()];
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<DeviceStatus, Error> {
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<T> {
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::<u32>();
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<T: Transport + 'static> Device for VirtioGpu<T> {
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<T: Transport + 'static> PageProvider for VirtioGpu<T> {
fn get_page(&self, offset: u64) -> Result<PhysicalAddress, Error> {
// 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<PhysicalAddress, Error> {
todo!()
}
fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> {
Ok(())
}
}
impl<T: Transport + 'static> DisplayDevice for VirtioGpu<T> {
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<FramebufferInfo>],
) -> Result<usize, usize> {
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<DisplayMode> {
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<DisplayMode>]) -> Result<usize, Error> {
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)
}

View File

@ -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<usize>,
/// 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<DisplayMode>;
/// Returns the list of available display modes
fn query_display_modes(&self, modes: &mut [MaybeUninit<DisplayMode>]) -> Result<usize, Error>;
/// 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<FramebufferInfo>],
) -> Result<usize, usize>;
/// Returns the currently active framebuffer, if there is exactly one. Returns an error
/// otherwise.
fn active_framebuffer(&self) -> Result<FramebufferInfo, usize> {
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<BTreeMap<u32, &'static DisplayDeviceWrapper>> =
IrqSafeRwLock::new(BTreeMap::new());
static DEVICE_CALLBACK: OneTimeInit<fn(&'static DisplayDeviceWrapper)> = 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<usize, Error> {
Err(Error::InvalidOperation)
}
async fn write(&self, _pos: u64, _buf: &[u8]) -> Result<usize, Error> {
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<PhysicalAddress, Error> {
self.inner.clone_page(offset, src_phys, src_attrs)
}
fn get_page(&self, offset: u64) -> Result<PhysicalAddress, Error> {
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
}
}

View File

@ -0,0 +1,3 @@
pub mod display;
pub use libk_device::*;

View File

@ -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<Align, Bytes: ?Sized> {
pub align: [Align; 0],

View File

@ -16,8 +16,7 @@ pub struct SharedMemory {
impl SharedMemory {
/// Creates a new buffer of shared memory
pub fn new(size: usize) -> Result<SharedMemory, Error> {
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())

View File

@ -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"),

View File

@ -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");

View File

@ -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<AcpiTables<AcpiHandlerImpl>>,
// Display
framebuffer: OneTimeInit<LinearFramebuffer>,
gop_framebuffer: OneTimeInit<LinearFramebuffer>,
fbconsole: OneTimeInit<FramebufferConsole>,
}
@ -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) {

View File

@ -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<Self, Error> {
// 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::<u32>(),
stride: info.stride / size_of::<u32>(),
}),
_ => {
// 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();
}
}

View File

@ -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<PcScreenFont<'static>>,
) -> Result<Self, Error> {
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);
// // }
// }
// }

View File

@ -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<ProcessId>,
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<Inner>,
phys_base: PhysicalAddress,
dimensions: DisplayDimensions,
mode: DisplayMode,
size: usize,
}
@ -51,7 +60,7 @@ impl LinearFramebuffer {
width: u32,
height: u32,
) -> Result<Self, Error> {
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<FramebufferAccess> {
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<u64, Error> {
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<DisplayMode> {
Some(self.mode)
}
fn query_display_modes(&self, modes: &mut [MaybeUninit<DisplayMode>]) -> Result<usize, Error> {
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<FramebufferInfo>],
) -> Result<usize, usize> {
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<u32> 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<u32> 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<u32> 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<u32> 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 _) }
// }
// }

View File

@ -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;
}

View File

@ -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<terminal::TerminalSize>),
/// 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<Framebuffer>),
/// Tells the display driver to send the framebuffer data to the display
FlushDisplay,
}

View File

@ -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};

View File

@ -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 {

View File

@ -0,0 +1,3 @@
#!/bin/sh
/sbin/service start /bin/colors

View File

@ -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<T>(pub T, pub T);
@ -23,14 +26,21 @@ impl Display<'_> {
pub fn open() -> Result<Self, Error> {
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::<u32>(),
size: framebuffer.size / size_of::<u32>(),
})
}
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();
}
}

View File

@ -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);