1#![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
92pub 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 pub fn get(kernel: &Kernel) -> Result<Arc<Self>, Errno> {
111 kernel.expando.get_or_try_init(|| error!(EINVAL))
112 }
113
114 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 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 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 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 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 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 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 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 fn get_memory(&self) -> Result<Arc<MemoryObject>, Errno> {
253 self.memory.lock().clone().ok_or_else(|| errno!(EIO))
254 }
255
256 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 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}
309impl 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 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
385fn register_display_stub_devices(registry: &DeviceRegistry) {
387 let objects = ®istry.objects;
388 let platform_bus = objects.platform_bus();
389 let soc = objects.soc_device();
390
391 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 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 &[],
467 false,
468 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}