Skip to main content

starnix_modules_framebuffer/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![recursion_limit = "512"]
6
7mod server;
8
9use crate::server::{FramebufferServer, init_viewport_scene, start_presentation_loop};
10use anyhow::anyhow;
11use argh::FromArgs;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_math as fmath;
14use fidl_fuchsia_ui_composition as fuicomposition;
15use fidl_fuchsia_ui_display_singleton as fuidisplay;
16use fidl_fuchsia_ui_views as fuiviews;
17use fuchsia_component::client::connect_to_protocol_sync;
18use starnix_core::device::kobject::DeviceMetadata;
19use starnix_core::device::{DeviceMode, DeviceOps};
20use starnix_core::mm::MemoryAccessorExt;
21use starnix_core::mm::memory::MemoryObject;
22use starnix_core::task::{CurrentTask, Kernel};
23use starnix_core::vfs::{
24    CloseFreeSafe, FileObject, FileOps, NamespaceNode, fileops_impl_memory, fileops_impl_noop_sync,
25};
26use starnix_logging::{log_info, log_warn, track_stub};
27use starnix_sync::{
28    FramebufferInfoLock, FramebufferMemoryLock, FramebufferViewBoundProtocolsLock,
29    FramebufferViewIdentityLock, LockDepMutex, LockDepRwLock,
30};
31use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
32use starnix_uapi::device_id::DeviceId;
33use starnix_uapi::errors::Errno;
34use starnix_uapi::open_flags::OpenFlags;
35use starnix_uapi::user_address::{MultiArchUserRef, UserAddress};
36use starnix_uapi::{
37    FB_BLANK_POWERDOWN, FB_BLANK_UNBLANK, FB_TYPE_PACKED_PIXELS, FB_VISUAL_TRUECOLOR, FBIOBLANK,
38    FBIOGET_FSCREENINFO, FBIOGET_VSCREENINFO, FBIOPUT_VSCREENINFO, errno, error, fb_bitfield,
39    fb_fix_screeninfo, fb_var_screeninfo, uapi,
40};
41use std::sync::Arc;
42use zerocopy::IntoBytes;
43
44fn get_display_size() -> Result<fmath::SizeU, Errno> {
45    let singleton_display_info =
46        connect_to_protocol_sync::<fuidisplay::InfoMarker>().map_err(|_| errno!(ENOENT))?;
47    let metrics = singleton_display_info
48        .get_metrics(zx::MonotonicInstant::INFINITE)
49        .map_err(|_| errno!(EINVAL))?;
50    let extent_in_px =
51        metrics.extent_in_px.ok_or("Failed to get extent_in_px").map_err(|_| errno!(EINVAL))?;
52    Ok(extent_in_px)
53}
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56struct AspectRatio {
57    width: u32,
58    height: u32,
59}
60
61impl std::str::FromStr for AspectRatio {
62    type Err = anyhow::Error;
63
64    fn from_str(ratio: &str) -> Result<Self, Self::Err> {
65        let (width_str, height_str) =
66            ratio.split_once(':').ok_or_else(|| anyhow!("Invalid aspect_ratio: {ratio:?}"))?;
67        let width: u32 = width_str.parse().map_err(|_| anyhow!("Invalid aspect ratio width"))?;
68        let height: u32 = height_str.parse().map_err(|_| anyhow!("Invalid aspect ratio height"))?;
69        if width == 0 || height == 0 {
70            return Err(anyhow!("Aspect ratio dimensions must be non-zero: {ratio:?}"));
71        }
72        Ok(Self { width, height })
73    }
74}
75
76/// Configuration options for the `framebuffer` module.
77#[derive(FromArgs, Debug, Default)]
78struct FramebufferArgs {
79    /// display aspect ratio formatted as "width:height" (e.g. "9:16")
80    #[argh(option)]
81    aspect_ratio: Option<AspectRatio>,
82}
83
84fn parse_args(args: &[&str]) -> Result<Option<AspectRatio>, anyhow::Error> {
85    let parsed =
86        FramebufferArgs::from_args(&["framebuffer"], args).map_err(|e| anyhow!("{}", e.output))?;
87    Ok(parsed.aspect_ratio)
88}
89
90/// Validates `framebuffer` module configuration `args`.
91pub fn validate_args(args: &[&str]) -> Result<(), anyhow::Error> {
92    parse_args(args).map(|_| ())
93}
94
95pub struct Framebuffer {
96    server: Option<Arc<FramebufferServer>>,
97    memory: LockDepMutex<Option<Arc<MemoryObject>>, FramebufferMemoryLock>,
98    pub info: LockDepRwLock<fb_var_screeninfo, FramebufferInfoLock>,
99    pub view_identity:
100        LockDepMutex<Option<fuiviews::ViewIdentityOnCreation>, FramebufferViewIdentityLock>,
101    pub view_bound_protocols:
102        LockDepMutex<Option<fuicomposition::ViewBoundProtocols>, FramebufferViewBoundProtocolsLock>,
103    pub initial_view_id_annotation: String,
104}
105
106impl Framebuffer {
107    /// Returns the current framebuffer if one was created for this kernel.
108    pub fn get(kernel: &Kernel) -> Result<Arc<Self>, Errno> {
109        kernel.expando.get_or_try_init(|| error!(EINVAL))
110    }
111
112    /// Initializes the framebuffer device. Should only be called once per kernel.
113    pub fn device_init(
114        kernel: &Kernel,
115        args: &[&str],
116        enable_visual_debugging: bool,
117        initial_view_id_annotation: String,
118    ) -> Result<Arc<Framebuffer>, anyhow::Error> {
119        let aspect_ratio = parse_args(args)?;
120        let registry = &kernel.device_registry;
121
122        let framebuffer = kernel.expando.get_or_try_init(|| {
123            Framebuffer::new(aspect_ratio, enable_visual_debugging, initial_view_id_annotation)
124        })?;
125
126        let graphics_class = registry.objects.graphics_class();
127        registry.register_device(
128            kernel,
129            "fb0".into(),
130            DeviceMetadata::new("fb0".into(), DeviceId::FB0, DeviceMode::Char),
131            graphics_class,
132            FramebufferDevice { framebuffer: framebuffer.clone() },
133        )?;
134
135        Ok(framebuffer)
136    }
137
138    /// Creates a new `Framebuffer` fit to the screen, while maintaining the provided aspect ratio.
139    ///
140    /// If the `aspect_ratio` is `None`, the framebuffer will be scaled to the display.
141    fn new(
142        aspect_ratio: Option<AspectRatio>,
143        enable_visual_debugging: bool,
144        initial_view_id_annotation: String,
145    ) -> Result<Self, Errno> {
146        let mut info = fb_var_screeninfo::default();
147
148        let display_size = get_display_size().unwrap_or(fmath::SizeU { width: 700, height: 1200 });
149
150        // If the container has a specific aspect ratio set, use that to fit the framebuffer
151        // inside of the display.
152        let (feature_width, feature_height) = aspect_ratio
153            .map(|ar| (ar.width, ar.height))
154            .unwrap_or((display_size.width, display_size.height));
155
156        // Scale to framebuffer to fit the display, while maintaining the expected aspect ratio.
157        let ratio =
158            std::cmp::min(display_size.width / feature_width, display_size.height / feature_height);
159        let (width, height) = (feature_width * ratio, feature_height * ratio);
160
161        info.xres = width;
162        info.yres = height;
163        info.xres_virtual = info.xres;
164        info.yres_virtual = info.yres;
165        info.bits_per_pixel = 32;
166        info.red = fb_bitfield { offset: 0, length: 8, msb_right: 0 };
167        info.green = fb_bitfield { offset: 8, length: 8, msb_right: 0 };
168        info.blue = fb_bitfield { offset: 16, length: 8, msb_right: 0 };
169        info.transp = fb_bitfield { offset: 24, length: 8, msb_right: 0 };
170
171        if let Ok((server, memory)) = FramebufferServer::new(width, height) {
172            let server = Arc::new(server);
173            let memory_len = memory.info()?.size_bytes as u32;
174
175            // Fill the buffer with black pixels as a placeholder, if visual debug is off.
176            // Fill the buffer with purple, if visual debug is on.
177            let background = if enable_visual_debugging {
178                [0xff, 0x00, 0xff, 0xff].repeat((memory_len / 4) as usize)
179            } else {
180                vec![0x00; memory_len as usize]
181            };
182
183            if let Err(err) = memory.write(&background, 0) {
184                log_warn!("could not write initial framebuffer: {:?}", err);
185            }
186
187            Ok(Self {
188                server: Some(server),
189                memory: Some(memory).into(),
190                info: info.into(),
191                view_identity: Default::default(),
192                view_bound_protocols: Default::default(),
193                initial_view_id_annotation,
194            })
195        } else {
196            Ok(Self {
197                server: None,
198                memory: Default::default(),
199                info: info.into(),
200                view_identity: Default::default(),
201                view_bound_protocols: Default::default(),
202                initial_view_id_annotation,
203            })
204        }
205    }
206
207    /// Starts presenting a view based on this framebuffer.
208    ///
209    /// # Parameters
210    /// * `incoming_dir`: the incoming service directory under which the
211    ///   `fuchsia.element.GraphicalPresenter` protocol can be retrieved.
212    pub fn start_server(&self, kernel: &Kernel, incoming_dir: Option<fio::DirectoryProxy>) {
213        if let Some(server) = &self.server {
214            let view_bound_protocols = self.view_bound_protocols.lock().take().unwrap();
215            let view_identity = self.view_identity.lock().take().unwrap();
216            log_info!("Presenting view using GraphicalPresenter");
217            start_presentation_loop(
218                kernel,
219                server.clone(),
220                view_bound_protocols,
221                view_identity,
222                incoming_dir,
223                self.initial_view_id_annotation.clone(),
224            );
225        }
226    }
227
228    /// Starts presenting a child view instead of the framebuffer.
229    ///
230    /// # Parameters
231    /// * `viewport_token`: handles to the child view
232    pub fn present_view(&self, viewport_token: fuiviews::ViewportCreationToken) {
233        if let Some(server) = &self.server {
234            init_viewport_scene(server.clone(), viewport_token);
235
236            // Release the memory associated with the framebuffer.
237            let mut memory = self.memory.lock();
238            if let Some(memory_ref) = memory.as_ref() {
239                let bytes = memory_ref.get_size();
240                let refs = Arc::strong_count(memory_ref);
241                *memory = None;
242                log_info!("Released framebuffer memory ({} bytes, {} refs)", bytes, refs);
243            }
244        }
245    }
246
247    /// Returns the framebuffer's memory.
248    fn get_memory(&self) -> Result<Arc<MemoryObject>, Errno> {
249        self.memory.lock().clone().ok_or_else(|| errno!(EIO))
250    }
251
252    /// Returns the logical size of the framebuffer's memory.
253    fn memory_len(&self) -> usize {
254        self.memory
255            .lock()
256            .as_ref()
257            .map_or(0, |memory| memory.info().map_or(0, |info| info.size_bytes)) as usize
258    }
259
260    /// Returns the allocated size of the framebuffer's memory.
261    fn memory_size(&self) -> usize {
262        self.memory.lock().as_ref().map_or(0, |memory| memory.get_size()) as usize
263    }
264}
265
266#[derive(Clone)]
267struct FramebufferDevice {
268    framebuffer: Arc<Framebuffer>,
269}
270
271type FbFixScreeninfoPtr =
272    MultiArchUserRef<uapi::fb_fix_screeninfo, uapi::arch32::fb_fix_screeninfo>;
273type FbVarScreeninfoPtr =
274    MultiArchUserRef<uapi::fb_var_screeninfo, uapi::arch32::fb_var_screeninfo>;
275
276fn set_display_power(mode: fuidisplay::PowerMode) -> Result<(), Errno> {
277    let singleton_display_power =
278        connect_to_protocol_sync::<fuidisplay::DisplayPowerMarker>().map_err(|_| errno!(ENOENT))?;
279    singleton_display_power
280        .set_power_mode(mode, zx::MonotonicInstant::INFINITE)
281        .map_err(|_| errno!(EIO))?
282        .map_err(|_| errno!(EINVAL))?;
283    Ok(())
284}
285
286impl DeviceOps for FramebufferDevice {
287    fn open(
288        &self,
289        _current_task: &CurrentTask,
290        dev: DeviceId,
291        node: &NamespaceNode,
292        _flags: OpenFlags,
293    ) -> Result<Box<dyn FileOps>, Errno> {
294        if dev.minor() != 0 {
295            return error!(ENODEV);
296        }
297        node.entry.node.update_info(|info| {
298            info.size = self.framebuffer.memory_len();
299            info.blocks = self.framebuffer.memory_size() / info.blksize;
300            Ok(())
301        })?;
302        Ok(Box::new(Arc::clone(&self.framebuffer)))
303    }
304}
305/// `Framebuffer` doesn't implement the `close` method.
306impl CloseFreeSafe for Framebuffer {}
307impl FileOps for Framebuffer {
308    fileops_impl_memory!(self, &self.get_memory()?);
309    fileops_impl_noop_sync!();
310
311    fn ioctl(
312        &self,
313        _file: &FileObject,
314        current_task: &CurrentTask,
315        request: u32,
316        arg: SyscallArg,
317    ) -> Result<SyscallResult, Errno> {
318        let user_addr = UserAddress::from(arg);
319        match request {
320            FBIOGET_FSCREENINFO => {
321                let info = self.info.read();
322                let finfo = fb_fix_screeninfo {
323                    id: zerocopy::FromBytes::read_from_bytes(&b"Starnix\0\0\0\0\0\0\0\0\0"[..])
324                        .unwrap(),
325                    smem_start: 0,
326                    smem_len: self.memory_len() as u32,
327                    type_: FB_TYPE_PACKED_PIXELS,
328                    visual: FB_VISUAL_TRUECOLOR,
329                    line_length: info.bits_per_pixel / 8 * info.xres,
330                    ..fb_fix_screeninfo::default()
331                };
332                let user_ref = FbFixScreeninfoPtr::new(current_task, user_addr);
333                current_task.write_multi_arch_object(user_ref, finfo)?;
334                Ok(SUCCESS)
335            }
336
337            FBIOGET_VSCREENINFO => {
338                let info = self.info.read();
339                let user_ref = FbVarScreeninfoPtr::new(current_task, user_addr);
340                current_task.write_multi_arch_object(user_ref, *info)?;
341                Ok(SUCCESS)
342            }
343
344            FBIOPUT_VSCREENINFO => {
345                let user_ref = FbVarScreeninfoPtr::new(current_task, user_addr);
346                let new_info: fb_var_screeninfo = current_task.read_multi_arch_object(user_ref)?;
347                let old_info = self.info.read();
348                // We don't yet support actually changing anything
349                if new_info.as_bytes() != old_info.as_bytes() {
350                    return error!(EINVAL);
351                }
352                Ok(SUCCESS)
353            }
354
355            FBIOBLANK => {
356                let arg = u32::from(arg);
357                match arg {
358                    FB_BLANK_POWERDOWN => {
359                        set_display_power(fuidisplay::PowerMode::Off)?;
360                        Ok(SUCCESS)
361                    }
362                    FB_BLANK_UNBLANK => {
363                        set_display_power(fuidisplay::PowerMode::On)?;
364                        Ok(SUCCESS)
365                    }
366                    _ => {
367                        track_stub!(TODO("https://fxbug.dev/475633434"), "FBIOBLANK", arg);
368                        error!(EINVAL)
369                    }
370                }
371            }
372
373            _ => {
374                track_stub!(TODO("https://fxbug.dev/475633434"), "fb ioctl", request);
375                error!(EINVAL)
376            }
377        }
378    }
379}