multiboot2/
framebuffer.rs

1//! Module for [`FramebufferTag`].
2
3use crate::TagType;
4use crate::tag::TagHeader;
5use core::fmt::Debug;
6use core::mem;
7use core::slice;
8use multiboot2_common::{MaybeDynSized, Tag};
9use thiserror::Error;
10#[cfg(feature = "builder")]
11use {alloc::boxed::Box, multiboot2_common::new_boxed};
12
13/// Helper struct to read bytes from a raw pointer and increase the pointer
14/// automatically.
15struct Reader<'a> {
16    buffer: &'a [u8],
17    off: usize,
18}
19
20impl<'a> Reader<'a> {
21    const fn new(buffer: &'a [u8]) -> Self {
22        Self { buffer, off: 0 }
23    }
24
25    /// Reads the next [`u8`] from the buffer and updates the internal pointer.
26    ///
27    /// # Panic
28    ///
29    /// Panics if the index is out of bounds.
30    fn read_next_u8(&mut self) -> u8 {
31        let val = self
32            .buffer
33            .get(self.off)
34            .cloned()
35            // This is not a solution I'm proud of, but at least it is safe.
36            // The whole framebuffer tag code originally is not from me.
37            // I hope someone from the community wants to improve this overall
38            // functionality someday.
39            .expect("Embedded framebuffer info should be properly sized and available");
40        self.off += 1;
41        val
42    }
43
44    /// Reads the next [`u16`] from the buffer and updates the internal pointer.
45    ///
46    /// # Panic
47    ///
48    /// Panics if the index is out of bounds.
49    fn read_next_u16(&mut self) -> u16 {
50        let u16_lo = self.read_next_u8() as u16;
51        let u16_hi = self.read_next_u8() as u16;
52        (u16_hi << 8) | u16_lo
53    }
54
55    const fn current_ptr(&self) -> *const u8 {
56        unsafe { self.buffer.as_ptr().add(self.off) }
57    }
58}
59
60/// The VBE Framebuffer information tag.
61#[derive(ptr_meta::Pointee, Eq)]
62#[repr(C, align(8))]
63pub struct FramebufferTag {
64    header: TagHeader,
65
66    /// Contains framebuffer physical address.
67    ///
68    /// This field is 64-bit wide but bootloader should set it under 4GiB if
69    /// possible for compatibility with payloads which aren’t aware of PAE or
70    /// amd64.
71    address: u64,
72
73    /// Contains the pitch in bytes.
74    pitch: u32,
75
76    /// Contains framebuffer width in pixels.
77    width: u32,
78
79    /// Contains framebuffer height in pixels.
80    height: u32,
81
82    /// Contains number of bits per pixel.
83    bpp: u8,
84
85    /// The type of framebuffer. See [`FramebufferTypeId`].
86    // TODO: Strictly speaking this causes UB for invalid values. However, no
87    //  sane bootloader puts something illegal there at the moment. When we
88    //  refactor this (newtype pattern?), we should also streamline other
89    //  parts in the code base accordingly.
90    framebuffer_type: FramebufferTypeId,
91
92    _padding: u16,
93
94    /// This optional data and its meaning depend on the [`FramebufferTypeId`].
95    buffer: [u8],
96}
97
98impl FramebufferTag {
99    /// Constructs a new tag.
100    #[cfg(feature = "builder")]
101    #[must_use]
102    pub fn new(
103        address: u64,
104        pitch: u32,
105        width: u32,
106        height: u32,
107        bpp: u8,
108        buffer_type: FramebufferType,
109    ) -> Box<Self> {
110        let header = TagHeader::new(Self::ID, 0);
111        let address = address.to_ne_bytes();
112        let pitch = pitch.to_ne_bytes();
113        let width = width.to_ne_bytes();
114        let height = height.to_ne_bytes();
115        let buffer_type_id = buffer_type.id();
116        let padding = [0; 2];
117        let optional_buffer = buffer_type.serialize();
118        new_boxed(
119            header,
120            &[
121                &address,
122                &pitch,
123                &width,
124                &height,
125                &[bpp],
126                &[buffer_type_id as u8],
127                &padding,
128                &optional_buffer,
129            ],
130        )
131    }
132
133    /// Contains framebuffer physical address.
134    ///
135    /// This field is 64-bit wide but bootloader should set it under 4GiB if
136    /// possible for compatibility with payloads which aren’t aware of PAE or
137    /// amd64.
138    #[must_use]
139    pub const fn address(&self) -> u64 {
140        self.address
141    }
142
143    /// Contains the pitch in bytes.
144    #[must_use]
145    pub const fn pitch(&self) -> u32 {
146        self.pitch
147    }
148
149    /// Contains framebuffer width in pixels.
150    #[must_use]
151    pub const fn width(&self) -> u32 {
152        self.width
153    }
154
155    /// Contains framebuffer height in pixels.
156    #[must_use]
157    pub const fn height(&self) -> u32 {
158        self.height
159    }
160
161    /// Contains number of bits per pixel.
162    #[must_use]
163    pub const fn bpp(&self) -> u8 {
164        self.bpp
165    }
166
167    /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`.
168    pub fn buffer_type(&self) -> Result<FramebufferType, UnknownFramebufferType> {
169        let mut reader = Reader::new(&self.buffer);
170
171        // TODO: We should use the newtype pattern instead or so to properly
172        //  solve this.
173        let fb_type_raw = self.framebuffer_type as u8;
174        let fb_type = FramebufferTypeId::try_from(fb_type_raw)?;
175
176        match fb_type {
177            FramebufferTypeId::Indexed => {
178                // TODO we can create a struct for this and implement
179                //  DynSizedStruct for it to leverage the already existing
180                //  functionality
181                let num_colors = reader.read_next_u16();
182
183                let palette = {
184                    // Ensure the slice can be created without causing UB
185                    assert_eq!(mem::size_of::<FramebufferColor>(), 3);
186
187                    unsafe {
188                        slice::from_raw_parts(
189                            reader.current_ptr().cast::<FramebufferColor>(),
190                            num_colors as usize,
191                        )
192                    }
193                };
194                Ok(FramebufferType::Indexed { palette })
195            }
196            FramebufferTypeId::RGB => {
197                let red_pos = reader.read_next_u8(); // These refer to the bit positions of the LSB of each field
198                let red_mask = reader.read_next_u8(); // And then the length of the field from LSB to MSB
199                let green_pos = reader.read_next_u8();
200                let green_mask = reader.read_next_u8();
201                let blue_pos = reader.read_next_u8();
202                let blue_mask = reader.read_next_u8();
203                Ok(FramebufferType::RGB {
204                    red: FramebufferField {
205                        position: red_pos,
206                        size: red_mask,
207                    },
208                    green: FramebufferField {
209                        position: green_pos,
210                        size: green_mask,
211                    },
212                    blue: FramebufferField {
213                        position: blue_pos,
214                        size: blue_mask,
215                    },
216                })
217            }
218            FramebufferTypeId::Text => Ok(FramebufferType::Text),
219        }
220    }
221}
222
223impl MaybeDynSized for FramebufferTag {
224    type Header = TagHeader;
225
226    const BASE_SIZE: usize = mem::size_of::<TagHeader>()
227        + mem::size_of::<u64>()
228        + 3 * mem::size_of::<u32>()
229        + 2 * mem::size_of::<u8>()
230        + mem::size_of::<u16>();
231
232    fn dst_len(header: &TagHeader) -> usize {
233        assert!(header.size as usize >= Self::BASE_SIZE);
234        header.size as usize - Self::BASE_SIZE
235    }
236}
237
238impl Tag for FramebufferTag {
239    type IDType = TagType;
240
241    const ID: TagType = TagType::Framebuffer;
242}
243
244impl Debug for FramebufferTag {
245    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246        f.debug_struct("FramebufferTag")
247            .field("typ", &self.header.typ)
248            .field("size", &self.header.size)
249            .field("buffer_type", &self.buffer_type())
250            .field("address", &self.address)
251            .field("pitch", &self.pitch)
252            .field("width", &self.width)
253            .field("height", &self.height)
254            .field("bpp", &self.bpp)
255            .finish()
256    }
257}
258
259impl PartialEq for FramebufferTag {
260    fn eq(&self, other: &Self) -> bool {
261        self.header == other.header
262            && self.address == { other.address }
263            && self.pitch == { other.pitch }
264            && self.width == { other.width }
265            && self.height == { other.height }
266            && self.bpp == { other.bpp }
267            && self.framebuffer_type == { other.framebuffer_type }
268            && self.buffer == other.buffer
269    }
270}
271
272/// ABI-compatible framebuffer type.
273#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
274#[repr(u8)]
275#[allow(clippy::upper_case_acronyms)]
276pub enum FramebufferTypeId {
277    Indexed = 0,
278    RGB = 1,
279    Text = 2,
280    // spec says: there may be more variants in the future
281}
282
283impl TryFrom<u8> for FramebufferTypeId {
284    type Error = UnknownFramebufferType;
285
286    fn try_from(value: u8) -> Result<Self, Self::Error> {
287        match value {
288            0 => Ok(Self::Indexed),
289            1 => Ok(Self::RGB),
290            2 => Ok(Self::Text),
291            val => Err(UnknownFramebufferType(val)),
292        }
293    }
294}
295
296impl From<FramebufferType<'_>> for FramebufferTypeId {
297    fn from(value: FramebufferType) -> Self {
298        match value {
299            FramebufferType::Indexed { .. } => Self::Indexed,
300            FramebufferType::RGB { .. } => Self::RGB,
301            FramebufferType::Text => Self::Text,
302        }
303    }
304}
305
306/// Structured accessory to the provided framebuffer type that is not ABI
307/// compatible.
308#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
309pub enum FramebufferType<'a> {
310    /// Indexed color.
311    Indexed {
312        #[allow(missing_docs)]
313        palette: &'a [FramebufferColor],
314    },
315
316    /// Direct RGB color.
317    #[allow(missing_docs)]
318    #[allow(clippy::upper_case_acronyms)]
319    RGB {
320        red: FramebufferField,
321        green: FramebufferField,
322        blue: FramebufferField,
323    },
324
325    /// EGA Text.
326    ///
327    /// In this case the framebuffer width and height are expressed in
328    /// characters and not in pixels.
329    ///
330    /// The bpp is equal 16 (16 bits per character) and pitch is expressed in bytes per text line.
331    Text,
332}
333
334impl FramebufferType<'_> {
335    #[must_use]
336    #[cfg(feature = "builder")]
337    const fn id(&self) -> FramebufferTypeId {
338        match self {
339            FramebufferType::Indexed { .. } => FramebufferTypeId::Indexed,
340            FramebufferType::RGB { .. } => FramebufferTypeId::RGB,
341            FramebufferType::Text => FramebufferTypeId::Text,
342        }
343    }
344
345    #[must_use]
346    #[cfg(feature = "builder")]
347    fn serialize(&self) -> alloc::vec::Vec<u8> {
348        let mut data = alloc::vec::Vec::new();
349        match self {
350            FramebufferType::Indexed { palette } => {
351                // TODO we can create a struct for this and implement
352                //  DynSizedStruct for it to leverage the already existing
353                //  functionality
354                let num_colors = palette.len() as u16;
355                data.extend(&num_colors.to_ne_bytes());
356                for color in *palette {
357                    let serialized_color = [color.red, color.green, color.blue];
358                    data.extend(&serialized_color);
359                }
360            }
361            FramebufferType::RGB { red, green, blue } => data.extend(&[
362                red.position,
363                red.size,
364                green.position,
365                green.size,
366                blue.position,
367                blue.size,
368            ]),
369            FramebufferType::Text => {}
370        }
371        data
372    }
373}
374
375/// An RGB color type field.
376#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
377#[repr(C)]
378pub struct FramebufferField {
379    /// Color field position.
380    pub position: u8,
381
382    /// Color mask size.
383    pub size: u8,
384}
385
386/// A framebuffer color descriptor in the palette.
387///
388/// On the ABI level, multiple values are consecutively without padding bytes.
389/// The spec is not precise in that regard, but looking at Limine's and GRUB's
390/// source code confirm that.
391#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
392#[repr(C)] // no align(8) here is correct
393pub struct FramebufferColor {
394    /// The Red component of the color.
395    pub red: u8,
396
397    /// The Green component of the color.
398    pub green: u8,
399
400    /// The Blue component of the color.
401    pub blue: u8,
402}
403
404/// Error when an unknown [`FramebufferTypeId`] is found.
405#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
406#[error("Unknown framebuffer type {0}")]
407pub struct UnknownFramebufferType(u8);
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412
413    // Compile time test
414    #[test]
415    fn test_size() {
416        assert_eq!(mem::size_of::<FramebufferColor>(), 3)
417    }
418
419    #[test]
420    #[cfg(feature = "builder")]
421    fn create_new() {
422        let tag = FramebufferTag::new(0x1000, 1, 1024, 1024, 8, FramebufferType::Text);
423        // Good test for Miri
424        dbg!(tag);
425
426        let tag = FramebufferTag::new(
427            0x1000,
428            1,
429            1024,
430            1024,
431            8,
432            FramebufferType::Indexed {
433                palette: &[
434                    FramebufferColor {
435                        red: 255,
436                        green: 255,
437                        blue: 255,
438                    },
439                    FramebufferColor {
440                        red: 127,
441                        green: 42,
442                        blue: 73,
443                    },
444                ],
445            },
446        );
447        // Good test for Miri
448        dbg!(tag);
449
450        let tag = FramebufferTag::new(
451            0x1000,
452            1,
453            1024,
454            1024,
455            8,
456            FramebufferType::RGB {
457                red: FramebufferField {
458                    position: 0,
459                    size: 0,
460                },
461                green: FramebufferField {
462                    position: 10,
463                    size: 20,
464                },
465                blue: FramebufferField {
466                    position: 30,
467                    size: 40,
468                },
469            },
470        );
471        // Good test for Miri
472        dbg!(tag);
473    }
474}