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
53use crate::proto::unsafe_protocol;
54use crate::util::usize_from_u32;
55use crate::{Result, Status};
56use core::marker::PhantomData;
57use core::mem;
58use core::ptr;
59
60/// Provides access to the video hardware's frame buffer.
61///
62/// The GOP can be used to set the properties of the frame buffer,
63/// and also allows the app to access the in-memory buffer.
64#[repr(C)]
65#[unsafe_protocol("9042a9de-23dc-4a38-96fb-7aded080516a")]
66pub struct GraphicsOutput {
67 query_mode: extern "efiapi" fn(
68 &GraphicsOutput,
69 mode: u32,
70 info_sz: &mut usize,
71 &mut *const ModeInfo,
72 ) -> Status,
73 set_mode: extern "efiapi" fn(&mut GraphicsOutput, mode: u32) -> Status,
74 // Clippy correctly complains that this is too complicated, but we can't change the spec.
75 blt: unsafe extern "efiapi" fn(
76 this: &mut GraphicsOutput,
77 buffer: *mut BltPixel,
78 op: u32,
79 source_x: usize,
80 source_y: usize,
81 dest_x: usize,
82 dest_y: usize,
83 width: usize,
84 height: usize,
85 stride: usize,
86 ) -> Status,
87 mode: *const ModeData,
88}
89
90impl GraphicsOutput {
91 /// Returns information for an available graphics mode that the graphics
92 /// device and the set of active video output devices supports.
93 pub fn query_mode(&self, index: u32) -> Result<Mode> {
94 let mut info_sz = 0;
95 let mut info = ptr::null();
96
97 (self.query_mode)(self, index, &mut info_sz, &mut info).into_with_val(|| {
98 let info = unsafe { *info };
99 Mode {
100 index,
101 info_sz,
102 info,
103 }
104 })
105 }
106
107 /// Returns information about all available graphics modes.
108 #[must_use]
109 pub fn modes(&self) -> ModeIter {
110 ModeIter {
111 gop: self,
112 current: 0,
113 max: self.mode().max_mode,
114 }
115 }
116
117 /// Sets the video device into the specified mode, clearing visible portions
118 /// of the output display to black.
119 ///
120 /// This function will invalidate the current framebuffer.
121 pub fn set_mode(&mut self, mode: &Mode) -> Result {
122 (self.set_mode)(self, mode.index).into()
123 }
124
125 /// Performs a blt (block transfer) operation on the frame buffer.
126 ///
127 /// Every operation requires different parameters.
128 pub fn blt(&mut self, op: BltOp) -> Result {
129 // Demultiplex the operation type.
130 unsafe {
131 match op {
132 BltOp::VideoFill {
133 color,
134 dest: (dest_x, dest_y),
135 dims: (width, height),
136 } => {
137 self.check_framebuffer_region((dest_x, dest_y), (width, height));
138 (self.blt)(
139 self,
140 &color as *const _ as *mut _,
141 0,
142 0,
143 0,
144 dest_x,
145 dest_y,
146 width,
147 height,
148 0,
149 )
150 .into()
151 }
152 BltOp::VideoToBltBuffer {
153 buffer,
154 src: (src_x, src_y),
155 dest: dest_region,
156 dims: (width, height),
157 } => {
158 self.check_framebuffer_region((src_x, src_y), (width, height));
159 self.check_blt_buffer_region(dest_region, (width, height), buffer.len());
160 match dest_region {
161 BltRegion::Full => (self.blt)(
162 self,
163 buffer.as_mut_ptr(),
164 1,
165 src_x,
166 src_y,
167 0,
168 0,
169 width,
170 height,
171 0,
172 )
173 .into(),
174 BltRegion::SubRectangle {
175 coords: (dest_x, dest_y),
176 px_stride,
177 } => (self.blt)(
178 self,
179 buffer.as_mut_ptr(),
180 1,
181 src_x,
182 src_y,
183 dest_x,
184 dest_y,
185 width,
186 height,
187 px_stride * core::mem::size_of::<BltPixel>(),
188 )
189 .into(),
190 }
191 }
192 BltOp::BufferToVideo {
193 buffer,
194 src: src_region,
195 dest: (dest_x, dest_y),
196 dims: (width, height),
197 } => {
198 self.check_blt_buffer_region(src_region, (width, height), buffer.len());
199 self.check_framebuffer_region((dest_x, dest_y), (width, height));
200 match src_region {
201 BltRegion::Full => (self.blt)(
202 self,
203 buffer.as_ptr() as *mut _,
204 2,
205 0,
206 0,
207 dest_x,
208 dest_y,
209 width,
210 height,
211 0,
212 )
213 .into(),
214 BltRegion::SubRectangle {
215 coords: (src_x, src_y),
216 px_stride,
217 } => (self.blt)(
218 self,
219 buffer.as_ptr() as *mut _,
220 2,
221 src_x,
222 src_y,
223 dest_x,
224 dest_y,
225 width,
226 height,
227 px_stride * core::mem::size_of::<BltPixel>(),
228 )
229 .into(),
230 }
231 }
232 BltOp::VideoToVideo {
233 src: (src_x, src_y),
234 dest: (dest_x, dest_y),
235 dims: (width, height),
236 } => {
237 self.check_framebuffer_region((src_x, src_y), (width, height));
238 self.check_framebuffer_region((dest_x, dest_y), (width, height));
239 (self.blt)(
240 self,
241 ptr::null_mut(),
242 3,
243 src_x,
244 src_y,
245 dest_x,
246 dest_y,
247 width,
248 height,
249 0,
250 )
251 .into()
252 }
253 }
254 }
255 }
256
257 /// Memory-safety check for accessing a region of the framebuffer
258 fn check_framebuffer_region(&self, coords: (usize, usize), dims: (usize, usize)) {
259 let (width, height) = self.current_mode_info().resolution();
260 assert!(
261 coords.0.saturating_add(dims.0) <= width,
262 "Horizontal framebuffer coordinate out of bounds"
263 );
264 assert!(
265 coords.1.saturating_add(dims.1) <= height,
266 "Vertical framebuffer coordinate out of bounds"
267 );
268 }
269
270 /// Memory-safety check for accessing a region of a user-provided buffer
271 fn check_blt_buffer_region(&self, region: BltRegion, dims: (usize, usize), buf_length: usize) {
272 match region {
273 BltRegion::Full => assert!(
274 dims.1.saturating_mul(dims.0) <= buf_length,
275 "BltBuffer access out of bounds"
276 ),
277 BltRegion::SubRectangle {
278 coords: (x, y),
279 px_stride,
280 } => {
281 assert!(
282 x.saturating_add(dims.0) <= px_stride,
283 "Horizontal BltBuffer coordinate out of bounds"
284 );
285 assert!(
286 y.saturating_add(dims.1).saturating_mul(px_stride) <= buf_length,
287 "Vertical BltBuffer coordinate out of bounds"
288 );
289 }
290 }
291 }
292
293 /// Returns the frame buffer information for the current mode.
294 #[must_use]
295 pub const fn current_mode_info(&self) -> ModeInfo {
296 *self.mode().info()
297 }
298
299 /// Access the frame buffer directly
300 pub fn frame_buffer(&mut self) -> FrameBuffer {
301 assert!(
302 self.mode().info().format != PixelFormat::BltOnly,
303 "Cannot access the framebuffer in a Blt-only mode"
304 );
305 let base = self.mode().fb_address as *mut u8;
306 let size = self.mode().fb_size;
307
308 FrameBuffer {
309 base,
310 size,
311 _lifetime: PhantomData,
312 }
313 }
314
315 const fn mode(&self) -> &ModeData {
316 unsafe { &*self.mode }
317 }
318}
319
320#[repr(C)]
321struct ModeData {
322 // Number of modes which the GOP supports.
323 max_mode: u32,
324 // Current mode.
325 mode: u32,
326 // Information about the current mode.
327 info: *const ModeInfo,
328 // Size of the above structure.
329 info_sz: usize,
330 // Physical address of the frame buffer.
331 fb_address: u64,
332 // Size in bytes. Equal to (pixel size) * height * stride.
333 fb_size: usize,
334}
335
336impl ModeData {
337 const fn info(&self) -> &ModeInfo {
338 unsafe { &*self.info }
339 }
340}
341
342/// Represents the format of the pixels in a frame buffer.
343#[derive(Debug, Copy, Clone, Eq, PartialEq)]
344#[repr(u32)]
345pub enum PixelFormat {
346 /// Each pixel is 32-bit long, with 24-bit RGB, and the last byte is reserved.
347 Rgb = 0,
348 /// Each pixel is 32-bit long, with 24-bit BGR, and the last byte is reserved.
349 Bgr,
350 /// Custom pixel format, check the associated bitmask.
351 Bitmask,
352 /// The graphics mode does not support drawing directly to the frame buffer.
353 ///
354 /// This means you will have to use the `blt` function which will
355 /// convert the graphics data to the device's internal pixel format.
356 BltOnly,
357 // SAFETY: UEFI also defines a PixelFormatMax variant, and states that all
358 // valid enum values are guaranteed to be smaller. Since that is the
359 // case, adding a new enum variant would be a breaking change, so it
360 // is safe to model this C enum as a Rust enum.
361}
362
363/// Bitmask used to indicate which bits of a pixel represent a given color.
364#[derive(Debug, Copy, Clone, Eq, PartialEq)]
365#[repr(C)]
366pub struct PixelBitmask {
367 /// The bits indicating the red channel.
368 pub red: u32,
369 /// The bits indicating the green channel.
370 pub green: u32,
371 /// The bits indicating the blue channel.
372 pub blue: u32,
373 /// The reserved bits, which are ignored by the video hardware.
374 pub reserved: u32,
375}
376
377/// Represents a graphics mode compatible with a given graphics device.
378pub struct Mode {
379 index: u32,
380 info_sz: usize,
381 info: ModeInfo,
382}
383
384impl Mode {
385 /// The size of the info structure in bytes.
386 ///
387 /// Newer versions of the spec might add extra information, in a backwards compatible way.
388 #[must_use]
389 pub const fn info_size(&self) -> usize {
390 self.info_sz
391 }
392
393 /// Returns a reference to the mode info structure.
394 #[must_use]
395 pub const fn info(&self) -> &ModeInfo {
396 &self.info
397 }
398}
399
400/// Information about a graphics output mode.
401#[derive(Debug, Copy, Clone)]
402#[repr(C)]
403pub struct ModeInfo {
404 // The only known version, associated with the current spec, is 0.
405 version: u32,
406 hor_res: u32,
407 ver_res: u32,
408 format: PixelFormat,
409 mask: PixelBitmask,
410 stride: u32,
411}
412
413impl ModeInfo {
414 /// Returns the (horizontal, vertical) resolution.
415 ///
416 /// On desktop monitors, this usually means (width, height).
417 #[must_use]
418 pub const fn resolution(&self) -> (usize, usize) {
419 (usize_from_u32(self.hor_res), usize_from_u32(self.ver_res))
420 }
421
422 /// Returns the format of the frame buffer.
423 #[must_use]
424 pub const fn pixel_format(&self) -> PixelFormat {
425 self.format
426 }
427
428 /// Returns the bitmask of the custom pixel format, if available.
429 #[must_use]
430 pub const fn pixel_bitmask(&self) -> Option<PixelBitmask> {
431 match self.format {
432 PixelFormat::Bitmask => Some(self.mask),
433 _ => None,
434 }
435 }
436
437 /// Returns the number of pixels per scanline.
438 ///
439 /// Due to performance reasons, the stride might not be equal to the width,
440 /// instead the stride might be bigger for better alignment.
441 #[must_use]
442 pub const fn stride(&self) -> usize {
443 usize_from_u32(self.stride)
444 }
445}
446
447/// Iterator for [`Mode`]s of the [`GraphicsOutput`] protocol.
448pub struct ModeIter<'gop> {
449 gop: &'gop GraphicsOutput,
450 current: u32,
451 max: u32,
452}
453
454impl<'gop> Iterator for ModeIter<'gop> {
455 type Item = Mode;
456
457 fn next(&mut self) -> Option<Self::Item> {
458 let index: u32 = self.current;
459 if index < self.max {
460 let m: Result = self.gop.query_mode(index);
461 self.current += 1;
462
463 m.ok().or_else(|| self.next())
464 } else {
465 None
466 }
467 }
468
469 fn size_hint(&self) -> (usize, Option<usize>) {
470 let size: usize = (self.max - self.current) as usize;
471 (size, Some(size))
472 }
473}
474
475impl ExactSizeIterator for ModeIter<'_> {}
476
477/// Format of pixel data used for blitting.
478///
479/// This is a BGR 24-bit format with an 8-bit padding, to keep each pixel 32-bit in size.
480#[allow(missing_docs)]
481#[derive(Debug, Copy, Clone)]
482#[repr(C)]
483pub struct BltPixel {
484 pub blue: u8,
485 pub green: u8,
486 pub red: u8,
487 _reserved: u8,
488}
489
490impl BltPixel {
491 /// Create a new pixel from RGB values.
492 #[must_use]
493 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
494 Self {
495 red,
496 green,
497 blue,
498 _reserved: 0,
499 }
500 }
501}
502
503impl From<u32> for BltPixel {
504 fn from(color: u32) -> Self {
505 Self {
506 blue: (color & 0x00_00_FF) as u8,
507 green: ((color & 0x00_FF_00) >> 8) as u8,
508 red: ((color & 0xFF_00_00) >> 16) as u8,
509 _reserved: 0,
510 }
511 }
512}
513
514/// Region of the `BltBuffer` which we are operating on
515///
516/// Some `Blt` operations can operate on either the full `BltBuffer` or a
517/// sub-rectangle of it, but require the stride to be known in the latter case.
518#[derive(Clone, Copy, Debug)]
519pub enum BltRegion {
520 /// Operate on the full `BltBuffer`
521 Full,
522
523 /// Operate on a sub-rectangle of the `BltBuffer`
524 SubRectangle {
525 /// Coordinate of the rectangle in the `BltBuffer`
526 coords: (usize, usize),
527
528 /// Stride (length of each row of the `BltBuffer`) in **pixels**
529 px_stride: usize,
530 },
531}
532
533/// Blit operation to perform.
534#[derive(Debug)]
535pub enum BltOp<'buf> {
536 /// Fills a rectangle of video display with a pixel color.
537 VideoFill {
538 /// The color to fill with.
539 color: BltPixel,
540 /// The X / Y coordinates of the destination rectangle.
541 dest: (usize, usize),
542 /// The width / height of the rectangle.
543 dims: (usize, usize),
544 },
545 /// Reads data from the video display to the buffer.
546 VideoToBltBuffer {
547 /// Buffer into which to copy data.
548 buffer: &'buf mut [BltPixel],
549 /// Coordinates of the source rectangle, in the frame buffer.
550 src: (usize, usize),
551 /// Location of the destination rectangle in the user-provided buffer
552 dest: BltRegion,
553 /// Width / height of the rectangles.
554 dims: (usize, usize),
555 },
556 /// Write data from the buffer to the video rectangle.
557 /// Delta must be the stride (count of bytes in a row) of the buffer.
558 BufferToVideo {
559 /// Buffer from which to copy data.
560 buffer: &'buf [BltPixel],
561 /// Location of the source rectangle in the user-provided buffer.
562 src: BltRegion,
563 /// Coordinates of the destination rectangle, in the frame buffer.
564 dest: (usize, usize),
565 /// Width / height of the rectangles.
566 dims: (usize, usize),
567 },
568 /// Copy from the source rectangle in video memory to
569 /// the destination rectangle, also in video memory.
570 VideoToVideo {
571 /// Coordinates of the source rectangle, in the frame buffer.
572 src: (usize, usize),
573 /// Coordinates of the destination rectangle, also in the frame buffer.
574 dest: (usize, usize),
575 /// Width / height of the rectangles.
576 dims: (usize, usize),
577 },
578}
579
580/// Direct access to a memory-mapped frame buffer
581pub struct FrameBuffer<'gop> {
582 base: *mut u8,
583 size: usize,
584 _lifetime: PhantomData<&'gop mut u8>,
585}
586
587impl<'gop> FrameBuffer<'gop> {
588 /// Access the raw framebuffer pointer
589 ///
590 /// To use this pointer safely and correctly, you must...
591 /// - Honor the pixel format and stride specified by the mode info
592 /// - Keep memory accesses in bound
593 /// - Use volatile reads and writes
594 /// - Make sure that the pointer does not outlive the FrameBuffer
595 ///
596 /// On some implementations this framebuffer pointer can be used after
597 /// exiting boot services, but that is not guaranteed by the UEFI Specification.
598 pub fn as_mut_ptr(&mut self) -> *mut u8 {
599 self.base
600 }
601
602 /// Query the framebuffer size in bytes
603 #[must_use]
604 pub const fn size(&self) -> usize {
605 self.size
606 }
607
608 /// Modify the i-th byte of the frame buffer
609 ///
610 /// # Safety
611 ///
612 /// This operation is unsafe because...
613 /// - You must honor the pixel format and stride specified by the mode info
614 /// - There is no bound checking on memory accesses in release mode
615 #[inline]
616 pub unsafe fn write_byte(&mut self, index: usize, value: u8) {
617 debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
618 self.base.add(index).write_volatile(value)
619 }
620
621 /// Read the i-th byte of the frame buffer
622 ///
623 /// # Safety
624 ///
625 /// This operation is unsafe because...
626 /// - You must honor the pixel format and stride specified by the mode info
627 /// - There is no bound checking on memory accesses in release mode
628 #[inline]
629 #[must_use]
630 pub unsafe fn read_byte(&self, index: usize) -> u8 {
631 debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
632 self.base.add(index).read_volatile()
633 }
634
635 /// Write a value in the frame buffer, starting at the i-th byte
636 ///
637 /// We only recommend using this method with [u8; N] arrays. Once Rust has
638 /// const generics, it will be deprecated and replaced with a write_bytes()
639 /// method that only accepts [u8; N] input.
640 ///
641 /// # Safety
642 ///
643 /// This operation is unsafe because...
644 /// - It is your responsibility to make sure that the value type makes sense
645 /// - You must honor the pixel format and stride specified by the mode info
646 /// - There is no bound checking on memory accesses in release mode
647 #[inline]
648 pub unsafe fn write_value<T>(&mut self, index: usize, value: T) {
649 debug_assert!(
650 index.saturating_add(mem::size_of::<T>()) <= self.size,
651 "Frame buffer accessed out of bounds"
652 );
653 let ptr = self.base.add(index).cast::<T>();
654 ptr.write_volatile(value)
655 }
656
657 /// Read a value from the frame buffer, starting at the i-th byte
658 ///
659 /// We only recommend using this method with [u8; N] arrays. Once Rust has
660 /// const generics, it will be deprecated and replaced with a read_bytes()
661 /// method that only accepts [u8; N] input.
662 ///
663 /// # Safety
664 ///
665 /// This operation is unsafe because...
666 /// - It is your responsibility to make sure that the value type makes sense
667 /// - You must honor the pixel format and stride specified by the mode info
668 /// - There is no bound checking on memory accesses in release mode
669 #[inline]
670 #[must_use]
671 pub unsafe fn read_value<T>(&self, index: usize) -> T {
672 debug_assert!(
673 index.saturating_add(mem::size_of::<T>()) <= self.size,
674 "Frame buffer accessed out of bounds"
675 );
676 (self.base.add(index) as *const T).read_volatile()
677 }
678}
679