Skip to content

Commit 5e95f52

Browse files
authored
Clean up framebuffer abstraction (#64)
1 parent 08ffc84 commit 5e95f52

File tree

2 files changed

+107
-35
lines changed
  • src/proto/console
  • uefi-test-runner/src/proto/console

2 files changed

+107
-35
lines changed

src/proto/console/gop.rs

+96-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
//! In theory, a buffer with a width of 640 should have (640 * 4) bytes per row,
2424
//! but in practice there might be some extra padding used for efficiency.
2525
26+
use core::marker::PhantomData;
27+
use core::mem;
2628
use core::ptr;
2729
use crate::{Completion, Result, Status};
2830

@@ -249,29 +251,20 @@ impl GraphicsOutput {
249251
*self.mode.info
250252
}
251253

252-
/// Returns the base pointer and size (in bytes) of the framebuffer
253-
///
254-
/// To use this pointer safely, a caller must...
255-
/// - Honor the pixel format specificed by the mode info
256-
/// - Keep pointer accesses in bound
257-
/// - Use volatile writes so that the compiler does not optimize out or
258-
/// aggressively reorder framebuffer accesses
259-
/// - Make sure that the pointer is not used beyond its validity limit
260-
///
261-
/// Although the UEFI spec makes no clear statement about framebuffer
262-
/// pointer validity, it seems reasonable to expect the framebuffer pointer
263-
/// to be valid until the next mode change. In the future, a safer interface
264-
/// may be introduced, which would enforce volatile writes and automatically
265-
/// check for dangling pointers using Rust's borrow checker.
266-
pub fn frame_buffer(&mut self) -> (*mut u8, usize) {
254+
/// Access the frame buffer directly
255+
pub fn frame_buffer(&mut self) -> FrameBuffer {
267256
assert!(
268257
self.mode.info.format != PixelFormat::BltOnly,
269258
"Cannot access the framebuffer in a Blt-only mode"
270259
);
271-
let data = self.mode.fb_address as *mut u8;
272-
let len = self.mode.fb_size;
260+
let base = self.mode.fb_address as *mut u8;
261+
let size = self.mode.fb_size;
273262

274-
(data, len)
263+
FrameBuffer {
264+
base,
265+
size,
266+
_lifetime: PhantomData,
267+
}
275268
}
276269
}
277270

@@ -519,3 +512,88 @@ pub enum BltOp<'a> {
519512
dims: (usize, usize),
520513
},
521514
}
515+
516+
/// Direct access to a memory-mapped frame buffer
517+
pub struct FrameBuffer<'a> {
518+
base: *mut u8,
519+
size: usize,
520+
_lifetime: PhantomData<&'a mut u8>,
521+
}
522+
523+
impl<'a> FrameBuffer<'a> {
524+
/// Access the raw framebuffer pointer
525+
///
526+
/// To use this pointer safely and correctly, you must...
527+
/// - Honor the pixel format and stride specified by the mode info
528+
/// - Keep memory accesses in bound
529+
/// - Use volatile reads and writes
530+
/// - Make sure that the pointer does not outlive the FrameBuffer
531+
pub fn as_mut_ptr(&mut self) -> *mut u8 {
532+
self.base
533+
}
534+
535+
/// Query the framebuffer size in bytes
536+
pub fn size(&self) -> usize {
537+
self.size
538+
}
539+
540+
/// Modify the i-th byte of the frame buffer
541+
///
542+
/// This operation is unsafe because...
543+
/// - You must honor the pixel format and stride specified by the mode info
544+
/// - There is no bound checking on memory accesses in release mode
545+
#[inline]
546+
pub unsafe fn write_byte(&mut self, index: usize, value: u8) {
547+
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
548+
self.base.add(index).write_volatile(value)
549+
}
550+
551+
/// Read the i-th byte of the frame buffer
552+
///
553+
/// This operation is unsafe because...
554+
/// - You must honor the pixel format and stride specified by the mode info
555+
/// - There is no bound checking on memory accesses in release mode
556+
#[inline]
557+
pub unsafe fn read_byte(&self, index: usize) -> u8 {
558+
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
559+
self.base.add(index).read_volatile()
560+
}
561+
562+
/// Write a value in the frame buffer, starting at the i-th byte
563+
///
564+
/// We only recommend using this method with [u8; N] arrays. Once Rust has
565+
/// const generics, it will be deprecated and replaced with a write_bytes()
566+
/// method that only accepts [u8; N] input.
567+
///
568+
/// This operation is unsafe because...
569+
/// - It is your reponsibility to make sure that the value type makes sense
570+
/// - You must honor the pixel format and stride specified by the mode info
571+
/// - There is no bound checking on memory accesses in release mode
572+
#[inline]
573+
pub unsafe fn write_value<T>(&mut self, index: usize, value: T) {
574+
debug_assert!(
575+
index.saturating_add(mem::size_of::<T>()) <= self.size,
576+
"Frame buffer accessed out of bounds"
577+
);
578+
(self.base.add(index) as *mut T).write_volatile(value)
579+
}
580+
581+
/// Read a value from the frame buffer, starting at the i-th byte
582+
///
583+
/// We only recommend using this method with [u8; N] arrays. Once Rust has
584+
/// const generics, it will be deprecated and replaced with a read_bytes()
585+
/// method that only accepts [u8; N] input.
586+
///
587+
/// This operation is unsafe because...
588+
/// - It is your reponsibility to make sure that the value type makes sense
589+
/// - You must honor the pixel format and stride specified by the mode info
590+
/// - There is no bound checking on memory accesses in release mode
591+
#[inline]
592+
pub unsafe fn read_value<T>(&self, index: usize) -> T {
593+
debug_assert!(
594+
index.saturating_add(mem::size_of::<T>()) <= self.size,
595+
"Frame buffer accessed out of bounds"
596+
);
597+
(self.base.add(index) as *const T).read_volatile()
598+
}
599+
}

