springboard_api/
config.rs

1#![allow(deprecated)]
2
3/// Allows configuring the bootloader behavior.
4///
5/// TODO: describe use together with `entry_point` macro
6/// TODO: example
7#[derive(Debug, PartialEq, Eq, Clone, Copy)]
8#[non_exhaustive]
9pub struct BootloaderConfig {
10   /// The version of the bootloader API.
11   ///
12   /// Automatically generated from the crate version. Checked on deserialization to
13   /// ensure that the kernel and bootloader use the same API version, i.e. the same config
14   /// and boot info format.
15   pub version: ApiVersion,
16
17   /// Configuration for (optional) page table mappings created by the bootloader.
18   pub mappings: Mappings,
19
20   /// The size of the stack that the bootloader should allocate for the kernel (in bytes).
21   ///
22   /// The bootloader starts the kernel with a valid stack pointer. This setting defines
23   /// the stack size that the bootloader should allocate and map.
24   ///
25   /// The stack is created with a additional guard page, so a stack overflow will lead to
26   /// a page fault.
27   pub kernel_stack_size: u64,
28
29   /// Configuration for the frame buffer that can be used by the kernel to display pixels
30   /// on the screen.
31   #[deprecated(
32   since = "0.11.1",
33   note = "The frame buffer is now configured through the `BootConfig` struct when creating the bootable disk image"
34   )]
35   pub frame_buffer: FrameBuffer,
36}
37
38impl BootloaderConfig {
39   pub(crate) const UUID: [u8; 16] = [
40      0x74, 0x3C, 0xA9, 0x61, 0x09, 0x36, 0x46, 0xA0, 0xBB, 0x55, 0x5C, 0x15, 0x89, 0x15, 0x25,
41      0x3D,
42   ];
43   #[doc(hidden)]
44   pub const SERIALIZED_LEN: usize = 124;
45
46   /// Creates a new default configuration with the following values:
47   ///
48   /// - `kernel_stack_size`: 80kiB
49   /// - `mappings`: See [`Mappings::new_default()`]
50   pub const fn new_default() -> Self {
51      Self {
52         kernel_stack_size: 80 * 1024,
53         version: ApiVersion::new_default(),
54         mappings: Mappings::new_default(),
55         frame_buffer: FrameBuffer::new_default(),
56      }
57   }
58
59   /// Serializes the configuration to a byte array.
60   ///
61   /// This is used by the [`crate::entry_point`] macro to store the configuration in a
62   /// dedicated section in the resulting ELF file.
63   pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] {
64      let Self {
65         version,
66         mappings,
67         kernel_stack_size,
68         frame_buffer
69      } = self;
70      let ApiVersion {
71         version_major,
72         version_minor,
73         version_patch,
74         pre_release,
75      } = version;
76      let Mappings {
77         kernel_stack,
78         boot_info,
79         framebuffer,
80         physical_memory,
81         page_table_recursive,
82         aslr,
83         dynamic_range_start,
84         dynamic_range_end,
85         ramdisk_memory,
86      } = mappings;
87      let FrameBuffer {
88         minimum_framebuffer_height,
89         minimum_framebuffer_width,
90      } = frame_buffer;
91
92      let version = {
93         let one = concat_2_2(version_major.to_le_bytes(), version_minor.to_le_bytes());
94         let two = concat_2_1(version_patch.to_le_bytes(), [*pre_release as u8]);
95         concat_4_3(one, two)
96      };
97      let buf = concat_16_7(Self::UUID, version);
98      let buf = concat_23_8(buf, kernel_stack_size.to_le_bytes());
99
100      let buf = concat_31_9(buf, kernel_stack.serialize());
101      let buf = concat_40_9(buf, boot_info.serialize());
102      let buf = concat_49_9(buf, framebuffer.serialize());
103
104      let buf = concat_58_10(
105         buf,
106         match physical_memory {
107            Option::None => [0; 10],
108            Option::Some(m) => concat_1_9([1], m.serialize()),
109         },
110      );
111      let buf = concat_68_10(
112         buf,
113         match page_table_recursive {
114            Option::None => [0; 10],
115            Option::Some(m) => concat_1_9([1], m.serialize()),
116         },
117      );
118      let buf = concat_78_1(buf, [(*aslr) as u8]);
119      let buf = concat_79_9(
120         buf,
121         match dynamic_range_start {
122            Option::None => [0; 9],
123            Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
124         },
125      );
126      let buf = concat_88_9(
127         buf,
128         match dynamic_range_end {
129            Option::None => [0; 9],
130            Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
131         },
132      );
133
134      let buf = concat_97_9(buf, ramdisk_memory.serialize());
135
136      let buf = concat_106_9(
137         buf,
138         match minimum_framebuffer_height {
139            Option::None => [0; 9],
140            Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
141         },
142      );
143
144      concat_115_9(
145         buf,
146         match minimum_framebuffer_width {
147            Option::None => [0; 9],
148            Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()),
149         },
150      )
151   }
152
153   /// Tries to deserialize a config byte array that was created using [`Self::serialize`].
154   ///
155   /// This is used by the bootloader to deserialize the configuration given in the kernel's
156   /// ELF file.
157   ///
158   /// TODO: return error enum
159   pub fn deserialize(serialized: &[u8]) -> Result<Self, &'static str> {
160      if serialized.len() != Self::SERIALIZED_LEN {
161         return Err("invalid len");
162      }
163
164      let s = serialized;
165
166      let (uuid, s) = split_array_ref(s);
167      if uuid != &Self::UUID {
168         return Err("invalid UUID");
169      }
170
171      let (version, s) = {
172         let (&major, s) = split_array_ref(s);
173         let (&minor, s) = split_array_ref(s);
174         let (&patch, s) = split_array_ref(s);
175         let (&pre, s) = split_array_ref(s);
176         let pre = match pre {
177            [0] => false,
178            [1] => true,
179            _ => return Err("invalid pre version"),
180         };
181
182         let version = ApiVersion {
183            version_major: u16::from_le_bytes(major),
184            version_minor: u16::from_le_bytes(minor),
185            version_patch: u16::from_le_bytes(patch),
186            pre_release: pre,
187         };
188         (version, s)
189      };
190
191      // TODO check version against this crate version -> error if they're different
192
193      let (&kernel_stack_size, s) = split_array_ref(s);
194
195      let (mappings, s) = {
196         let (&kernel_stack, s) = split_array_ref(s);
197         let (&boot_info, s) = split_array_ref(s);
198         let (&framebuffer, s) = split_array_ref(s);
199         let (&physical_memory_some, s) = split_array_ref(s);
200         let (&physical_memory, s) = split_array_ref(s);
201         let (&page_table_recursive_some, s) = split_array_ref(s);
202         let (&page_table_recursive, s) = split_array_ref(s);
203         let (&[alsr], s) = split_array_ref(s);
204         let (&dynamic_range_start_some, s) = split_array_ref(s);
205         let (&dynamic_range_start, s) = split_array_ref(s);
206         let (&dynamic_range_end_some, s) = split_array_ref(s);
207         let (&dynamic_range_end, s) = split_array_ref(s);
208         let (&ramdisk_memory, s) = split_array_ref(s);
209
210         let mappings = Mappings {
211            kernel_stack: Mapping::deserialize(&kernel_stack)?,
212            boot_info: Mapping::deserialize(&boot_info)?,
213            framebuffer: Mapping::deserialize(&framebuffer)?,
214            physical_memory: match physical_memory_some {
215               [0] if physical_memory == [0; 9] => Option::None,
216               [1] => Option::Some(Mapping::deserialize(&physical_memory)?),
217               _ => return Err("invalid phys memory value"),
218            },
219            page_table_recursive: match page_table_recursive_some {
220               [0] if page_table_recursive == [0; 9] => Option::None,
221               [1] => Option::Some(Mapping::deserialize(&page_table_recursive)?),
222               _ => return Err("invalid page table recursive value"),
223            },
224            aslr: match alsr {
225               1 => true,
226               0 => false,
227               _ => return Err("invalid aslr value"),
228            },
229            dynamic_range_start: match dynamic_range_start_some {
230               [0] if dynamic_range_start == [0; 8] => Option::None,
231               [1] => Option::Some(u64::from_le_bytes(dynamic_range_start)),
232               _ => return Err("invalid dynamic range start value"),
233            },
234            dynamic_range_end: match dynamic_range_end_some {
235               [0] if dynamic_range_end == [0; 8] => Option::None,
236               [1] => Option::Some(u64::from_le_bytes(dynamic_range_end)),
237               _ => return Err("invalid dynamic range end value"),
238            },
239            ramdisk_memory: Mapping::deserialize(&ramdisk_memory)?,
240         };
241         (mappings, s)
242      };
243
244      let (frame_buffer, s) = {
245         let (&min_framebuffer_height_some, s) = split_array_ref(s);
246         let (&min_framebuffer_height, s) = split_array_ref(s);
247         let (&min_framebuffer_width_some, s) = split_array_ref(s);
248         let (&min_framebuffer_width, s) = split_array_ref(s);
249
250         let frame_buffer = FrameBuffer {
251            minimum_framebuffer_height: match min_framebuffer_height_some {
252               [0] if min_framebuffer_height == [0; 8] => Option::None,
253               [1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)),
254               _ => return Err("minimum_framebuffer_height invalid"),
255            },
256            minimum_framebuffer_width: match min_framebuffer_width_some {
257               [0] if min_framebuffer_width == [0; 8] => Option::None,
258               [1] => Option::Some(u64::from_le_bytes(min_framebuffer_width)),
259               _ => return Err("minimum_framebuffer_width invalid"),
260            },
261         };
262         (frame_buffer, s)
263      };
264
265      if !s.is_empty() {
266         return Err("unexpected rest");
267      }
268
269      Ok(Self {
270         version,
271         kernel_stack_size: u64::from_le_bytes(kernel_stack_size),
272         mappings,
273         frame_buffer,
274      })
275   }
276
277   #[cfg(test)]
278   fn random() -> Self {
279      Self {
280         version: ApiVersion::random(),
281         mappings: Mappings::random(),
282         kernel_stack_size: rand::random(),
283         frame_buffer: FrameBuffer::random(),
284      }
285   }
286}
287
288impl Default for BootloaderConfig {
289   fn default() -> Self {
290      Self::new_default()
291   }
292}
293
294/// A semver-compatible version.
295#[derive(Debug, PartialEq, Eq, Clone, Copy)]
296#[repr(C)]
297pub struct ApiVersion {
298   /// Bootloader version (major).
299   version_major: u16,
300   /// Bootloader version (minor).
301   version_minor: u16,
302   /// Bootloader version (patch).
303   version_patch: u16,
304   /// Whether the bootloader API version is a pre-release.
305   ///
306   /// We can't store the full prerelease string of the version number since it could be
307   /// arbitrarily long.
308   pre_release: bool,
309}
310
311impl ApiVersion {
312   pub(crate) const fn new_default() -> Self {
313      Self {
314         version_major: version_info::VERSION_MAJOR,
315         version_minor: version_info::VERSION_MINOR,
316         version_patch: version_info::VERSION_PATCH,
317         pre_release: version_info::VERSION_PRE,
318      }
319   }
320
321   #[cfg(test)]
322   fn random() -> ApiVersion {
323      Self {
324         version_major: rand::random(),
325         version_minor: rand::random(),
326         version_patch: rand::random(),
327         pre_release: rand::random(),
328      }
329   }
330
331   /// Returns the major version number.
332   pub fn version_major(&self) -> u16 {
333      self.version_major
334   }
335
336   /// Returns the minor version number.
337   pub fn version_minor(&self) -> u16 {
338      self.version_minor
339   }
340
341   /// Returns the patch version number.
342   pub fn version_patch(&self) -> u16 {
343      self.version_patch
344   }
345
346   /// Returns whether this version is a pre-release, e.g., an alpha version.
347   pub fn pre_release(&self) -> bool {
348      self.pre_release
349   }
350}
351
352impl Default for ApiVersion {
353   fn default() -> Self {
354      Self::new_default()
355   }
356}
357
358/// Allows to configure the virtual memory mappings created by the bootloader.
359#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
360#[non_exhaustive]
361pub struct Mappings {
362   /// Configures how the kernel stack should be mapped.
363   ///
364   /// If a fixed address is set, it must be page aligned.
365   ///
366   /// Note that the first page of the kernel stack is intentionally left unmapped
367   /// to act as a guard page. This ensures that a page fault occurs on a stack
368   /// overflow. For example, setting the kernel stack address to
369   /// `FixedAddress(0xf_0000_0000)` will result in a guard page at address
370   /// `0xf_0000_0000` and the kernel stack starting at address `0xf_0000_1000`.
371   pub kernel_stack: Mapping,
372   /// Specifies where the [`crate::BootInfo`] struct should be placed in virtual memory.
373   pub boot_info: Mapping,
374   /// Specifies the mapping of the frame buffer memory region.
375   pub framebuffer: Mapping,
376   /// The bootloader supports to map the whole physical memory into the virtual address
377   /// space at some offset. This is useful for accessing and modifying the page tables set
378   /// up by the bootloader.
379   ///
380   /// Defaults to `None`, i.e. no mapping of the physical memory.
381   pub physical_memory: Option<Mapping>,
382   /// As an alternative to mapping the whole physical memory (see [`Self::physical_memory`]),
383   /// the bootloader also has support for setting up a
384   /// [recursive level 4 page table](https://os.phil-opp.com/paging-implementation/#recursive-page-tables).
385   ///
386   /// Defaults to `None`, i.e. no recursive mapping.
387   pub page_table_recursive: Option<Mapping>,
388   /// Whether to randomize non-statically configured addresses.
389   /// The kernel base address will be randomized when it's compiled as
390   /// a position independent executable.
391   ///
392   /// Defaults to `false`.
393   pub aslr: bool,
394   /// The lowest virtual address for dynamic addresses.
395   ///
396   /// Defaults to `0`.
397   pub dynamic_range_start: Option<u64>,
398   /// The highest virtual address for dynamic addresses.
399   ///
400   /// Defaults to `0xffff_ffff_ffff_f000`.
401   pub dynamic_range_end: Option<u64>,
402   /// Virtual address to map ramdisk image, if present on disk
403   /// Defaults to dynamic
404   pub ramdisk_memory: Mapping,
405}
406
407impl Mappings {
408   /// Creates a new mapping configuration with dynamic mapping for kernel, boot info and
409   /// frame buffer. Neither physical memory mapping nor recursive page table creation are
410   /// enabled.
411   pub const fn new_default() -> Self {
412      Self {
413         kernel_stack: Mapping::new_default(),
414         boot_info: Mapping::new_default(),
415         framebuffer: Mapping::new_default(),
416         physical_memory: Option::None,
417         page_table_recursive: Option::None,
418         aslr: false,
419         dynamic_range_start: None,
420         dynamic_range_end: None,
421         ramdisk_memory: Mapping::new_default(),
422      }
423   }
424
425   #[cfg(test)]
426   fn random() -> Mappings {
427      let phys = rand::random();
428      let recursive = rand::random();
429      Self {
430         kernel_stack: Mapping::random(),
431         boot_info: Mapping::random(),
432         framebuffer: Mapping::random(),
433         physical_memory: if phys {
434            Option::Some(Mapping::random())
435         } else {
436            Option::None
437         },
438         page_table_recursive: if recursive {
439            Option::Some(Mapping::random())
440         } else {
441            Option::None
442         },
443         aslr: rand::random(),
444         dynamic_range_start: if rand::random() {
445            Option::Some(rand::random())
446         } else {
447            Option::None
448         },
449         dynamic_range_end: if rand::random() {
450            Option::Some(rand::random())
451         } else {
452            Option::None
453         },
454         ramdisk_memory: Mapping::random(),
455      }
456   }
457}
458
459/// Specifies how the bootloader should map a memory region into the virtual address space.
460#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
461pub enum Mapping {
462   /// Look for an unused virtual memory region at runtime.
463   Dynamic,
464   /// Try to map the region at the given virtual address.
465   ///
466   /// The given virtual address must be page-aligned.
467   ///
468   /// This setting can lead to runtime boot errors if the given address is not aligned,
469   /// already in use, or invalid for other reasons.
470   FixedAddress(u64),
471}
472
473impl Mapping {
474   /// Creates a new [`Mapping::Dynamic`].
475   ///
476   /// This function has identical results as [`Default::default`], the only difference is
477   /// that this is a `const` function.
478   pub const fn new_default() -> Self {
479      Self::Dynamic
480   }
481
482   #[cfg(test)]
483   fn random() -> Mapping {
484      let fixed = rand::random();
485      if fixed {
486         Self::Dynamic
487      } else {
488         Self::FixedAddress(rand::random())
489      }
490   }
491
492   const fn serialize(&self) -> [u8; 9] {
493      match self {
494         Mapping::Dynamic => [0; 9],
495         Mapping::FixedAddress(addr) => concat_1_8([1], addr.to_le_bytes()),
496      }
497   }
498
499   fn deserialize(serialized: &[u8; 9]) -> Result<Self, &'static str> {
500      let (&variant, s) = split_array_ref(serialized);
501      let (&addr, s) = split_array_ref(s);
502      if !s.is_empty() {
503         return Err("invalid mapping format");
504      }
505
506      match variant {
507         [0] if addr == [0; 8] => Ok(Mapping::Dynamic),
508         [1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))),
509         _ => Err("invalid mapping value"),
510      }
511   }
512}
513
514impl Default for Mapping {
515   fn default() -> Self {
516      Self::new_default()
517   }
518}
519
520/// Configuration for the frame buffer used for graphical output.
521#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
522#[non_exhaustive]
523pub struct FrameBuffer {
524   /// Instructs the bootloader to set up a framebuffer format that has at least the given height.
525   ///
526   /// If this is not possible, the bootloader will fall back to a smaller format.
527   pub minimum_framebuffer_height: Option<u64>,
528   /// Instructs the bootloader to set up a framebuffer format that has at least the given width.
529   ///
530   /// If this is not possible, the bootloader will fall back to a smaller format.
531   pub minimum_framebuffer_width: Option<u64>,
532}
533
534impl FrameBuffer {
535   /// Creates a default configuration without any requirements.
536   pub const fn new_default() -> Self {
537      Self {
538         minimum_framebuffer_height: Option::None,
539         minimum_framebuffer_width: Option::None,
540      }
541   }
542
543   #[cfg(test)]
544   fn random() -> FrameBuffer {
545      Self {
546         minimum_framebuffer_height: if rand::random() {
547            Option::Some(rand::random())
548         } else {
549            Option::None
550         },
551         minimum_framebuffer_width: if rand::random() {
552            Option::Some(rand::random())
553         } else {
554            Option::None
555         },
556      }
557   }
558}
559
560/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677
561///
562/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized,
563/// see https://github.com/rust-lang/rust/issues/90091
564fn split_array_ref<const N: usize, T>(slice: &[T]) -> (&[T; N], &[T]) {
565   let (a, b) = slice.split_at(N);
566   // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at)
567   unsafe { (&*(a.as_ptr() as *const [T; N]), b) }
568}
569
570#[cfg(test)]
571mod tests {
572   use super::*;
573
574   #[test]
575   fn mapping_serde() {
576      for _ in 0..10000 {
577         let config = Mapping::random();
578         assert_eq!(Mapping::deserialize(&config.serialize()), Ok(config));
579      }
580   }
581
582   #[test]
583   fn config_serde() {
584      for _ in 0..10000 {
585         let config = BootloaderConfig::random();
586         assert_eq!(
587            BootloaderConfig::deserialize(&config.serialize()),
588            Ok(config)
589         );
590      }
591   }
592}
593
594// IMPORTS //
595
596use {
597   crate::{concat::*, version_info}
598};