blob: 3c10c09832a023baf6762895b7602b0f62391123 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![recursion_limit = "512"]
mod server;
use crate::server::{FramebufferServer, init_viewport_scene, start_presentation_loop};
use fuchsia_component::client::connect_to_protocol_sync;
use starnix_core::device::kobject::DeviceMetadata;
use starnix_core::device::{DeviceMode, DeviceOps};
use starnix_core::mm::MemoryAccessorExt;
use starnix_core::mm::memory::MemoryObject;
use starnix_core::task::{CurrentTask, Kernel};
use starnix_core::vfs::{
CloseFreeSafe, FileObject, FileOps, NamespaceNode, fileops_impl_memory, fileops_impl_noop_sync,
};
use starnix_logging::{log_info, log_warn};
use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, RwLock, Unlocked};
use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
use starnix_uapi::device_type::DeviceType;
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::{UserAddress, UserRef};
use starnix_uapi::{
FB_TYPE_PACKED_PIXELS, FB_VISUAL_TRUECOLOR, FBIOGET_FSCREENINFO, FBIOGET_VSCREENINFO,
FBIOPUT_VSCREENINFO, errno, error, fb_bitfield, fb_fix_screeninfo, fb_var_screeninfo,
};
use std::sync::Arc;
use zerocopy::IntoBytes;
use {
fidl_fuchsia_io as fio, fidl_fuchsia_math as fmath,
fidl_fuchsia_ui_composition as fuicomposition, fidl_fuchsia_ui_display_singleton as fuidisplay,
fidl_fuchsia_ui_views as fuiviews,
};
fn get_display_size() -> Result<fmath::SizeU, Errno> {
let singleton_display_info =
connect_to_protocol_sync::<fuidisplay::InfoMarker>().map_err(|_| errno!(ENOENT))?;
let metrics = singleton_display_info
.get_metrics(zx::MonotonicInstant::INFINITE)
.map_err(|_| errno!(EINVAL))?;
let extent_in_px =
metrics.extent_in_px.ok_or("Failed to get extent_in_px").map_err(|_| errno!(EINVAL))?;
Ok(extent_in_px)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AspectRatio {
pub width: u32,
pub height: u32,
}
pub struct Framebuffer {
server: Option<Arc<FramebufferServer>>,
memory: Mutex<Option<Arc<MemoryObject>>>,
pub info: RwLock<fb_var_screeninfo>,
pub view_identity: Mutex<Option<fuiviews::ViewIdentityOnCreation>>,
pub view_bound_protocols: Mutex<Option<fuicomposition::ViewBoundProtocols>>,
}
impl Framebuffer {
/// Returns the current fraembuffer if one was created for this kernel.
pub fn get(kernel: &Kernel) -> Result<Arc<Self>, Errno> {
kernel.expando.get_or_try_init(|| error!(EINVAL))
}
/// Initialize the framebuffer device. Should only be called once per kernel.
pub fn device_init<L>(
locked: &mut Locked<L>,
system_task: &CurrentTask,
aspect_ratio: Option<AspectRatio>,
enable_visual_debugging: bool,
) -> Result<Arc<Framebuffer>, Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
let kernel = system_task.kernel();
let registry = &kernel.device_registry;
let framebuffer = kernel
.expando
.get_or_try_init(|| Framebuffer::new(aspect_ratio, enable_visual_debugging))?;
let graphics_class = registry.objects.graphics_class();
registry.register_device(
locked,
system_task,
"fb0".into(),
DeviceMetadata::new("fb0".into(), DeviceType::FB0, DeviceMode::Char),
graphics_class,
FramebufferDevice { framebuffer: framebuffer.clone() },
)?;
Ok(framebuffer)
}
/// Creates a new `Framebuffer` fit to the screen, while maintaining the provided aspect ratio.
///
/// If the `aspect_ratio` is `None`, the framebuffer will be scaled to the display.
fn new(
aspect_ratio: Option<AspectRatio>,
enable_visual_debugging: bool,
) -> Result<Self, Errno> {
let mut info = fb_var_screeninfo::default();
let display_size = get_display_size().unwrap_or(fmath::SizeU { width: 700, height: 1200 });
// If the container has a specific aspect ratio set, use that to fit the framebuffer
// inside of the display.
let (feature_width, feature_height) = aspect_ratio
.map(|ar| (ar.width, ar.height))
.unwrap_or((display_size.width, display_size.height));
// Scale to framebuffer to fit the display, while maintaining the expected aspect ratio.
let ratio =
std::cmp::min(display_size.width / feature_width, display_size.height / feature_height);
let (width, height) = (feature_width * ratio, feature_height * ratio);
info.xres = width;
info.yres = height;
info.xres_virtual = info.xres;
info.yres_virtual = info.yres;
info.bits_per_pixel = 32;
info.red = fb_bitfield { offset: 0, length: 8, msb_right: 0 };
info.green = fb_bitfield { offset: 8, length: 8, msb_right: 0 };
info.blue = fb_bitfield { offset: 16, length: 8, msb_right: 0 };
info.transp = fb_bitfield { offset: 24, length: 8, msb_right: 0 };
if let Ok((server, memory)) = FramebufferServer::new(width, height) {
let server = Arc::new(server);
let memory_len = memory.info()?.size_bytes as u32;
// Fill the buffer with black pixels as a placeholder, if visual debug is off.
// Fill the buffer with purple, if visual debug is on.
let background = if enable_visual_debugging {
[0xff, 0x00, 0xff, 0xff].repeat((memory_len / 4) as usize)
} else {
vec![0x00; memory_len as usize]
};
if let Err(err) = memory.write(&background, 0) {
log_warn!("could not write initial framebuffer: {:?}", err);
}
Ok(Self {
server: Some(server),
memory: Mutex::new(Some(memory)),
info: RwLock::new(info),
view_identity: Default::default(),
view_bound_protocols: Default::default(),
})
} else {
Ok(Self {
server: None,
memory: Default::default(),
info: RwLock::new(info),
view_identity: Default::default(),
view_bound_protocols: Default::default(),
})
}
}
/// Starts presenting a view based on this framebuffer.
///
/// # Parameters
/// * `incoming_dir`: the incoming service directory under which the
/// `fuchsia.element.GraphicalPresenter` protocol can be retrieved.
pub fn start_server(&self, kernel: &Kernel, incoming_dir: Option<fio::DirectoryProxy>) {
if let Some(server) = &self.server {
let view_bound_protocols = self.view_bound_protocols.lock().take().unwrap();
let view_identity = self.view_identity.lock().take().unwrap();
log_info!("Presenting view using GraphicalPresenter");
start_presentation_loop(
kernel,
server.clone(),
view_bound_protocols,
view_identity,
incoming_dir,
);
}
}
/// Starts presenting a child view instead of the framebuffer.
///
/// # Parameters
/// * `viewport_token`: handles to the child view
pub fn present_view(&self, viewport_token: fuiviews::ViewportCreationToken) {
if let Some(server) = &self.server {
init_viewport_scene(server.clone(), viewport_token);
// Release the memory associated with the framebuffer.
let mut memory = self.memory.lock();
if let Some(memory_ref) = memory.as_ref() {
let bytes = memory_ref.get_size();
let refs = Arc::strong_count(memory_ref);
*memory = None;
log_info!("Released framebuffer memory ({} bytes, {} refs)", bytes, refs);
}
}
}
/// Returns the framebuffer's memory.
fn get_memory(&self) -> Result<Arc<MemoryObject>, Errno> {
self.memory.lock().clone().ok_or_else(|| errno!(EIO))
}
/// Returns the logical size of the framebuffer's memory.
fn memory_len(&self) -> usize {
self.memory
.lock()
.as_ref()
.map_or(0, |memory| memory.info().map_or(0, |info| info.size_bytes)) as usize
}
/// Returns the allocated size of the framebuffer's memory.
fn memory_size(&self) -> usize {
self.memory.lock().as_ref().map_or(0, |memory| memory.get_size()) as usize
}
}
#[derive(Clone)]
struct FramebufferDevice {
framebuffer: Arc<Framebuffer>,
}
impl DeviceOps for FramebufferDevice {
fn open(
&self,
_locked: &mut Locked<FileOpsCore>,
_current_task: &CurrentTask,
dev: DeviceType,
node: &NamespaceNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
if dev.minor() != 0 {
return error!(ENODEV);
}
node.entry.node.update_info(|info| {
info.size = self.framebuffer.memory_len();
info.blocks = self.framebuffer.memory_size() / info.blksize;
Ok(())
})?;
Ok(Box::new(Arc::clone(&self.framebuffer)))
}
}
/// `Framebuffer` doesn't implement the `close` method.
impl CloseFreeSafe for Framebuffer {}
impl FileOps for Framebuffer {
fileops_impl_memory!(self, &self.get_memory()?);
fileops_impl_noop_sync!();
fn ioctl(
&self,
_locked: &mut Locked<Unlocked>,
_file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
match request {
FBIOGET_FSCREENINFO => {
let info = self.info.read();
let finfo = fb_fix_screeninfo {
id: zerocopy::FromBytes::read_from_bytes(&b"Starnix\0\0\0\0\0\0\0\0\0"[..])
.unwrap(),
smem_start: 0,
smem_len: self.memory_len() as u32,
type_: FB_TYPE_PACKED_PIXELS,
visual: FB_VISUAL_TRUECOLOR,
line_length: info.bits_per_pixel / 8 * info.xres,
..fb_fix_screeninfo::default()
};
current_task.write_object(UserRef::new(user_addr), &finfo)?;
Ok(SUCCESS)
}
FBIOGET_VSCREENINFO => {
let info = self.info.read();
current_task.write_object(UserRef::new(user_addr), &*info)?;
Ok(SUCCESS)
}
FBIOPUT_VSCREENINFO => {
let new_info: fb_var_screeninfo =
current_task.read_object(UserRef::new(user_addr))?;
let old_info = self.info.read();
// We don't yet support actually changing anything
if new_info.as_bytes() != old_info.as_bytes() {
return error!(EINVAL);
}
Ok(SUCCESS)
}
_ => {
error!(EINVAL)
}
}
}
}