uefi-test-runner/src/proto/console/gop.rs

+11-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use uefi::prelude::*;
2-
use uefi::proto::console::gop::{BltOp, BltPixel, GraphicsOutput, PixelFormat};
2+
use uefi::proto::console::gop::{BltOp, BltPixel, FrameBuffer, GraphicsOutput, PixelFormat};
33
use uefi::table::boot::BootServices;
44
use uefi_exts::BootServicesExt;
55

@@ -54,20 +54,14 @@ fn draw_fb(gop: &mut GraphicsOutput) {
5454
let stride = mi.stride();
5555
let (width, height) = mi.resolution();
5656

57-
let (fb_base, _fb_size) = gop.frame_buffer();
57+
let mut fb = gop.frame_buffer();
5858

59-
type PixelWriter = unsafe fn(*mut u8, [u8; 3]);
60-
unsafe fn write_pixel_rgb(pixel_base: *mut u8, rgb: [u8; 3]) {
61-
let [r, g, b] = rgb;
62-
pixel_base.add(0).write_volatile(r);
63-
pixel_base.add(1).write_volatile(g);
64-
pixel_base.add(2).write_volatile(b);
59+
type PixelWriter = unsafe fn(&mut FrameBuffer, usize, [u8; 3]);
60+
unsafe fn write_pixel_rgb(fb: &mut FrameBuffer, pixel_base: usize, rgb: [u8; 3]) {
61+
fb.write_value(pixel_base, rgb);
6562
};
66-
unsafe fn write_pixel_bgr(pixel_base: *mut u8, rgb: [u8; 3]) {
67-
let [r, g, b] = rgb;
68-
pixel_base.add(0).write_volatile(b);
69-
pixel_base.add(1).write_volatile(g);
70-
pixel_base.add(2).write_volatile(r);
63+
unsafe fn write_pixel_bgr(fb: &mut FrameBuffer, pixel_base: usize, rgb: [u8; 3]) {
64+
fb.write_value(pixel_base, [rgb[2], rgb[1], rgb[0]]);
7165
};
7266
let write_pixel: PixelWriter = match mi.pixel_format() {
7367
PixelFormat::RGB => write_pixel_rgb,
@@ -78,15 +72,15 @@ fn draw_fb(gop: &mut GraphicsOutput) {
7872
}
7973
};
8074

81-
let fill_rectangle = |(x1, y1), (x2, y2), color| {
75+
let mut fill_rectangle = |(x1, y1), (x2, y2), color| {
8276
assert!((x1 < width) && (x2 < width), "Bad X coordinate");
8377
assert!((y1 < height) && (y2 < height), "Bad Y coordinate");
8478
for row in y1..y2 {
8579
for column in x1..x2 {
8680
unsafe {
87-
let index = (row * stride) + column;
88-
let pixel_base = fb_base.add(4 * index);
89-
write_pixel(pixel_base, color);
81+
let pixel_index = (row * stride) + column;
82+
let pixel_base = 4 * pixel_index;
83+
write_pixel(&mut fb, pixel_base, color);
9084
}
9185
}
9286
}

0 commit comments

Comments
 (0)