diff --git a/.gitignore b/.gitignore index a5690b4f525d2edf429beda0f9338a2481aec2cb..dbc5ad7e8c02e2a93edd7218ec85836f86a60c99 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ last-update tools/cipd.gni .idea/ *.iml +**/Cargo.lock **/Cargo.toml diff --git a/bin/ui/recovery_ui/BUILD.gn b/bin/ui/recovery_ui/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..3f22415cca5220c9c71800b8a7662d26c349e236 --- /dev/null +++ b/bin/ui/recovery_ui/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright 2017 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/rust/rustc_binary.gni") +import("//build/package.gni") + +rustc_binary("bin") { + name = "recovery_ui" + + with_lto = "fat" + + deps = [ + "//garnet/public/rust/crates/fuchsia-zircon", + "//garnet/public/rust/crates/fuchsia-async", + "//garnet/public/rust/crates/fuchsia-framebuffer", + ] +} + +package("recovery_ui") { + deps = [ + ":bin", + ] + + binary = "rust_crates/recovery_ui" + +} diff --git a/bin/ui/recovery_ui/src/main.rs b/bin/ui/recovery_ui/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..1502f4bb0298c766414d68dbccf048dae319801f --- /dev/null +++ b/bin/ui/recovery_ui/src/main.rs @@ -0,0 +1,49 @@ +extern crate fuchsia_async as async; +extern crate fuchsia_framebuffer; +extern crate fuchsia_zircon; + +use fuchsia_framebuffer::{FrameBuffer, PixelFormat}; +use std::io::{self, Read}; +use std::{thread, time}; + +/// Convenience function that can be called from main and causes the Fuchsia process being +/// run over ssh to be terminated when the user hits control-C. +fn wait_for_close() { + thread::spawn(move || loop { + let mut input = [0; 1]; + if io::stdin().read_exact(&mut input).is_err() { + std::process::exit(0); + } + }); +} + +fn main() { + println!("Recovery UI"); + wait_for_close(); + + let mut executor = async::Executor::new().unwrap(); + + let fb = FrameBuffer::new(None, &mut executor).unwrap(); + let config = fb.get_config(); + + let values565 = &[31, 248]; + let values8888 = &[255, 0, 255, 255]; + + let pink_frame = fb.new_frame(&mut executor).unwrap(); + + for y in 0..config.height { + for x in 0..config.width { + match config.format { + PixelFormat::RgbX888 => pink_frame.write_pixel(x, y, values8888), + PixelFormat::Argb8888 => pink_frame.write_pixel(x, y, values8888), + PixelFormat::Rgb565 => pink_frame.write_pixel(x, y, values565), + _ => {} + } + } + } + + pink_frame.present(&fb).unwrap(); + loop { + thread::sleep(time::Duration::from_millis(25000)); + } +} diff --git a/packages/prod/all b/packages/prod/all index d03f12d164bdb49e238b3946db8d673282f7c648..4ab5b8b34c54bca3764fd2d7451b0a7a146a1a58 100644 --- a/packages/prod/all +++ b/packages/prod/all @@ -50,6 +50,7 @@ "garnet/packages/prod/power_manager", "garnet/packages/prod/ralink", "garnet/packages/prod/recovery_netstack", + "garnet/packages/prod/recovery_ui", "garnet/packages/prod/root_ssl_certificates", "garnet/packages/prod/run", "garnet/packages/prod/runtime", diff --git a/packages/prod/recovery_ui b/packages/prod/recovery_ui new file mode 100644 index 0000000000000000000000000000000000000000..a9d3d9cd8a6c9306fb1f5ffe3b09b394ae2cfe0f --- /dev/null +++ b/packages/prod/recovery_ui @@ -0,0 +1,5 @@ +{ + "packages": { + "recovery_ui": "//garnet/bin/ui/recovery_ui" + } +} diff --git a/public/rust/crates/BUILD.gn b/public/rust/crates/BUILD.gn index 5a17a057e19fb1c63e1cd7789ca44705baf2b0e5..5ea52acdf6e405d1ed2bf672bdb1531f323d8e19 100644 --- a/public/rust/crates/BUILD.gn +++ b/public/rust/crates/BUILD.gn @@ -12,6 +12,7 @@ package("rust-crates-tests") { "fdio", "fuchsia-async", "fuchsia-syslog", + "fuchsia-framebuffer", "fuchsia-trace", "fuchsia-zircon", "shared-buffer", diff --git a/public/rust/crates/fdio/src/fdio_sys.rs b/public/rust/crates/fdio/src/fdio_sys.rs index 7ec9a67d77cf69c680892a7812e2d39a2e22cc18..33a751541f2755394a039624ba603b6f4fa93c1e 100644 --- a/public/rust/crates/fdio/src/fdio_sys.rs +++ b/public/rust/crates/fdio/src/fdio_sys.rs @@ -166,6 +166,7 @@ pub const IOCTL_FAMILY_CAMERA: raw::c_int = 50; pub const IOCTL_FAMILY_BT_HOST: raw::c_int = 51; pub const IOCTL_FAMILY_WLANPHY: raw::c_int = 52; pub const IOCTL_FAMILY_WLANTAP: raw::c_int = 0x36; +pub const IOCTL_FAMILY_DISPLAY_CONTROLLER: raw::c_int = 0x37; pub const ZXRIO_SOCKET_DIR_NONE: &'static [u8; 5usize] = b"none\x00"; pub const ZXRIO_SOCKET_DIR_SOCKET: &'static [u8; 7usize] = b"socket\x00"; pub const ZXRIO_SOCKET_DIR_ACCEPT: &'static [u8; 7usize] = b"accept\x00"; diff --git a/public/rust/crates/fuchsia-framebuffer/BUILD.gn b/public/rust/crates/fuchsia-framebuffer/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..bf0d3006ae604b909369c0cb400a0802f38d4837 --- /dev/null +++ b/public/rust/crates/fuchsia-framebuffer/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright 2017 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/rust/rustc_library.gni") + +rustc_library("fuchsia-framebuffer") { + name = "fuchsia_framebuffer" + version = "0.1.0" + deps = [ + "//garnet/public/rust/crates/fuchsia-async", + "//garnet/public/rust/crates/fuchsia-zircon", + "//garnet/public/rust/crates/fdio", + "//garnet/public/rust/crates/shared-buffer", + "//third_party/rust-crates/rustc_deps:failure", + "//third_party/rust-crates/rustc_deps:futures", + "//zircon/public/fidl/display:display-rustc", + ] +} diff --git a/public/rust/crates/fuchsia-framebuffer/src/lib.rs b/public/rust/crates/fuchsia-framebuffer/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..f8659219a76440de80dc34ca9d2db87d2750b95f --- /dev/null +++ b/public/rust/crates/fuchsia-framebuffer/src/lib.rs @@ -0,0 +1,399 @@ +#![allow(dead_code)] +#[macro_use] +extern crate failure; +extern crate fdio; +extern crate fidl_fuchsia_display as display; +extern crate fuchsia_async as async; +extern crate fuchsia_zircon as zx; +extern crate shared_buffer; + +use async::futures::{FutureExt, StreamExt}; +use display::{ControllerEvent, ControllerProxy, ImageConfig}; +use failure::Error; +use fdio::fdio_sys::{fdio_ioctl, IOCTL_FAMILY_DISPLAY_CONTROLLER, IOCTL_KIND_GET_HANDLE}; +use fdio::make_ioctl; +use shared_buffer::SharedBuffer; +use std::cell::RefCell; +use std::fs::{File, OpenOptions}; +use std::mem; +use std::os::unix::io::AsRawFd; +use std::ptr; +use std::rc::Rc; +use zx::sys::{zx_cache_flush, zx_handle_t, ZX_CACHE_FLUSH_DATA, ZX_VM_FLAG_PERM_READ, + ZX_VM_FLAG_PERM_WRITE}; +use zx::{Handle, Status, Vmar, Vmo}; + +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_NONE: u32 = 0; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_RGB_565: u32 = 131073; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_RGB_332: u32 = 65538; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_RGB_2220: u32 = 65539; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_ARGB_8888: u32 = 262148; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_RGB_x888: u32 = 262149; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_MONO_8: u32 = 65543; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_GRAY_8: u32 = 65543; +#[allow(non_camel_case_types, non_upper_case_globals)] +const ZX_PIXEL_FORMAT_MONO_1: u32 = 6; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PixelFormat { + Argb8888, + Gray8, + Mono1, + Mono8, + Rgb2220, + Rgb332, + Rgb565, + RgbX888, + Unknown, +} + +impl Default for PixelFormat { + fn default() -> PixelFormat { + PixelFormat::Unknown + } +} + +impl From<u32> for PixelFormat { + fn from(pixel_format: u32) -> Self { + #[allow(non_upper_case_globals)] + match pixel_format { + ZX_PIXEL_FORMAT_ARGB_8888 => PixelFormat::Argb8888, + ZX_PIXEL_FORMAT_MONO_1 => PixelFormat::Mono1, + ZX_PIXEL_FORMAT_MONO_8 => PixelFormat::Mono8, + ZX_PIXEL_FORMAT_RGB_2220 => PixelFormat::Rgb2220, + ZX_PIXEL_FORMAT_RGB_332 => PixelFormat::Rgb332, + ZX_PIXEL_FORMAT_RGB_565 => PixelFormat::Rgb565, + ZX_PIXEL_FORMAT_RGB_x888 => PixelFormat::RgbX888, + // ZX_PIXEL_FORMAT_GRAY_8 is an alias for ZX_PIXEL_FORMAT_MONO_8 + ZX_PIXEL_FORMAT_NONE => PixelFormat::Unknown, + _ => PixelFormat::Unknown, + } + } +} + +impl Into<u32> for PixelFormat { + fn into(self) -> u32 { + match self { + PixelFormat::Argb8888 => ZX_PIXEL_FORMAT_ARGB_8888, + PixelFormat::Mono1 => ZX_PIXEL_FORMAT_MONO_1, + PixelFormat::Mono8 => ZX_PIXEL_FORMAT_MONO_8, + PixelFormat::Rgb2220 => ZX_PIXEL_FORMAT_RGB_2220, + PixelFormat::Rgb332 => ZX_PIXEL_FORMAT_RGB_332, + PixelFormat::Rgb565 => ZX_PIXEL_FORMAT_RGB_565, + PixelFormat::RgbX888 => ZX_PIXEL_FORMAT_RGB_x888, + PixelFormat::Gray8 => ZX_PIXEL_FORMAT_GRAY_8, + PixelFormat::Unknown => ZX_PIXEL_FORMAT_NONE, + } + } +} + +fn pixel_format_bytes(pixel_format: u32) -> usize { + ((pixel_format >> 16) & 7) as usize +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct Config { + pub display_id: u64, + pub width: u32, + pub height: u32, + pub linear_stride_pixels: u32, + pub format: PixelFormat, + pub pixel_size_bytes: u32, +} + +impl Config { + pub fn linear_stride_bytes(&self) -> usize { + self.linear_stride_pixels as usize * self.pixel_size_bytes as usize + } +} + +pub struct Frame<'a> { + config: Config, + image_id: u64, + pixel_buffer_addr: usize, + pixel_buffer: SharedBuffer<'a>, +} + +impl<'a> Frame<'a> { + fn allocate_image_vmo( + framebuffer: &FrameBuffer, executor: &mut async::Executor, + ) -> Result<Vmo, Error> { + let vmo: Rc<RefCell<Option<Vmo>>> = Rc::new(RefCell::new(None)); + let vmo_response = framebuffer + .controller + .allocate_vmo(framebuffer.byte_size() as u64) + .map(|(status, allocated_vmo)| { + if status == Status::OK { + vmo.replace(allocated_vmo); + } + }); + executor.run_singlethreaded(vmo_response)?; + let vmo = vmo.replace(None); + if let Some(vmo) = vmo { + Ok(vmo) + } else { + Err(format_err!("Could not allocate image vmo")) + } + } + + fn import_image_vmo( + framebuffer: &FrameBuffer, executor: &mut async::Executor, image_vmo: Vmo, + ) -> Result<u64, Error> { + let pixel_format: u32 = framebuffer.config.format.into(); + let mut image_config = ImageConfig { + width: framebuffer.config.width, + height: framebuffer.config.height, + pixel_format: pixel_format as i32, + type_: 0, + }; + + let image_id: Rc<RefCell<Option<u64>>> = Rc::new(RefCell::new(None)); + let import_response = framebuffer + .controller + .import_vmo_image(&mut image_config, image_vmo, 0) + .map(|(status, id)| { + if status == Status::OK { + image_id.replace(Some(id)); + } + }); + + executor.run_singlethreaded(import_response)?; + + let image_id = image_id.replace(None); + if let Some(image_id) = image_id { + Ok(image_id) + } else { + Err(format_err!("Could not import image vmo")) + } + } + + pub fn new( + framebuffer: &'a FrameBuffer, executor: &mut async::Executor, + ) -> Result<Frame<'a>, Error> { + let image_vmo = Self::allocate_image_vmo(framebuffer, executor)?; + + // map image VMO + let pixel_buffer_addr = Vmar::root_self().map( + 0, + &image_vmo, + 0, + framebuffer.byte_size(), + ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE, + )?; + + // import image VMO + let image_id = Self::import_image_vmo(framebuffer, executor, image_vmo)?; + + // construct frame + let frame_buffer_pixel_ptr = pixel_buffer_addr as *mut u8; + Ok(Frame { + config: framebuffer.get_config(), + image_id: image_id, + pixel_buffer_addr, + pixel_buffer: unsafe { + SharedBuffer::new(frame_buffer_pixel_ptr, framebuffer.byte_size()) + }, + }) + } + + pub fn write_pixel(&self, x: u32, y: u32, value: &[u8]) { + let pixel_size = self.config.pixel_size_bytes as usize; + let offset = self.linear_stride_bytes() * y as usize + x as usize * pixel_size; + self.pixel_buffer.write_at(offset, value); + } + + pub fn fill_rectangle(&self, x: u32, y: u32, width: u32, height: u32, value: &[u8]) { + let left = x.min(self.config.width); + let right = (left + width).min(self.config.width); + let top = y.min(self.config.height); + let bottom = (top + height).min(self.config.width); + for j in top..bottom { + for i in left..right { + self.write_pixel(i, j, value); + } + } + } + + pub fn present(&self, framebuffer: &FrameBuffer) -> Result<(), Error> { + let frame_buffer_pixel_ptr = self.pixel_buffer_addr as *mut u8; + let result = unsafe { + zx_cache_flush( + frame_buffer_pixel_ptr, + self.byte_size(), + ZX_CACHE_FLUSH_DATA, + ) + }; + if result != 0 { + return Err(format_err!("zx_cache_flush failed: {}", result)); + } + framebuffer + .controller + .set_display_image(self.config.display_id, self.image_id, 0, 0, 0)?; + framebuffer.controller.apply_config()?; + Ok(()) + } + + fn byte_size(&self) -> usize { + self.linear_stride_bytes() * self.config.height as usize + } + + fn linear_stride_bytes(&self) -> usize { + self.config.linear_stride_pixels as usize * self.config.pixel_size_bytes as usize + } +} + +impl<'a> Drop for Frame<'a> { + fn drop(&mut self) { + Vmar::root_self() + .unmap(self.pixel_buffer_addr, self.byte_size()) + .unwrap(); + } +} + +pub struct FrameBuffer { + display_controller: File, + controller: ControllerProxy, + config: Config, +} + +impl FrameBuffer { + fn get_display_handle(file: &File) -> Result<Handle, Error> { + let fd = file.as_raw_fd() as i32; + let ioctl_display_controller_get_handle = + make_ioctl(IOCTL_KIND_GET_HANDLE, IOCTL_FAMILY_DISPLAY_CONTROLLER, 1); + let mut display_handle: zx_handle_t = 0; + let display_handle_ptr: *mut std::os::raw::c_void = + &mut display_handle as *mut _ as *mut std::os::raw::c_void; + let result_size = unsafe { + fdio_ioctl( + fd, + ioctl_display_controller_get_handle, + ptr::null(), + 0, + display_handle_ptr, + mem::size_of::<zx_handle_t>(), + ) + }; + + if result_size != mem::size_of::<zx_handle_t>() as isize { + return Err(format_err!( + "ioctl_display_controller_get_handle failed: {}", + result_size + )); + } + + Ok(unsafe { Handle::from_raw(display_handle) }) + } + + fn create_config_from_event_stream( + proxy: &ControllerProxy, executor: &mut async::Executor, + ) -> Result<Config, Error> { + let config: Rc<RefCell<Option<Config>>> = Rc::new(RefCell::new(None)); + let stream = proxy.take_event_stream(); + let event_listener = stream + .filter(|event| { + if let ControllerEvent::DisplaysChanged { added, .. } = event { + let mut display_id; + let mut zx_pixel_format = 0; + let mut linear_stride_pixels = 0; + let mut pixel_format = PixelFormat::Unknown; + let mut pixel_size_bytes = 0; + if added.len() > 0 { + let first_added = &added[0]; + display_id = first_added.id; + if first_added.pixel_format.len() > 0 { + zx_pixel_format = first_added.pixel_format[0]; + pixel_format = zx_pixel_format.into(); + } + if first_added.modes.len() > 0 { + let mode = &first_added.modes[0]; + if pixel_format != PixelFormat::Unknown { + pixel_size_bytes = pixel_format_bytes(zx_pixel_format); + linear_stride_pixels = mode.horizontal_resolution; + } + let calculated_config = Config { + display_id: display_id, + width: mode.horizontal_resolution, + height: mode.vertical_resolution, + linear_stride_pixels, + format: pixel_format, + pixel_size_bytes: pixel_size_bytes as u32, + }; + config.replace(Some(calculated_config)); + } + } + } + Ok(true) + }) + .next(); + + executor + .run_singlethreaded(event_listener) + .map_err(|(e, _rest_of_stream)| e)?; + + let config = config.replace(None); + if let Some(config) = config { + Ok(config) + } else { + Err(format_err!("Could not find display")) + } + } + + pub fn new( + display_index: Option<usize>, executor: &mut async::Executor, + ) -> Result<FrameBuffer, Error> { + let device_path = format!( + "/dev/class/display-controller/{:03}", + display_index.unwrap_or(0) + ); + let file = OpenOptions::new().read(true).write(true).open(device_path)?; + let zx_handle = Self::get_display_handle(&file)?; + let channel = async::Channel::from_channel(zx_handle.into())?; + let proxy = ControllerProxy::new(channel); + let config = Self::create_config_from_event_stream(&proxy, executor)?; + + Ok(FrameBuffer { + display_controller: file, + controller: proxy, + config: config, + }) + } + + pub fn new_frame<'a>(&'a self, executor: &mut async::Executor) -> Result<Frame<'a>, Error> { + Frame::new(&self, executor) + } + + pub fn get_config(&self) -> Config { + self.config + } + + pub fn byte_size(&self) -> usize { + self.config.height as usize * self.config.linear_stride_bytes() + } +} + +impl Drop for FrameBuffer { + fn drop(&mut self) {} +} + +#[cfg(test)] +mod tests { + extern crate fuchsia_async as async; + + use FrameBuffer; + + #[test] + fn test_framebuffer() { + let mut executor = async::Executor::new().unwrap(); + let fb = FrameBuffer::new(None, &mut executor).unwrap(); + let _frame = fb.new_frame(&mut executor).unwrap(); + } +}