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