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