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, bail};
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_math as fmath;
13use fidl_fuchsia_ui_composition as fuicomposition;
14use fidl_fuchsia_ui_display_singleton as fuidisplay;
15use fidl_fuchsia_ui_views as fuiviews;
16use fuchsia_component::client::connect_to_protocol_sync;
17use starnix_core::device::kobject::DeviceMetadata;
18use starnix_core::device::{DeviceMode, DeviceOps, DeviceRegistry};
19use starnix_core::fs::sysfs::build_device_directory;
20use starnix_core::mm::MemoryAccessorExt;
21use starnix_core::mm::memory::MemoryObject;
22use starnix_core::task::{ArgNameAndValue, CurrentTask, Kernel};
23use starnix_core::vfs::pseudo::stub_bytes_file::StubBytesFile;
24use starnix_core::vfs::pseudo::stub_empty_file::StubEmptyFile;
25use starnix_core::vfs::{
26    CloseFreeSafe, FileObject, FileOps, NamespaceNode, fileops_impl_memory, fileops_impl_noop_sync,
27};
28use starnix_logging::{bug_ref, log_info, log_warn, track_stub};
29use starnix_sync::{
30    FramebufferInfoLock, FramebufferMemoryLock, FramebufferViewBoundProtocolsLock,
31    FramebufferViewIdentityLock, LockDepMutex, LockDepRwLock,
32};
33use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
34use starnix_uapi::device_id::DeviceId;
35use starnix_uapi::errors::Errno;
36use starnix_uapi::file_mode::mode;
37use starnix_uapi::open_flags::OpenFlags;
38use starnix_uapi::user_address::{MultiArchUserRef, UserAddress};
39use starnix_uapi::{
40    FB_BLANK_POWERDOWN, FB_BLANK_UNBLANK, FB_TYPE_PACKED_PIXELS, FB_VISUAL_TRUECOLOR, FBIOBLANK,
41    FBIOGET_FSCREENINFO, FBIOGET_VSCREENINFO, FBIOPUT_VSCREENINFO, errno, error, fb_bitfield,
42    fb_fix_screeninfo, fb_var_screeninfo, uapi,
43};
44use std::sync::Arc;
45use zerocopy::IntoBytes;
46
47fn get_display_size() -> Result<fmath::SizeU, Errno> {
48    let singleton_display_info =
49        connect_to_protocol_sync::<fuidisplay::InfoMarker>().map_err(|_| errno!(ENOENT))?;
50    let metrics = singleton_display_info
51        .get_metrics(zx::MonotonicInstant::INFINITE)
52        .map_err(|_| errno!(EINVAL))?;
53    let extent_in_px =
54        metrics.extent_in_px.ok_or("Failed to get extent_in_px").map_err(|_| errno!(EINVAL))?;
55    Ok(extent_in_px)
56}
57
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
59struct AspectRatio {
60    width: u32,
61    height: u32,
62}
63
64impl std::str::FromStr for AspectRatio {
65    type Err = anyhow::Error;
66
67    fn from_str(ratio: &str) -> Result<Self, Self::Err> {
68        let (width_str, height_str) =
69            ratio.split_once(':').ok_or_else(|| anyhow!("Invalid aspect_ratio: {ratio:?}"))?;
70        let width: u32 = width_str.parse().map_err(|_| anyhow!("Invalid aspect ratio width"))?;
71        let height: u32 = height_str.parse().map_err(|_| anyhow!("Invalid aspect ratio height"))?;
72        if width == 0 || height == 0 {
73            return Err(anyhow!("Aspect ratio dimensions must be non-zero: {ratio:?}"));
74        }
75        Ok(Self { width, height })
76    }
77}
78
79fn parse_args(args: &[&str]) -> Result<Option<AspectRatio>, anyhow::Error> {
80    let mut aspect_ratio: Option<AspectRatio> = None;
81    for &arg in args {
82        match ArgNameAndValue::from(arg) {
83            ArgNameAndValue { name: "aspect_ratio", value: Some(value) } => {
84                aspect_ratio = Some(value.parse()?);
85            }
86            _ => bail!("invalid framebuffer argument: {arg}"),
87        }
88    }
89    Ok(aspect_ratio)
90}
91
92/// Validates `framebuffer` module configuration `args`.
93pub fn validate_args(args: &[&str]) -> Result<(), anyhow::Error> {
94    parse_args(args).map(|_| ())
95}
96
97pub struct Framebuffer {
98    server: Option<Arc<FramebufferServer>>,
99    memory: LockDepMutex<Option<Arc<MemoryObject>>, FramebufferMemoryLock>,
100    pub info: LockDepRwLock<fb_var_screeninfo, FramebufferInfoLock>,
101    pub view_identity:
102        LockDepMutex<Option<fuiviews::ViewIdentityOnCreation>, FramebufferViewIdentityLock>,
103    pub view_bound_protocols:
104        LockDepMutex<Option<fuicomposition::ViewBoundProtocols>, FramebufferViewBoundProtocolsLock>,
105    pub initial_view_id_annotation: String,
106}
107
108impl Framebuffer {
109    /// Returns the current framebuffer if one was created for this kernel.
110    pub fn get(kernel: &Kernel) -> Result<Arc<Self>, Errno> {
111        kernel.expando.get_or_try_init(|| error!(EINVAL))
112    }
113
114    /// Initializes the framebuffer device. Should only be called once per kernel.
115    pub fn device_init(
116        kernel: &Kernel,
117        args: &[&str],
118        enable_visual_debugging: bool,
119        initial_view_id_annotation: String,
120    ) -> Result<Arc<Framebuffer>, anyhow::Error> {
121        let aspect_ratio = parse_args(args)?;
122        let registry = &kernel.device_registry;
123
124        let framebuffer = kernel.expando.get_or_try_init(|| {
125            Framebuffer::new(aspect_ratio, enable_visual_debugging, initial_view_id_annotation)
126        })?;
127
128        let graphics_class = registry.objects.graphics_class();
129        registry.add_device(
130            kernel,
131            "fb0".into(),
132            DeviceMetadata::new("fb0".into(), DeviceId::FB0, DeviceMode::Char),
133            graphics_class,
134            FramebufferDevice { framebuffer: framebuffer.clone() },
135        );
136
137        register_display_stub_devices(registry);
138
139        Ok(framebuffer)
140    }
141
142    /// Creates a new `Framebuffer` fit to the screen, while maintaining the provided aspect ratio.
143    ///
144    /// If the `aspect_ratio` is `None`, the framebuffer will be scaled to the display.
145    fn new(
146        aspect_ratio: Option<AspectRatio>,
147        enable_visual_debugging: bool,
148        initial_view_id_annotation: String,
149    ) -> Result<Self, Errno> {
150        let mut info = fb_var_screeninfo::default();
151
152        let display_size = get_display_size().unwrap_or(fmath::SizeU { width: 700, height: 1200 });
153
154        // If the container has a specific aspect ratio set, use that to fit the framebuffer
155        // inside of the display.
156        let (feature_width, feature_height) = aspect_ratio
157            .map(|ar| (ar.width, ar.height))
158            .unwrap_or((display_size.width, display_size.height));
159
160        // Scale to framebuffer to fit the display, while maintaining the expected aspect ratio.
161        let ratio =
162            std::cmp::min(display_size.width / feature_width, display_size.height / feature_height);
163        let (width, height) = (feature_width * ratio, feature_height * ratio);
164
165        info.xres = width;
166        info.yres = height;
167        info.xres_virtual = info.xres;
168        info.yres_virtual = info.yres;
169        info.bits_per_pixel = 32;
170        info.red = fb_bitfield { offset: 0, length: 8, msb_right: 0 };
171        info.green = fb_bitfield { offset: 8, length: 8, msb_right: 0 };
172        info.blue = fb_bitfield { offset: 16, length: 8, msb_right: 0 };
173        info.transp = fb_bitfield { offset: 24, length: 8, msb_right: 0 };
174
175        if let Ok((server, memory)) = FramebufferServer::new(width, height) {
176            let server = Arc::new(server);
177            let memory_len = memory.info()?.size_bytes as u32;
178
179            // Fill the buffer with black pixels as a placeholder, if visual debug is off.
180            // Fill the buffer with purple, if visual debug is on.
181            let background = if enable_visual_debugging {
182                [0xff, 0x00, 0xff, 0xff].repeat((memory_len / 4) as usize)
183            } else {
184                vec![0x00; memory_len as usize]
185            };
186
187            if let Err(err) = memory.write(&background, 0) {
188                log_warn!("could not write initial framebuffer: {:?}", err);
189            }
190
191            Ok(Self {
192                server: Some(server),
193                memory: Some(memory).into(),
194                info: info.into(),
195                view_identity: Default::default(),
196                view_bound_protocols: Default::default(),
197                initial_view_id_annotation,
198            })
199        } else {
200            Ok(Self {
201                server: None,
202                memory: Default::default(),
203                info: info.into(),
204                view_identity: Default::default(),
205                view_bound_protocols: Default::default(),
206                initial_view_id_annotation,
207            })
208        }
209    }
210
211    /// Starts presenting a view based on this framebuffer.
212    ///
213    /// # Parameters
214    /// * `incoming_dir`: the incoming service directory under which the
215    ///   `fuchsia.element.GraphicalPresenter` protocol can be retrieved.
216    pub fn start_server(&self, kernel: &Kernel, incoming_dir: Option<fio::DirectoryProxy>) {
217        if let Some(server) = &self.server {
218            let view_bound_protocols = self.view_bound_protocols.lock().take().unwrap();
219            let view_identity = self.view_identity.lock().take().unwrap();
220            log_info!("Presenting view using GraphicalPresenter");
221            start_presentation_loop(
222                kernel,
223                server.clone(),
224                view_bound_protocols,
225                view_identity,
226                incoming_dir,
227                self.initial_view_id_annotation.clone(),
228            );
229        }
230    }
231
232    /// Starts presenting a child view instead of the framebuffer.
233    ///
234    /// # Parameters
235    /// * `viewport_token`: handles to the child view
236    pub fn present_view(&self, viewport_token: fuiviews::ViewportCreationToken) {
237        if let Some(server) = &self.server {
238            init_viewport_scene(server.clone(), viewport_token);
239
240            // Release the memory associated with the framebuffer.
241            let mut memory = self.memory.lock();
242            if let Some(memory_ref) = memory.as_ref() {
243                let bytes = memory_ref.get_size();
244                let refs = Arc::strong_count(memory_ref);
245                *memory = None;
246                log_info!("Released framebuffer memory ({} bytes, {} refs)", bytes, refs);
247            }
248        }
249    }
250
251    /// Returns the framebuffer's memory.
252    fn get_memory(&self) -> Result<Arc<MemoryObject>, Errno> {
253        self.memory.lock().clone().ok_or_else(|| errno!(EIO))
254    }
255
256    /// Returns the logical size of the framebuffer's memory.
257    fn memory_len(&self) -> usize {
258        self.memory
259            .lock()
260            .as_ref()
261            .map_or(0, |memory| memory.info().map_or(0, |info| info.size_bytes)) as usize
262    }
263
264    /// Returns the allocated size of the framebuffer's memory.
265    fn memory_size(&self) -> usize {
266        self.memory.lock().as_ref().map_or(0, |memory| memory.get_size()) as usize
267    }
268}
269
270#[derive(Clone)]
271struct FramebufferDevice {
272    framebuffer: Arc<Framebuffer>,
273}
274
275type FbFixScreeninfoPtr =
276    MultiArchUserRef<uapi::fb_fix_screeninfo, uapi::arch32::fb_fix_screeninfo>;
277type FbVarScreeninfoPtr =
278    MultiArchUserRef<uapi::fb_var_screeninfo, uapi::arch32::fb_var_screeninfo>;
279
280fn set_display_power(mode: fuidisplay::PowerMode) -> Result<(), Errno> {
281    let singleton_display_power =
282        connect_to_protocol_sync::<fuidisplay::DisplayPowerMarker>().map_err(|_| errno!(ENOENT))?;
283    singleton_display_power
284        .set_power_mode(mode, zx::MonotonicInstant::INFINITE)
285        .map_err(|_| errno!(EIO))?
286        .map_err(|_| errno!(EINVAL))?;
287    Ok(())
288}
289
290impl DeviceOps for FramebufferDevice {
291    fn open(
292        &self,
293        _current_task: &CurrentTask,
294        dev: DeviceId,
295        node: &NamespaceNode,
296        _flags: OpenFlags,
297    ) -> Result<Box<dyn FileOps>, Errno> {
298        if dev.minor() != 0 {
299            return error!(ENODEV);
300        }
301        node.entry.node.update_info(|info| {
302            info.size = self.framebuffer.memory_len();
303            info.blocks = self.framebuffer.memory_size() / info.blksize;
304            Ok(())
305        })?;
306        Ok(Box::new(Arc::clone(&self.framebuffer)))
307    }
308}
309/// `Framebuffer` doesn't implement the `close` method.
310impl CloseFreeSafe for Framebuffer {}
311impl FileOps for Framebuffer {
312    fileops_impl_memory!(self, &self.get_memory()?);
313    fileops_impl_noop_sync!();
314
315    fn ioctl(
316        &self,
317        _file: &FileObject,
318        current_task: &CurrentTask,
319        request: u32,
320        arg: SyscallArg,
321    ) -> Result<SyscallResult, Errno> {
322        let user_addr = UserAddress::from(arg);
323        match request {
324            FBIOGET_FSCREENINFO => {
325                let info = self.info.read();
326                let finfo = fb_fix_screeninfo {
327                    id: zerocopy::FromBytes::read_from_bytes(&b"Starnix\0\0\0\0\0\0\0\0\0"[..])
328                        .unwrap(),
329                    smem_start: 0,
330                    smem_len: self.memory_len() as u32,
331                    type_: FB_TYPE_PACKED_PIXELS,
332                    visual: FB_VISUAL_TRUECOLOR,
333                    line_length: info.bits_per_pixel / 8 * info.xres,
334                    ..fb_fix_screeninfo::default()
335                };
336                let user_ref = FbFixScreeninfoPtr::new(current_task, user_addr);
337                current_task.write_multi_arch_object(user_ref, finfo)?;
338                Ok(SUCCESS)
339            }
340
341            FBIOGET_VSCREENINFO => {
342                let info = self.info.read();
343                let user_ref = FbVarScreeninfoPtr::new(current_task, user_addr);
344                current_task.write_multi_arch_object(user_ref, *info)?;
345                Ok(SUCCESS)
346            }
347
348            FBIOPUT_VSCREENINFO => {
349                let user_ref = FbVarScreeninfoPtr::new(current_task, user_addr);
350                let new_info: fb_var_screeninfo = current_task.read_multi_arch_object(user_ref)?;
351                let old_info = self.info.read();
352                // We don't yet support actually changing anything
353                if new_info.as_bytes() != old_info.as_bytes() {
354                    return error!(EINVAL);
355                }
356                Ok(SUCCESS)
357            }
358
359            FBIOBLANK => {
360                let arg = u32::from(arg);
361                match arg {
362                    FB_BLANK_POWERDOWN => {
363                        set_display_power(fuidisplay::PowerMode::Off)?;
364                        Ok(SUCCESS)
365                    }
366                    FB_BLANK_UNBLANK => {
367                        set_display_power(fuidisplay::PowerMode::On)?;
368                        Ok(SUCCESS)
369                    }
370                    _ => {
371                        track_stub!(TODO("https://fxbug.dev/475633434"), "FBIOBLANK", arg);
372                        error!(EINVAL)
373                    }
374                }
375            }
376
377            _ => {
378                track_stub!(TODO("https://fxbug.dev/475633434"), "fb ioctl", request);
379                error!(EINVAL)
380            }
381        }
382    }
383}
384
385/// Registers stub DRM display controller, connector, and panel backlight devices in sysfs.
386fn register_display_stub_devices(registry: &DeviceRegistry) {
387    let objects = &registry.objects;
388    let platform_bus = objects.platform_bus();
389    let soc = objects.soc_device();
390
391    // TODO(https://fxbug.dev/452096300): Stub Qualcomm MDSS display controller and DRM connector.
392    let mdss_mdp = registry.add_bus_device(
393        "5e00000.qcom,mdss_mdp".into(),
394        Some(soc),
395        platform_bus,
396        build_device_directory,
397    );
398    let drm_class = objects.get_or_create_class("drm".into());
399    let card0 = registry.add_numberless_device(
400        "card0".into(),
401        Some(mdss_mdp.clone()),
402        drm_class.clone(),
403        build_device_directory,
404    );
405    registry.add_numberless_device(
406        "sde-conn-0-DSI-1".into(),
407        Some(card0.clone()),
408        drm_class.clone(),
409        |device, dir| {
410            build_device_directory(device, dir);
411            dir.entry(
412                "display_power_state",
413                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
414                mode!(IFREG, 0o644),
415            );
416            dir.entry(
417                "panel_power_state",
418                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
419                mode!(IFREG, 0o644),
420            );
421        },
422    );
423    registry.add_numberless_device("card0-DSI-1".into(), Some(card0), drm_class, |device, dir| {
424        build_device_directory(device, dir);
425        dir.subdir("panel", 0o755, |dir| {
426            dir.entry(
427                "panel_name",
428                StubBytesFile::new_node_with_data(
429                    bug_ref!("https://fxbug.dev/560212202"),
430                    b"Placeholder",
431                ),
432                mode!(IFREG, 0o444),
433            );
434        });
435    });
436
437    // TODO(https://fxbug.dev/452096300): Stub panel backlight device parented by the display
438    // controller.
439    registry.add_numberless_device(
440        "panel0-backlight".into(),
441        Some(mdss_mdp),
442        objects.get_or_create_class("backlight".into()),
443        |device, dir| {
444            build_device_directory(device, dir);
445            dir.entry(
446                "brightness",
447                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
448                mode!(IFREG, 0o444),
449            );
450        },
451    );
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use starnix_core::testing::{anon_test_file, spawn_kernel_and_run};
458    use starnix_core::vfs::VecOutputBuffer;
459
460    #[::fuchsia::test]
461    async fn framebuffer_device_init_registers_display_and_backlight_devices() {
462        spawn_kernel_and_run(async |current_task| {
463            let kernel = current_task.kernel();
464            Framebuffer::device_init(
465                kernel,
466                /* args = */ &[],
467                /* enable_visual_debugging = */ false,
468                /* initial_view_id_annotation = */ String::new(),
469            )
470            .expect("Framebuffer::device_init should succeed");
471
472            let root = &kernel.device_registry.objects.root;
473            assert!(root.lookup("class/graphics/fb0".into()).is_some());
474            assert!(root.lookup("class/drm/card0".into()).is_some());
475            assert!(root.lookup("class/drm/sde-conn-0-DSI-1".into()).is_some());
476            assert!(
477                root.lookup(
478                    "devices/platform/soc/5e00000.qcom,mdss_mdp/drm/card0/sde-conn-0-DSI-1/display_power_state"
479                        .into(),
480                )
481                .is_some()
482            );
483            assert!(root.lookup("class/drm/card0-DSI-1".into()).is_some());
484
485            let panel_name = root
486                .lookup(
487                    "devices/platform/soc/5e00000.qcom,mdss_mdp/drm/card0/card0-DSI-1/panel/panel_name"
488                        .into(),
489                )
490                .expect("panel_name node should exist");
491            assert_eq!(panel_name.info().mode, mode!(IFREG, 0o444));
492            let file_ops = panel_name.create_file_ops(current_task, OpenFlags::RDONLY).unwrap();
493            let file = anon_test_file(current_task, file_ops, OpenFlags::RDONLY);
494            let mut buf = VecOutputBuffer::new(16);
495            let n = file.read(current_task, &mut buf).unwrap();
496            assert_eq!(&Vec::from(buf)[..n], b"Placeholder");
497
498            assert!(root.lookup("class/backlight/panel0-backlight".into()).is_some());
499            assert!(
500                root.lookup(
501                    "devices/platform/soc/5e00000.qcom,mdss_mdp/backlight/panel0-backlight/brightness"
502                        .into(),
503                )
504                .is_some()
505            );
506        })
507        .await;
508    }
509}