use crate::proto::Protocol;
use crate::{unsafe_guid, Completion, Result, Status};
use core::marker::PhantomData;
use core::mem;
use core::ptr;
#[repr(C)]
#[unsafe_guid("9042a9de-23dc-4a38-96fb-7aded080516a")]
#[derive(Protocol)]
pub struct GraphicsOutput<'boot> {
query_mode: extern "efiapi" fn(
&GraphicsOutput,
mode: u32,
info_sz: &mut usize,
&mut *const ModeInfo,
) -> Status,
set_mode: extern "efiapi" fn(&mut GraphicsOutput, mode: u32) -> Status,
#[allow(clippy::type_complexity)]
blt: unsafe extern "efiapi" fn(
this: &mut GraphicsOutput,
buffer: *mut BltPixel,
op: u32,
source_x: usize,
source_y: usize,
dest_x: usize,
dest_y: usize,
width: usize,
height: usize,
stride: usize,
) -> Status,
mode: &'boot ModeData<'boot>,
}
impl<'boot> GraphicsOutput<'boot> {
fn query_mode(&self, index: u32) -> Result<Mode> {
let mut info_sz = 0;
let mut info = ptr::null();
(self.query_mode)(self, index, &mut info_sz, &mut info).into_with_val(|| {
let info = unsafe { *info };
Mode {
index,
info_sz,
info,
}
})
}
pub fn modes(&'_ self) -> impl ExactSizeIterator<Item = Completion<Mode>> + '_ {
ModeIter {
gop: self,
current: 0,
max: self.mode.max_mode,
}
}
pub fn set_mode(&mut self, mode: &Mode) -> Result {
(self.set_mode)(self, mode.index).into()
}
pub fn blt(&mut self, op: BltOp) -> Result {
unsafe {
match op {
BltOp::VideoFill {
color,
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_framebuffer_region((dest_x, dest_y), (width, height));
(self.blt)(
self,
&color as *const _ as *mut _,
0,
0,
0,
dest_x,
dest_y,
width,
height,
0,
)
.into()
}
BltOp::VideoToBltBuffer {
buffer,
src: (src_x, src_y),
dest: dest_region,
dims: (width, height),
} => {
self.check_framebuffer_region((src_x, src_y), (width, height));
self.check_blt_buffer_region(dest_region, (width, height), buffer.len());
match dest_region {
BltRegion::Full => (self.blt)(
self,
buffer.as_mut_ptr(),
1,
src_x,
src_y,
0,
0,
width,
height,
0,
)
.into(),
BltRegion::SubRectangle {
coords: (dest_x, dest_y),
px_stride,
} => (self.blt)(
self,
buffer.as_mut_ptr(),
1,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
px_stride * core::mem::size_of::<BltPixel>(),
)
.into(),
}
}
BltOp::BufferToVideo {
buffer,
src: src_region,
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_blt_buffer_region(src_region, (width, height), buffer.len());
self.check_framebuffer_region((dest_x, dest_y), (width, height));
match src_region {
BltRegion::Full => (self.blt)(
self,
buffer.as_ptr() as *mut _,
2,
0,
0,
dest_x,
dest_y,
width,
height,
0,
)
.into(),
BltRegion::SubRectangle {
coords: (src_x, src_y),
px_stride,
} => (self.blt)(
self,
buffer.as_ptr() as *mut _,
2,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
px_stride * core::mem::size_of::<BltPixel>(),
)
.into(),
}
}
BltOp::VideoToVideo {
src: (src_x, src_y),
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_framebuffer_region((src_x, src_y), (width, height));
self.check_framebuffer_region((dest_x, dest_y), (width, height));
(self.blt)(
self,
ptr::null_mut(),
3,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
0,
)
.into()
}
}
}
}
fn check_framebuffer_region(&self, coords: (usize, usize), dims: (usize, usize)) {
let (width, height) = self.current_mode_info().resolution();
assert!(
coords.0.saturating_add(dims.0) <= width,
"Horizontal framebuffer coordinate out of bounds"
);
assert!(
coords.1.saturating_add(dims.1) <= height,
"Vertical framebuffer coordinate out of bounds"
);
}
fn check_blt_buffer_region(&self, region: BltRegion, dims: (usize, usize), buf_length: usize) {
match region {
BltRegion::Full => assert!(
dims.0.saturating_add(dims.1.saturating_mul(dims.0)) <= buf_length,
"BltBuffer access out of bounds"
),
BltRegion::SubRectangle {
coords: (x, y),
px_stride,
} => {
assert!(
x.saturating_add(dims.0) <= px_stride,
"Horizontal BltBuffer coordinate out of bounds"
);
assert!(
y.saturating_add(dims.1).saturating_mul(px_stride) <= buf_length,
"Vertical BltBuffer coordinate out of bounds"
);
}
}
}
pub fn current_mode_info(&self) -> ModeInfo {
*self.mode.info
}
pub fn frame_buffer(&mut self) -> FrameBuffer {
assert!(
self.mode.info.format != PixelFormat::BltOnly,
"Cannot access the framebuffer in a Blt-only mode"
);
let base = self.mode.fb_address as *mut u8;
let size = self.mode.fb_size;
FrameBuffer {
base,
size,
_lifetime: PhantomData,
}
}
}
#[repr(C)]
struct ModeData<'info> {
max_mode: u32,
mode: u32,
info: &'info ModeInfo,
info_sz: usize,
fb_address: u64,
fb_size: usize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
pub enum PixelFormat {
RGB = 0,
BGR,
Bitmask,
BltOnly,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct PixelBitmask {
pub red: u32,
pub green: u32,
pub blue: u32,
pub reserved: u32,
}
pub struct Mode {
index: u32,
info_sz: usize,
info: ModeInfo,
}
impl Mode {
pub fn info_size(&self) -> usize {
self.info_sz
}
pub fn info(&self) -> &ModeInfo {
&self.info
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct ModeInfo {
version: u32,
hor_res: u32,
ver_res: u32,
format: PixelFormat,
mask: PixelBitmask,
stride: u32,
}
impl ModeInfo {
pub fn resolution(&self) -> (usize, usize) {
(self.hor_res as usize, self.ver_res as usize)
}
pub fn pixel_format(&self) -> PixelFormat {
self.format
}
pub fn pixel_bitmask(&self) -> Option<PixelBitmask> {
match self.format {
PixelFormat::Bitmask => Some(self.mask),
_ => None,
}
}
pub fn stride(&self) -> usize {
self.stride as usize
}
}
struct ModeIter<'gop> {
gop: &'gop GraphicsOutput<'gop>,
current: u32,
max: u32,
}
impl<'gop> Iterator for ModeIter<'gop> {
type Item = Completion<Mode>;
fn next(&mut self) -> Option<Self::Item> {
let index = self.current;
if index < self.max {
self.current += 1;
self.gop.query_mode(index).ok().or_else(|| self.next())
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = (self.max - self.current) as usize;
(size, Some(size))
}
}
impl ExactSizeIterator for ModeIter<'_> {}
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct BltPixel {
pub blue: u8,
pub green: u8,
pub red: u8,
_reserved: u8,
}
impl BltPixel {
pub fn new(red: u8, green: u8, blue: u8) -> Self {
Self {
red,
green,
blue,
_reserved: 0,
}
}
}
impl From<u32> for BltPixel {
fn from(color: u32) -> Self {
Self {
blue: (color & 0x00_00_FF) as u8,
green: ((color & 0x00_FF_00) >> 8) as u8,
red: ((color & 0xFF_00_00) >> 16) as u8,
_reserved: 0,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum BltRegion {
Full,
SubRectangle {
coords: (usize, usize),
px_stride: usize,
},
}
#[derive(Debug)]
pub enum BltOp<'buf> {
VideoFill {
color: BltPixel,
dest: (usize, usize),
dims: (usize, usize),
},
VideoToBltBuffer {
buffer: &'buf mut [BltPixel],
src: (usize, usize),
dest: BltRegion,
dims: (usize, usize),
},
BufferToVideo {
buffer: &'buf [BltPixel],
src: BltRegion,
dest: (usize, usize),
dims: (usize, usize),
},
VideoToVideo {
src: (usize, usize),
dest: (usize, usize),
dims: (usize, usize),
},
}
pub struct FrameBuffer<'gop> {
base: *mut u8,
size: usize,
_lifetime: PhantomData<&'gop mut u8>,
}
impl<'gop> FrameBuffer<'gop> {
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.base
}
pub fn size(&self) -> usize {
self.size
}
#[inline]
pub unsafe fn write_byte(&mut self, index: usize, value: u8) {
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
self.base.add(index).write_volatile(value)
}
#[inline]
pub unsafe fn read_byte(&self, index: usize) -> u8 {
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
self.base.add(index).read_volatile()
}
#[inline]
pub unsafe fn write_value<T>(&mut self, index: usize, value: T) {
debug_assert!(
index.saturating_add(mem::size_of::<T>()) <= self.size,
"Frame buffer accessed out of bounds"
);
(self.base.add(index) as *mut T).write_volatile(value)
}
#[inline]
pub unsafe fn read_value<T>(&self, index: usize) -> T {
debug_assert!(
index.saturating_add(mem::size_of::<T>()) <= self.size,
"Frame buffer accessed out of bounds"
);
(self.base.add(index) as *const T).read_volatile()
}
}