display: basic virtio-gpu support, better display API
This commit is contained in:
parent
278c63d961
commit
6bd3d387bf
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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" }
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
21
kernel/driver/virtio/gpu/Cargo.toml
Normal file
21
kernel/driver/virtio/gpu/Cargo.toml
Normal 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"]
|
286
kernel/driver/virtio/gpu/src/command.rs
Normal file
286
kernel/driver/virtio/gpu/src/command.rs
Normal 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)
|
||||
}
|
||||
}
|
404
kernel/driver/virtio/gpu/src/lib.rs
Normal file
404
kernel/driver/virtio/gpu/src/lib.rs
Normal 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)
|
||||
}
|
223
kernel/libk/src/device/display.rs
Normal file
223
kernel/libk/src/device/display.rs
Normal 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
|
||||
}
|
||||
}
|
3
kernel/libk/src/device/mod.rs
Normal file
3
kernel/libk/src/device/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod display;
|
||||
|
||||
pub use libk_device::*;
|
@ -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],
|
||||
|
@ -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())
|
||||
|
@ -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"),
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
|
87
kernel/src/device/display/device.rs
Normal file
87
kernel/src/device/display/device.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
@ -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 _) }
|
||||
// }
|
||||
// }
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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 {
|
||||
|
3
userspace/arch/aarch64/rc.d/99-colors
Executable file
3
userspace/arch/aarch64/rc.d/99-colors
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/sbin/service start /bin/colors
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user