1 //! Graphics output protocol. 2 //! 3 //! The UEFI GOP is meant to replace existing [VGA][vga] hardware interfaces. 4 //! 5 //! The GOP provides access to a hardware frame buffer and allows UEFI apps 6 //! to draw directly to the graphics output device. 7 //! 8 //! The advantage of the GOP over legacy VGA is that it allows multiple GPUs 9 //! to exist and be used on the system. There is a GOP implementation for every 10 //! unique GPU in the system which supports UEFI. 11 //! 12 //! [vga]: https://en.wikipedia.org/wiki/Video_Graphics_Array 13 //! 14 //! # Definitions 15 //! 16 //! All graphics operations use a coordinate system where the top-left of the screen 17 //! is mapped to the point (0, 0), and `y` increases going down. 18 //! 19 //! Rectangles are defined by their top-left corner, and their width and height. 20 //! 21 //! The stride is understood as the length in bytes of a scan line / row of a buffer. 22 //! In theory, a buffer with a width of 640 should have (640 * 4) bytes per row, 23 //! but in practice there might be some extra padding used for efficiency. 24 //! 25 //! Frame buffers represent the graphics card's image buffers, backing the displays. 26 //! 27 //! Blits (**bl**ock **t**ransfer) can do high-speed memory copy between 28 //! the frame buffer and itself, or to and from some other buffers. 29 //! 30 //! # Blitting 31 //! 32 //! On certain hardware, the frame buffer is in a opaque format, 33 //! or cannot be accessed by the CPU. In those cases, it is not possible 34 //! to draw directly to the frame buffer. You must draw to another buffer 35 //! with a known pixel format, and then submit a blit command to copy that buffer 36 //! into the back buffer. 37 //! 38 //! Blitting can also copy a rectangle from the frame buffer to 39 //! another rectangle in the frame buffer, or move data out of the frame buffer 40 //! into a CPU-visible buffer. It can also do very fast color fills. 41 //! 42 //! The source and destination rectangles must always be of the same size: 43 //! no stretching / squashing will be done. 44 //! 45 //! # Animations 46 //! 47 //! UEFI does not mention if double buffering is used, nor how often 48 //! the frame buffer gets sent to the screen, but it's safe to assume that 49 //! the graphics card will re-draw the buffer at around the monitor's refresh rate. 50 //! You will have to implement your own double buffering if you want to 51 //! avoid tearing with animations. 52 53 use crate::proto::unsafe_protocol; 54 use crate::util::usize_from_u32; 55 use crate::{boot, Result, StatusExt}; 56 use core::fmt::{Debug, Formatter}; 57 use core::marker::PhantomData; 58 use core::mem; 59 use core::ptr::{self, NonNull}; 60 use uefi_raw::protocol::console::{ 61 GraphicsOutputBltOperation, GraphicsOutputModeInformation, GraphicsOutputProtocol, 62 GraphicsOutputProtocolMode, 63 }; 64 65 pub use uefi_raw::protocol::console::PixelBitmask; 66 67 /// Provides access to the video hardware's frame buffer. 68 /// 69 /// The GOP can be used to set the properties of the frame buffer, 70 /// and also allows the app to access the in-memory buffer. 71 #[derive(Debug)] 72 #[repr(transparent)] 73 #[unsafe_protocol(GraphicsOutputProtocol::GUID)] 74 pub struct GraphicsOutput(GraphicsOutputProtocol); 75 76 impl GraphicsOutput { 77 /// Returns information for an available graphics mode that the graphics 78 /// device and the set of active video output devices supports. query_mode(&self, index: u32) -> Result<Mode>79 fn query_mode(&self, index: u32) -> Result<Mode> { 80 let mut info_sz = 0; 81 let mut info_heap_ptr = ptr::null(); 82 // query_mode allocates a buffer and stores the heap ptr in the provided 83 // variable. In this buffer, the queried data can be found. 84 unsafe { (self.0.query_mode)(&self.0, index, &mut info_sz, &mut info_heap_ptr) } 85 .to_result_with_val(|| { 86 // Transform to owned info on the stack. 87 let info = unsafe { *info_heap_ptr }; 88 89 let info_heap_ptr = info_heap_ptr.cast::<u8>().cast_mut(); 90 91 // User has no benefit from propagating this error. If this 92 // fails, it is an error of the UEFI implementation. 93 unsafe { boot::free_pool(NonNull::new(info_heap_ptr).unwrap()) } 94 .expect("buffer should be deallocatable"); 95 96 Mode { 97 index, 98 info_sz, 99 info: ModeInfo(info), 100 } 101 }) 102 } 103 104 /// Returns a [`ModeIter`]. 105 #[must_use] modes(&self) -> ModeIter106 pub const fn modes(&self) -> ModeIter { 107 ModeIter { 108 gop: self, 109 current: 0, 110 max: self.mode().max_mode, 111 } 112 } 113 114 /// Sets the video device into the specified mode, clearing visible portions 115 /// of the output display to black. 116 /// 117 /// This function will invalidate the current framebuffer. set_mode(&mut self, mode: &Mode) -> Result118 pub fn set_mode(&mut self, mode: &Mode) -> Result { 119 unsafe { (self.0.set_mode)(&mut self.0, mode.index) }.to_result() 120 } 121 122 /// Performs a blt (block transfer) operation on the frame buffer. 123 /// 124 /// Every operation requires different parameters. blt(&mut self, op: BltOp) -> Result125 pub fn blt(&mut self, op: BltOp) -> Result { 126 // Demultiplex the operation type. 127 unsafe { 128 match op { 129 BltOp::VideoFill { 130 color, 131 dest: (dest_x, dest_y), 132 dims: (width, height), 133 } => { 134 self.check_framebuffer_region((dest_x, dest_y), (width, height)); 135 (self.0.blt)( 136 &mut self.0, 137 &color as *const _ as *mut _, 138 GraphicsOutputBltOperation::BLT_VIDEO_FILL, 139 0, 140 0, 141 dest_x, 142 dest_y, 143 width, 144 height, 145 0, 146 ) 147 .to_result() 148 } 149 BltOp::VideoToBltBuffer { 150 buffer, 151 src: (src_x, src_y), 152 dest: dest_region, 153 dims: (width, height), 154 } => { 155 self.check_framebuffer_region((src_x, src_y), (width, height)); 156 self.check_blt_buffer_region(dest_region, (width, height), buffer.len()); 157 match dest_region { 158 BltRegion::Full => (self.0.blt)( 159 &mut self.0, 160 buffer.as_mut_ptr().cast(), 161 GraphicsOutputBltOperation::BLT_VIDEO_TO_BLT_BUFFER, 162 src_x, 163 src_y, 164 0, 165 0, 166 width, 167 height, 168 0, 169 ) 170 .to_result(), 171 BltRegion::SubRectangle { 172 coords: (dest_x, dest_y), 173 px_stride, 174 } => (self.0.blt)( 175 &mut self.0, 176 buffer.as_mut_ptr().cast(), 177 GraphicsOutputBltOperation::BLT_VIDEO_TO_BLT_BUFFER, 178 src_x, 179 src_y, 180 dest_x, 181 dest_y, 182 width, 183 height, 184 px_stride * core::mem::size_of::<BltPixel>(), 185 ) 186 .to_result(), 187 } 188 } 189 BltOp::BufferToVideo { 190 buffer, 191 src: src_region, 192 dest: (dest_x, dest_y), 193 dims: (width, height), 194 } => { 195 self.check_blt_buffer_region(src_region, (width, height), buffer.len()); 196 self.check_framebuffer_region((dest_x, dest_y), (width, height)); 197 match src_region { 198 BltRegion::Full => (self.0.blt)( 199 &mut self.0, 200 buffer.as_ptr() as *mut _, 201 GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO, 202 0, 203 0, 204 dest_x, 205 dest_y, 206 width, 207 height, 208 0, 209 ) 210 .to_result(), 211 BltRegion::SubRectangle { 212 coords: (src_x, src_y), 213 px_stride, 214 } => (self.0.blt)( 215 &mut self.0, 216 buffer.as_ptr() as *mut _, 217 GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO, 218 src_x, 219 src_y, 220 dest_x, 221 dest_y, 222 width, 223 height, 224 px_stride * core::mem::size_of::<BltPixel>(), 225 ) 226 .to_result(), 227 } 228 } 229 BltOp::VideoToVideo { 230 src: (src_x, src_y), 231 dest: (dest_x, dest_y), 232 dims: (width, height), 233 } => { 234 self.check_framebuffer_region((src_x, src_y), (width, height)); 235 self.check_framebuffer_region((dest_x, dest_y), (width, height)); 236 (self.0.blt)( 237 &mut self.0, 238 ptr::null_mut(), 239 GraphicsOutputBltOperation::BLT_VIDEO_TO_VIDEO, 240 src_x, 241 src_y, 242 dest_x, 243 dest_y, 244 width, 245 height, 246 0, 247 ) 248 .to_result() 249 } 250 } 251 } 252 } 253 254 /// Memory-safety check for accessing a region of the framebuffer check_framebuffer_region(&self, coords: (usize, usize), dims: (usize, usize))255 fn check_framebuffer_region(&self, coords: (usize, usize), dims: (usize, usize)) { 256 let (width, height) = self.current_mode_info().resolution(); 257 assert!( 258 coords.0.saturating_add(dims.0) <= width, 259 "Horizontal framebuffer coordinate out of bounds" 260 ); 261 assert!( 262 coords.1.saturating_add(dims.1) <= height, 263 "Vertical framebuffer coordinate out of bounds" 264 ); 265 } 266 267 /// Memory-safety check for accessing a region of a user-provided buffer check_blt_buffer_region(&self, region: BltRegion, dims: (usize, usize), buf_length: usize)268 fn check_blt_buffer_region(&self, region: BltRegion, dims: (usize, usize), buf_length: usize) { 269 match region { 270 BltRegion::Full => assert!( 271 dims.1.saturating_mul(dims.0) <= buf_length, 272 "BltBuffer access out of bounds" 273 ), 274 BltRegion::SubRectangle { 275 coords: (x, y), 276 px_stride, 277 } => { 278 assert!( 279 x.saturating_add(dims.0) <= px_stride, 280 "Horizontal BltBuffer coordinate out of bounds" 281 ); 282 assert!( 283 y.saturating_add(dims.1).saturating_mul(px_stride) <= buf_length, 284 "Vertical BltBuffer coordinate out of bounds" 285 ); 286 } 287 } 288 } 289 290 /// Returns the frame buffer information for the current mode. 291 #[must_use] current_mode_info(&self) -> ModeInfo292 pub const fn current_mode_info(&self) -> ModeInfo { 293 unsafe { *self.mode().info.cast_const().cast::<ModeInfo>() } 294 } 295 296 /// Access the frame buffer directly frame_buffer(&mut self) -> FrameBuffer297 pub fn frame_buffer(&mut self) -> FrameBuffer { 298 assert!( 299 self.current_mode_info().pixel_format() != PixelFormat::BltOnly, 300 "Cannot access the framebuffer in a Blt-only mode" 301 ); 302 let base = self.mode().frame_buffer_base as *mut u8; 303 let size = self.mode().frame_buffer_size; 304 305 FrameBuffer { 306 base, 307 size, 308 _lifetime: PhantomData, 309 } 310 } 311 mode(&self) -> &GraphicsOutputProtocolMode312 const fn mode(&self) -> &GraphicsOutputProtocolMode { 313 unsafe { &*self.0.mode.cast_const() } 314 } 315 } 316 317 /// Represents the format of the pixels in a frame buffer. 318 #[derive(Debug, Copy, Clone, Eq, PartialEq)] 319 #[repr(u32)] 320 pub enum PixelFormat { 321 /// Each pixel is 32-bit long, with 24-bit RGB, and the last byte is reserved. 322 Rgb = 0, 323 /// Each pixel is 32-bit long, with 24-bit BGR, and the last byte is reserved. 324 Bgr, 325 /// Custom pixel format, check the associated bitmask. 326 Bitmask, 327 /// The graphics mode does not support drawing directly to the frame buffer. 328 /// 329 /// This means you will have to use the `blt` function which will 330 /// convert the graphics data to the device's internal pixel format. 331 BltOnly, 332 // SAFETY: UEFI also defines a PixelFormatMax variant, and states that all 333 // valid enum values are guaranteed to be smaller. Since that is the 334 // case, adding a new enum variant would be a breaking change, so it 335 // is safe to model this C enum as a Rust enum. 336 } 337 338 /// Represents a graphics mode compatible with a given graphics device. 339 #[derive(Copy, Clone, Debug)] 340 pub struct Mode { 341 index: u32, 342 info_sz: usize, 343 info: ModeInfo, 344 } 345 346 impl Mode { 347 /// The size of the info structure in bytes. 348 /// 349 /// Newer versions of the spec might add extra information, in a backwards compatible way. 350 #[must_use] info_size(&self) -> usize351 pub const fn info_size(&self) -> usize { 352 self.info_sz 353 } 354 355 /// Returns a reference to the mode info structure. 356 #[must_use] info(&self) -> &ModeInfo357 pub const fn info(&self) -> &ModeInfo { 358 &self.info 359 } 360 } 361 362 /// Information about a graphics output mode. 363 #[derive(Debug, Copy, Clone, Eq, PartialEq)] 364 #[repr(transparent)] 365 pub struct ModeInfo(GraphicsOutputModeInformation); 366 367 impl ModeInfo { 368 /// Returns the (horizontal, vertical) resolution. 369 /// 370 /// On desktop monitors, this usually means (width, height). 371 #[must_use] resolution(&self) -> (usize, usize)372 pub const fn resolution(&self) -> (usize, usize) { 373 ( 374 usize_from_u32(self.0.horizontal_resolution), 375 usize_from_u32(self.0.vertical_resolution), 376 ) 377 } 378 379 /// Returns the format of the frame buffer. 380 #[must_use] pixel_format(&self) -> PixelFormat381 pub const fn pixel_format(&self) -> PixelFormat { 382 match self.0.pixel_format.0 { 383 0 => PixelFormat::Rgb, 384 1 => PixelFormat::Bgr, 385 2 => PixelFormat::Bitmask, 386 3 => PixelFormat::BltOnly, 387 _ => panic!("invalid pixel format"), 388 } 389 } 390 391 /// Returns the bitmask of the custom pixel format, if available. 392 #[must_use] pixel_bitmask(&self) -> Option<PixelBitmask>393 pub const fn pixel_bitmask(&self) -> Option<PixelBitmask> { 394 match self.pixel_format() { 395 PixelFormat::Bitmask => Some(self.0.pixel_information), 396 _ => None, 397 } 398 } 399 400 /// Returns the number of pixels per scanline. 401 /// 402 /// Due to performance reasons, the stride might not be equal to the width, 403 /// instead the stride might be bigger for better alignment. 404 #[must_use] stride(&self) -> usize405 pub const fn stride(&self) -> usize { 406 usize_from_u32(self.0.pixels_per_scan_line) 407 } 408 } 409 410 /// Iterator for [`Mode`]s of the [`GraphicsOutput`] protocol. 411 pub struct ModeIter<'gop> { 412 gop: &'gop GraphicsOutput, 413 current: u32, 414 max: u32, 415 } 416 417 impl<'gop> Iterator for ModeIter<'gop> { 418 type Item = Mode; 419 next(&mut self) -> Option<Self::Item>420 fn next(&mut self) -> Option<Self::Item> { 421 let index = self.current; 422 if index < self.max { 423 let m = self.gop.query_mode(index); 424 self.current += 1; 425 426 m.ok().or_else(|| self.next()) 427 } else { 428 None 429 } 430 } 431 size_hint(&self) -> (usize, Option<usize>)432 fn size_hint(&self) -> (usize, Option<usize>) { 433 let size = (self.max - self.current) as usize; 434 (size, Some(size)) 435 } 436 } 437 438 impl<'gop> Debug for ModeIter<'gop> { fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result439 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 440 f.debug_struct("ModeIter") 441 .field("current", &self.current) 442 .field("max", &self.max) 443 .finish() 444 } 445 } 446 447 impl ExactSizeIterator for ModeIter<'_> {} 448 449 /// Format of pixel data used for blitting. 450 /// 451 /// This is a BGR 24-bit format with an 8-bit padding, to keep each pixel 32-bit in size. 452 #[allow(missing_docs)] 453 #[derive(Debug, Copy, Clone)] 454 #[repr(C)] 455 pub struct BltPixel { 456 pub blue: u8, 457 pub green: u8, 458 pub red: u8, 459 _reserved: u8, 460 } 461 462 impl BltPixel { 463 /// Create a new pixel from RGB values. 464 #[must_use] new(red: u8, green: u8, blue: u8) -> Self465 pub const fn new(red: u8, green: u8, blue: u8) -> Self { 466 Self { 467 red, 468 green, 469 blue, 470 _reserved: 0, 471 } 472 } 473 } 474 475 impl From<u32> for BltPixel { from(color: u32) -> Self476 fn from(color: u32) -> Self { 477 Self { 478 blue: (color & 0x00_00_FF) as u8, 479 green: ((color & 0x00_FF_00) >> 8) as u8, 480 red: ((color & 0xFF_00_00) >> 16) as u8, 481 _reserved: 0, 482 } 483 } 484 } 485 486 /// Region of the `BltBuffer` which we are operating on 487 /// 488 /// Some `Blt` operations can operate on either the full `BltBuffer` or a 489 /// sub-rectangle of it, but require the stride to be known in the latter case. 490 #[derive(Clone, Copy, Debug)] 491 pub enum BltRegion { 492 /// Operate on the full `BltBuffer` 493 Full, 494 495 /// Operate on a sub-rectangle of the `BltBuffer` 496 SubRectangle { 497 /// Coordinate of the rectangle in the `BltBuffer` 498 coords: (usize, usize), 499 500 /// Stride (length of each row of the `BltBuffer`) in **pixels** 501 px_stride: usize, 502 }, 503 } 504 505 /// Blit operation to perform. 506 #[derive(Debug)] 507 pub enum BltOp<'buf> { 508 /// Fills a rectangle of video display with a pixel color. 509 VideoFill { 510 /// The color to fill with. 511 color: BltPixel, 512 /// The X / Y coordinates of the destination rectangle. 513 dest: (usize, usize), 514 /// The width / height of the rectangle. 515 dims: (usize, usize), 516 }, 517 /// Reads data from the video display to the buffer. 518 VideoToBltBuffer { 519 /// Buffer into which to copy data. 520 buffer: &'buf mut [BltPixel], 521 /// Coordinates of the source rectangle, in the frame buffer. 522 src: (usize, usize), 523 /// Location of the destination rectangle in the user-provided buffer 524 dest: BltRegion, 525 /// Width / height of the rectangles. 526 dims: (usize, usize), 527 }, 528 /// Write data from the buffer to the video rectangle. 529 /// Delta must be the stride (count of bytes in a row) of the buffer. 530 BufferToVideo { 531 /// Buffer from which to copy data. 532 buffer: &'buf [BltPixel], 533 /// Location of the source rectangle in the user-provided buffer. 534 src: BltRegion, 535 /// Coordinates of the destination rectangle, in the frame buffer. 536 dest: (usize, usize), 537 /// Width / height of the rectangles. 538 dims: (usize, usize), 539 }, 540 /// Copy from the source rectangle in video memory to 541 /// the destination rectangle, also in video memory. 542 VideoToVideo { 543 /// Coordinates of the source rectangle, in the frame buffer. 544 src: (usize, usize), 545 /// Coordinates of the destination rectangle, also in the frame buffer. 546 dest: (usize, usize), 547 /// Width / height of the rectangles. 548 dims: (usize, usize), 549 }, 550 } 551 552 /// Direct access to a memory-mapped frame buffer 553 #[derive(Debug)] 554 pub struct FrameBuffer<'gop> { 555 base: *mut u8, 556 size: usize, 557 _lifetime: PhantomData<&'gop mut u8>, 558 } 559 560 impl<'gop> FrameBuffer<'gop> { 561 /// Access the raw framebuffer pointer 562 /// 563 /// To use this pointer safely and correctly, you must... 564 /// - Honor the pixel format and stride specified by the mode info 565 /// - Keep memory accesses in bound 566 /// - Use volatile reads and writes 567 /// - Make sure that the pointer does not outlive the FrameBuffer 568 /// 569 /// On some implementations this framebuffer pointer can be used after 570 /// exiting boot services, but that is not guaranteed by the UEFI Specification. as_mut_ptr(&mut self) -> *mut u8571 pub fn as_mut_ptr(&mut self) -> *mut u8 { 572 self.base 573 } 574 575 /// Query the framebuffer size in bytes 576 #[must_use] size(&self) -> usize577 pub const fn size(&self) -> usize { 578 self.size 579 } 580 581 /// Modify the i-th byte of the frame buffer 582 /// 583 /// # Safety 584 /// 585 /// This operation is unsafe because... 586 /// - You must honor the pixel format and stride specified by the mode info 587 /// - There is no bound checking on memory accesses in release mode 588 #[inline] write_byte(&mut self, index: usize, value: u8)589 pub unsafe fn write_byte(&mut self, index: usize, value: u8) { 590 debug_assert!(index < self.size, "Frame buffer accessed out of bounds"); 591 self.base.add(index).write_volatile(value) 592 } 593 594 /// Read the i-th byte of the frame buffer 595 /// 596 /// # Safety 597 /// 598 /// This operation is unsafe because... 599 /// - You must honor the pixel format and stride specified by the mode info 600 /// - There is no bound checking on memory accesses in release mode 601 #[inline] 602 #[must_use] read_byte(&self, index: usize) -> u8603 pub unsafe fn read_byte(&self, index: usize) -> u8 { 604 debug_assert!(index < self.size, "Frame buffer accessed out of bounds"); 605 self.base.add(index).read_volatile() 606 } 607 608 /// Write a value in the frame buffer, starting at the i-th byte 609 /// 610 /// We only recommend using this method with [u8; N] arrays. Once Rust has 611 /// const generics, it will be deprecated and replaced with a write_bytes() 612 /// method that only accepts [u8; N] input. 613 /// 614 /// # Safety 615 /// 616 /// This operation is unsafe because... 617 /// - It is your responsibility to make sure that the value type makes sense 618 /// - You must honor the pixel format and stride specified by the mode info 619 /// - There is no bound checking on memory accesses in release mode 620 #[inline] write_value<T>(&mut self, index: usize, value: T)621 pub unsafe fn write_value<T>(&mut self, index: usize, value: T) { 622 debug_assert!( 623 index.saturating_add(mem::size_of::<T>()) <= self.size, 624 "Frame buffer accessed out of bounds" 625 ); 626 let ptr = self.base.add(index).cast::<T>(); 627 ptr.write_volatile(value) 628 } 629 630 /// Read a value from the frame buffer, starting at the i-th byte 631 /// 632 /// We only recommend using this method with [u8; N] arrays. Once Rust has 633 /// const generics, it will be deprecated and replaced with a read_bytes() 634 /// method that only accepts [u8; N] input. 635 /// 636 /// # Safety 637 /// 638 /// This operation is unsafe because... 639 /// - It is your responsibility to make sure that the value type makes sense 640 /// - You must honor the pixel format and stride specified by the mode info 641 /// - There is no bound checking on memory accesses in release mode 642 #[inline] 643 #[must_use] read_value<T>(&self, index: usize) -> T644 pub unsafe fn read_value<T>(&self, index: usize) -> T { 645 debug_assert!( 646 index.saturating_add(mem::size_of::<T>()) <= self.size, 647 "Frame buffer accessed out of bounds" 648 ); 649 (self.base.add(index) as *const T).read_volatile() 650 } 651 } 652