blob: 03617d32746ffff5f2d9512b19c32249e0c4f3ab [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.
use super::framebuffer_server::{init_viewport_scene, spawn_view_provider, FramebufferServer};
use crate::{
device::{features::AspectRatio, DeviceMode, DeviceOps},
fs::{
buffers::{InputBuffer, OutputBuffer},
kobject::{KObjectDeviceAttribute, KType},
sysfs::SysFsDirectory,
*,
},
logging::*,
mm::{MemoryAccessorExt, ProtectionFlags},
syscalls::*,
task::{CurrentTask, Kernel},
types::*,
};
use fidl_fuchsia_math as fmath;
use fidl_fuchsia_ui_composition as fuicomposition;
use fidl_fuchsia_ui_display_singleton as fuidisplay;
use fidl_fuchsia_ui_views as fuiviews;
use fuchsia_component::client::connect_to_protocol_sync;
use fuchsia_zircon as zx;
use starnix_lock::RwLock;
use std::sync::Arc;
use zerocopy::AsBytes;
pub struct Framebuffer {
vmo: Arc<zx::Vmo>,
vmo_len: u32,
pub info: RwLock<fb_var_screeninfo>,
server: Option<Arc<FramebufferServer>>,
}
impl 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.
pub fn new(aspect_ratio: Option<&AspectRatio>) -> Result<Arc<Self>, Errno> {
let mut info = fb_var_screeninfo::default();
let display_size =
Self::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) = FramebufferServer::new(width, height) {
let server = Arc::new(server);
let vmo = Arc::new(server.get_vmo()?);
let vmo_len = vmo.info().map_err(|_| errno!(EINVAL))?.size_bytes as u32;
// Fill the buffer with white pixels as a placeholder.
if let Err(err) = vmo.write(&vec![0xff; vmo_len as usize], 0) {
log_warn!("could not write initial framebuffer: {:?}", err);
}
Ok(Arc::new(Self { vmo, vmo_len, server: Some(server), info: RwLock::new(info) }))
} else {
let vmo_len = info.xres * info.yres * (info.bits_per_pixel / 8);
let vmo = Arc::new(zx::Vmo::create(vmo_len as u64).map_err(|s| match s {
zx::Status::NO_MEMORY => errno!(ENOMEM),
_ => impossible_error(s),
})?);
Ok(Arc::new(Self { vmo, vmo_len, server: None, info: RwLock::new(info) }))
}
}
/// Starts serving a view based on this framebuffer in `outgoing_dir`.
///
/// # Parameters
/// * `view_bound_protocols`: handles to input clients which will be associated with the view
/// * `outgoing_dir`: the path under which the `ViewProvider` protocol will be served
pub fn start_server(
&self,
view_bound_protocols: fuicomposition::ViewBoundProtocols,
view_identity: fuiviews::ViewIdentityOnCreation,
outgoing_dir: fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>,
) {
if let Some(server) = &self.server {
spawn_view_provider(server.clone(), view_bound_protocols, view_identity, outgoing_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);
}
}
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::Time::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)
}
}
impl DeviceOps for Arc<Framebuffer> {
fn open(
&self,
_current_task: &CurrentTask,
dev: DeviceType,
node: &FsNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
if dev.minor() != 0 {
return error!(ENODEV);
}
node.update_info(|info| {
info.size = self.vmo_len as usize;
info.blocks = self.vmo.get_size().map_err(impossible_error)? as usize / info.blksize;
Ok(())
})?;
Ok(Box::new(Arc::clone(self)))
}
}
impl FileOps for Arc<Framebuffer> {
fileops_impl_seekable!();
fn ioctl(
&self,
_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(&b"Starnix\0\0\0\0\0\0\0\0\0"[..]).unwrap(),
smem_start: 0,
smem_len: self.vmo_len,
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)
}
}
}
fn read(
&self,
file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
VmoFileObject::read(&self.vmo, file, offset, data)
}
fn write(
&self,
file: &FileObject,
current_task: &CurrentTask,
offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
VmoFileObject::write(&self.vmo, file, current_task, offset, data)
}
fn get_vmo(
&self,
file: &FileObject,
current_task: &CurrentTask,
_length: Option<usize>,
prot: ProtectionFlags,
) -> Result<Arc<zx::Vmo>, Errno> {
VmoFileObject::get_vmo(&self.vmo, file, current_task, prot)
}
}
pub fn fb_device_init(kernel: &Arc<Kernel>) {
let graphics_class = kernel.device_registry.virtual_bus().get_or_create_child(
b"graphics",
KType::Class,
SysFsDirectory::new,
);
let fb_attr = KObjectDeviceAttribute::new(
Some(graphics_class),
b"fb0",
b"fb0",
DeviceType::FB0,
DeviceMode::Char,
);
kernel.add_and_register_device(fb_attr, kernel.framebuffer.clone());
}