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