tartan_uefi/
lib.rs

1//! Support for Unified Extensible Firmware Interface
2//!
3//! See EDK `MdePkg/Include/Uefi/UefiSpec.h`
4
5#![no_std]
6#![feature(fn_traits)]
7#![warn(clippy::pedantic)]
8#![allow(clippy::must_use_candidate)]
9#![allow(clippy::non_ascii_literal)]
10#![allow(clippy::upper_case_acronyms)]
11
12extern crate alloc;
13
14use ::alloc::vec::Vec;
15use core::ffi::c_void;
16use core::fmt;
17use core::mem::{align_of, size_of};
18use core::slice;
19use crc_any::CRCu32;
20use tartan_bitfield::bitfield;
21use tartan_c_enum::c_enum;
22
23pub mod allocator;
24pub mod global;
25#[macro_use]
26pub mod io;
27
28
29/// Constant page size defined by UEFI specification for [`BootServices::allocate_pages`].
30pub const PAGE_SIZE: usize = 4096;
31
32
33#[repr(transparent)]
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Handle(usize);
36
37impl Handle {
38    pub const NULL: Handle = Handle(0);
39}
40
41
42c_enum! {
43    pub enum Revision(u32) {
44        V2_80 = (2 << 16) | 80,
45        V2_70 = (2 << 16) | 70,
46        V2_60 = (2 << 16) | 60,
47        V2_50 = (2 << 16) | 50,
48        V2_40 = (2 << 16) | 40,
49        V2_31 = (2 << 16) | 31,
50        V2_30 = (2 << 16) | 30,
51        V2_20 = (2 << 16) | 20,
52        V2_10 = (2 << 16) | 10,
53        V2_00 =  2 << 16      ,
54        V1_10 = (1 << 16) | 10,
55        V1_02 = (1 << 16) |  2,
56    }
57}
58
59impl Revision {
60    pub const LATEST: Revision = Revision::V2_80;
61
62    pub fn major_version(self) -> u16 {
63        #![allow(clippy::cast_possible_truncation)]
64        (self.0 >> 16) as u16
65    }
66
67    pub fn minor_version(self) -> u16 {
68        #![allow(clippy::cast_possible_truncation)]
69        (self.0 as u16) / 10
70    }
71
72    pub fn fix_version(self) -> u16 {
73        #![allow(clippy::cast_possible_truncation)]
74        (self.0 as u16) % 10
75    }
76}
77
78impl fmt::Display for Revision {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(
81            f,
82            "{}.{}.{}",
83            self.major_version(),
84            self.minor_version(),
85            self.fix_version(),
86        )
87    }
88}
89
90#[cfg(test)]
91mod test_revision {
92    use super::*;
93
94    #[test]
95    fn test_components() {
96        assert_eq!(Revision::V1_02.major_version(), 1);
97        assert_eq!(Revision::V1_02.minor_version(), 0);
98        assert_eq!(Revision::V1_02.fix_version(), 2);
99
100        assert_eq!(Revision::V2_31.major_version(), 2);
101        assert_eq!(Revision::V2_31.minor_version(), 3);
102        assert_eq!(Revision::V2_31.fix_version(), 1);
103    }
104}
105
106pub type Result = core::result::Result<Status, Status>;
107
108c_enum! {
109    pub enum Status(usize) {
110        // Appendix D
111        Success             = 0,
112
113        WarnUnknownGlyph    = 1,
114        WarnDeleteFailure   = 2,
115        WarnWriteFailure    = 3,
116        WarnBufferTooSmall  = 4,
117        WarnStaleData       = 5,
118        WarnFileSystem      = 6,
119        WarnResetRequired   = 7,
120
121        LoadError           = Status::ERROR_BIT | 1,
122        InvalidParameter    = Status::ERROR_BIT | 2,
123        Unsupported         = Status::ERROR_BIT | 3,
124        BadBufferSize       = Status::ERROR_BIT | 4,
125        BufferTooSmall      = Status::ERROR_BIT | 5,
126        NotReady            = Status::ERROR_BIT | 6,
127        DeviceError         = Status::ERROR_BIT | 7,
128        WriteProtected      = Status::ERROR_BIT | 8,
129        OutOfResources      = Status::ERROR_BIT | 9,
130        VolumeCorrupted     = Status::ERROR_BIT | 10,
131        VolumeFull          = Status::ERROR_BIT | 11,
132        NoMedia             = Status::ERROR_BIT | 12,
133        MediaChanged        = Status::ERROR_BIT | 13,
134        NotFound            = Status::ERROR_BIT | 14,
135        AccessDenied        = Status::ERROR_BIT | 15,
136        NoResponse          = Status::ERROR_BIT | 16,
137        NoMapping           = Status::ERROR_BIT | 17,
138        Timeout             = Status::ERROR_BIT | 18,
139        NotStarted          = Status::ERROR_BIT | 19,
140        AlreadyStarted      = Status::ERROR_BIT | 20,
141        Aborted             = Status::ERROR_BIT | 21,
142        ICMPError           = Status::ERROR_BIT | 22,
143        TFTPError           = Status::ERROR_BIT | 23,
144        ProtocolError       = Status::ERROR_BIT | 24,
145        IncompatibleVersion = Status::ERROR_BIT | 25,
146        SecurityViolation   = Status::ERROR_BIT | 26,
147        CRCError            = Status::ERROR_BIT | 27,
148        EndOfMedia          = Status::ERROR_BIT | 28,
149        EndOfFile           = Status::ERROR_BIT | 31,
150        InvalidLanguage     = Status::ERROR_BIT | 32,
151        CompromisedData     = Status::ERROR_BIT | 33,
152        HTTPError           = Status::ERROR_BIT | 35,
153    }
154}
155
156impl Status {
157    pub const ERROR_BIT: usize = 0x1_usize.reverse_bits(); // high bit
158
159    pub fn is_error(self) -> bool {
160        (self.0 & Status::ERROR_BIT) != 0
161    }
162
163    pub fn is_warning(self) -> bool {
164        self != Status::Success && !self.is_error()
165    }
166
167    /// Wraps success *and* warning codes in [`Ok`], and error codes in [`Err`].
168    #[allow(clippy::missing_errors_doc)]
169    pub fn into_result(self) -> Result {
170        if self.is_error() {
171            Err(self)
172        } else {
173            Ok(self)
174        }
175    }
176}
177
178impl From<Status> for Result {
179    fn from(status: Status) -> Self {
180        status.into_result()
181    }
182}
183
184#[cfg(test)]
185mod test_status {
186    use super::*;
187
188    #[test]
189    fn test_equality() {
190        let status = Status(1);
191        assert_eq!(status, Status::WarnUnknownGlyph);
192        assert_ne!(status, Status::Success);
193        assert_ne!(status, Status::LoadError);
194    }
195
196    #[test]
197    #[allow(clippy::bool_assert_comparison)]
198    #[allow(clippy::branches_sharing_code)]
199    fn test_is_error() {
200        assert_eq!(false, Status::Success.is_error());
201
202        assert_eq!(false, Status::WarnUnknownGlyph.is_error());
203        assert_eq!(false, Status::WarnFileSystem.is_error());
204        assert_eq!(false, Status::WarnResetRequired.is_error());
205
206        assert_eq!(true, Status::LoadError.is_error());
207        assert_eq!(true, Status::AccessDenied.is_error());
208        assert_eq!(true, Status::HTTPError.is_error());
209
210        if cfg!(target_pointer_width = "32") {
211            assert_eq!(Status(0x0000_0000).is_error(), false);
212            assert_eq!(Status(0x0000_0001).is_error(), false);
213            assert_eq!(Status(0x7fff_ffff).is_error(), false);
214            assert_eq!(Status(0x8000_0000).is_error(), true);
215            assert_eq!(Status(0xffff_ffff).is_error(), true);
216        } else {
217            assert_eq!(Status(0x0000_0000_0000_0000).is_error(), false);
218            assert_eq!(Status(0x0000_0000_0000_0001).is_error(), false);
219            assert_eq!(Status(0x7fff_ffff_ffff_ffff).is_error(), false);
220            assert_eq!(Status(0x8000_0000_0000_0000).is_error(), true);
221            assert_eq!(Status(0xffff_ffff_ffff_ffff).is_error(), true);
222        }
223    }
224
225    #[test]
226    #[allow(clippy::bool_assert_comparison)]
227    #[allow(clippy::branches_sharing_code)]
228    fn test_is_warning() {
229        assert_eq!(false, Status::Success.is_warning());
230
231        assert_eq!(true, Status::WarnUnknownGlyph.is_warning());
232        assert_eq!(true, Status::WarnFileSystem.is_warning());
233        assert_eq!(true, Status::WarnResetRequired.is_warning());
234
235        assert_eq!(false, Status::LoadError.is_warning());
236        assert_eq!(false, Status::AccessDenied.is_warning());
237        assert_eq!(false, Status::HTTPError.is_warning());
238
239        if cfg!(target_pointer_width = "32") {
240            assert_eq!(Status(0x0000_0000).is_warning(), false);
241            assert_eq!(Status(0x0000_0001).is_warning(), true);
242            assert_eq!(Status(0x7fff_ffff).is_warning(), true);
243            assert_eq!(Status(0x8000_0000).is_warning(), false);
244            assert_eq!(Status(0xffff_ffff).is_warning(), false);
245        } else {
246            assert_eq!(Status(0x0000_0000_0000_0000).is_warning(), false);
247            assert_eq!(Status(0x0000_0000_0000_0001).is_warning(), true);
248            assert_eq!(Status(0x7fff_ffff_ffff_ffff).is_warning(), true);
249            assert_eq!(Status(0x8000_0000_0000_0000).is_warning(), false);
250            assert_eq!(Status(0xffff_ffff_ffff_ffff).is_warning(), false);
251        }
252    }
253
254    #[test]
255    fn test_into_result() {
256        assert_eq!(Status::Success.into_result(), Ok(Status::Success));
257        assert_eq!(Status::WarnFileSystem.into_result(), Ok(Status::WarnFileSystem));
258        assert_eq!(Status::LoadError.into_result(), Err(Status::LoadError));
259        assert_eq!(Status::HTTPError.into_result(), Err(Status::HTTPError));
260    }
261}
262
263
264pub trait Table {
265    const SIGNATURE: u64;
266    const MIN_REVISION: Revision;
267
268    fn header(&self) -> &TableHeader;
269
270    fn verify(&self)
271    where
272        Self: Sized,
273    {
274        self.verify_signature();
275        self.verify_revision();
276        self.verify_size();
277        self.verify_crc32();
278    }
279
280    fn verify_signature(&self) {
281        let actual_signature = self.header().signature;
282        assert!(
283            actual_signature == Self::SIGNATURE,
284            "Signature mismatch. Expected {:x}, received {:x}",
285            actual_signature,
286            Self::SIGNATURE,
287        );
288    }
289
290    fn verify_revision(&self) {
291        let actual_revision = self.header().revision;
292        assert!(
293            actual_revision >= Self::MIN_REVISION,
294            "Revision {} older than minimum supported revision {}",
295            actual_revision,
296            Self::MIN_REVISION,
297        );
298    }
299
300    fn verify_size(&self)
301    where
302        Self: Sized,
303    {
304        let actual_size = self.header().header_size as usize;
305        assert!(
306            actual_size >= size_of::<Self>(),
307            "Header size {} was less than expected {} bytes",
308            actual_size,
309            size_of::<Self>(),
310        );
311    }
312
313    fn verify_crc32(&self) {
314        const CRC_FIELD_LENGTH: usize = size_of::<u32>();
315
316        let header = self.header();
317        let size = header.header_size as usize;
318        let start_address = core::ptr::from_ref(self).cast::<u8>();
319        let crc_field_address = core::ptr::addr_of!(header.crc32).cast::<u8>();
320
321        let mut crc = CRCu32::crc32();
322        unsafe {
323            // Digest start of header until right before CRC value
324            crc.digest(slice::from_raw_parts(
325                start_address,
326                crc_field_address.offset_from(start_address).try_into().unwrap(),
327            ));
328
329            // Replace CRC field with zeros
330            crc.digest(&[0_u8; CRC_FIELD_LENGTH]);
331
332            // Digest rest of header starting past end of CRC field
333            let crc_field_end = crc_field_address.add(CRC_FIELD_LENGTH);
334            let end_address = start_address.add(size);
335            crc.digest(slice::from_raw_parts(
336                crc_field_end,
337                end_address.offset_from(crc_field_end).try_into().unwrap(),
338            ));
339        }
340
341        let orig_remainder = header.crc32;
342        let actual_remainder = crc.get_crc();
343        assert!(
344            actual_remainder == orig_remainder,
345            "Calculated CRC {actual_remainder:x} does not match listed value \
346             {orig_remainder:x}"
347        );
348    }
349}
350
351
352#[repr(C)]
353pub struct TableHeader {
354    pub signature: u64,
355    pub revision: Revision,
356    pub header_size: u32,
357    pub crc32: u32,
358    reserved: u32,
359}
360
361
362#[repr(C)]
363pub struct SystemTable<'a> {
364    pub header: TableHeader,
365    pub firmware_vendor: *const u16,
366    pub firmware_revision: u32,
367    pub console_in_handle: Handle,
368    pub console_in: Option<&'a proto::SimpleTextInput>,
369    pub console_out_handle: Handle,
370    pub console_out: Option<&'a proto::SimpleTextOutput>,
371    pub std_err_handle: Handle,
372    pub std_err: Option<&'a proto::SimpleTextOutput>,
373    pub runtime_services: &'a RuntimeServices,
374    pub boot_services: Option<&'a BootServices>,
375    pub config_entry_count: usize,
376    pub config_table: *const ConfigurationTable,
377}
378
379impl Table for SystemTable<'_> {
380    const SIGNATURE: u64 = 0x5453_5953_2049_4249;
381    const MIN_REVISION: Revision = Revision::V2_00;
382    fn header(&self) -> &TableHeader {
383        &self.header
384    }
385}
386
387impl SystemTable<'_> {
388    /// Safe(r) wrapper around [`BootServices::exit_boot_services`] that fetches the
389    /// latest memory map and ensures that pointers to boot services are removed from the
390    /// system table on success.
391    ///
392    /// # Safety
393    /// After this succeeds, pointers to functions that provide any kind of boot services
394    /// are no longer valid. This includes the console streams and memory allocation,
395    /// which may be referenced by globals outside of the control of this object. It is
396    /// the caller's responsibility to make sure any dangling references are cleared or
397    /// unused.
398    pub unsafe fn exit_boot_services(&mut self, image_handle: Handle) -> MemoryMap {
399        #![allow(clippy::missing_panics_doc)]
400        let boot_services = self.boot_services.unwrap();
401        let memory_map = boot_services.get_memory_map();
402        // This shouldn't fail since we just got the memory map
403        boot_services.exit_boot_services(image_handle, memory_map.key).unwrap();
404
405        // Clear pointers to boot services and console streams, which are now invalid
406        self.boot_services = None;
407        self.console_in_handle = Handle::NULL;
408        self.console_in = None;
409        self.console_out_handle = Handle::NULL;
410        self.console_out = None;
411        self.std_err_handle = Handle::NULL;
412        self.std_err = None;
413
414        memory_map
415    }
416}
417
418
419#[repr(C)]
420pub struct RuntimeServices {
421    pub header: TableHeader,
422
423    // Time Services
424    get_time: usize,
425    set_time: usize,
426    get_wakeup_time: usize,
427    set_wakeup_time: usize,
428
429    // Virtual Memory Services
430    set_virtual_address_map: usize,
431    convert_pointer: usize,
432
433    // Variable Services
434    get_variable: usize,
435    get_next_variable_name: usize,
436    set_variable: usize,
437
438    // Miscellaneous Services
439    get_next_high_monotonic_count: usize,
440    reset_system: usize,
441
442    // UEFI 2.0 Capsule Services
443    update_capsule: usize,
444    query_capsule_capabilities: usize,
445
446    // Miscellaneous UEFI 2.0 Service
447    query_variable_info: usize,
448}
449
450impl Table for RuntimeServices {
451    const SIGNATURE: u64 = 0x5652_4553_544e_5552;
452    const MIN_REVISION: Revision = Revision::V2_00;
453    fn header(&self) -> &TableHeader {
454        &self.header
455    }
456}
457
458
459#[repr(C)]
460pub struct BootServices {
461    pub header: TableHeader,
462
463    // Task Priority Services
464    raise_tpl: usize,
465    restore_tpl: usize,
466
467    // Memory Services
468    // NOTE: Physical addresses are represented as u64 even on 32-bit systems
469    allocate_pages_: unsafe extern "C" fn(
470        allocate_type: AllocateType,
471        memory_type: MemoryType,
472        page_count: usize,
473        physical_address: *mut u64,
474    ) -> Status,
475    pub free_pages:
476        unsafe extern "C" fn(physical_address: u64, page_count: usize) -> Status,
477    get_memory_map_: unsafe extern "C" fn(
478        map_size: &mut usize,
479        map: *mut c_void,
480        map_key: &mut usize,
481        descriptor_size: &mut usize,
482        descriptor_version: &mut u32,
483    ) -> Status,
484    pub allocate_pool: unsafe extern "C" fn(
485        pool_type: MemoryType,
486        size: usize,
487        buffer: *mut *mut c_void,
488    ) -> Status,
489    pub free_pool: unsafe extern "C" fn(buffer: *mut c_void) -> Status,
490
491    // Event & Timer Services
492    create_event: usize,
493    set_timer: usize,
494    wait_for_event: usize,
495    signal_event: usize,
496    close_event: usize,
497    check_event: usize,
498
499    // Protocol Handler Services
500    install_protocol_interface: usize,
501    reinstall_protocol_interface: usize,
502    uninstall_protocol_interface: usize,
503    pub handle_protocol: unsafe extern "C" fn(
504        handle: Handle,
505        protocol: &GUID,
506        interface: *mut *const c_void,
507    ) -> Status,
508    reserved: usize,
509    register_protocol_notify: usize,
510    locate_handle: usize,
511    locate_device_path: usize,
512    install_configuration_table: usize,
513
514    // Image Services
515    load_image: usize,
516    start_image: usize,
517    exit: usize,
518    unload_image: usize,
519    exit_boot_services_:
520        unsafe extern "C" fn(image_handle: Handle, memory_map_key: usize) -> Status,
521
522    // Miscellaneous Services
523    get_next_monotonic_count: usize,
524    stall: usize,
525    set_watchdog_timer: usize,
526
527    // Driver Support Services
528    connect_controller: usize,
529    disconnect_controller: usize,
530
531    // Open and Close Protocol Services
532    open_protocol_: unsafe extern "C" fn(
533        handle: Handle,
534        guid: &GUID,
535        interface: *mut *const c_void,
536        agent_handle: Handle,
537        controller_handle: Handle,
538        attributes: OpenProtocolAttributes,
539    ) -> Status,
540    close_protocol: usize,
541    open_protocol_information: usize,
542
543    // Library Services
544    protocols_per_handle: usize,
545    locate_handle_buffer: usize,
546    locate_protocol: usize,
547    install_multiple_protocol_interfaces: usize,
548    uninstall_multiple_protocol_interfaces: usize,
549
550    // 32-bit CRC Services
551    calculate_crc32: usize,
552
553    // Miscellaneous Services
554    copy_mem: usize,
555    set_mem: usize,
556    create_event_ex: usize,
557}
558
559impl Table for BootServices {
560    const SIGNATURE: u64 = 0x5652_4553_544f_4f42;
561    const MIN_REVISION: Revision = Revision::V2_00;
562    fn header(&self) -> &TableHeader {
563        &self.header
564    }
565}
566
567impl BootServices {
568    /// Allocate a number of pages of a given type of memory, optionally constraining its
569    /// location.
570    ///
571    /// Pages are 4KB ([`PAGE_SIZE`]) on all platforms.
572    ///
573    /// The meaning of `reference_address` depends on `allocate_type`:
574    ///   * [`AnyAddress`](AllocateType::AnyAddress): Search for a block of pages anywhere
575    ///     in memory. `reference_address` is ignored, and should be `None`.
576    ///   * [`MaxAddress`](AllocateType::MaxAddress): Search for a block of pages below
577    ///     the given address.
578    ///   * [`ExactAddress`](AllocateType::ExactAddress): Reserve the block of pages
579    ///     starting at `reference_address`.
580    ///
581    /// # Errors
582    /// This function will return:
583    ///   * [`Status::OutOfResources`] if there was not enough memory available with the
584    ///     specified constraints.
585    ///   * [`Status::InvalidParameter`] if the memory type is unsupported.
586    ///   * [`Status::NotFound`] if the requested memory location is out of bounds of
587    ///     physical memory.
588    pub fn allocate_pages(
589        &self,
590        allocate_type: AllocateType,
591        memory_type: MemoryType,
592        page_count: usize,
593        reference_address: Option<u64>,
594    ) -> core::result::Result<u64, Status> {
595        let mut physical_address = reference_address.unwrap_or_default();
596        unsafe {
597            (self.allocate_pages_)(
598                allocate_type,
599                memory_type,
600                page_count,
601                &raw mut physical_address,
602            )
603            .into_result()?;
604        }
605        Ok(physical_address)
606    }
607
608
609    /// Get a map representing the status of all available memory.
610    ///
611    /// # Panics
612    /// Panics if the firmware does not behave according to the spec.
613    pub fn get_memory_map(&self) -> MemoryMap {
614        let mut memory_map_size = 0_usize;
615        let mut memory_map = MemoryMap::new();
616
617        loop {
618            memory_map.raw_map.resize(memory_map_size, 0);
619
620            let result = unsafe {
621                (self.get_memory_map_)(
622                    &mut memory_map_size,
623                    // TODO: Make sure this is aligned properly. memory_map.verify() will
624                    // check and panic if it isn't, but we should be able to ensure it.
625                    memory_map.raw_map.as_mut_ptr().cast(),
626                    &mut memory_map.key,
627                    &mut memory_map.descriptor_size,
628                    &mut memory_map.descriptor_version,
629                )
630                .into_result()
631            };
632            match result {
633                Ok(_) => break,
634                Err(Status::BufferTooSmall) => {
635                    // Allow room for another entry since we have to reallocate the buffer
636                    memory_map_size += memory_map.descriptor_size;
637                }
638                // We shouldn't run into any of the other errors listed in the spec.
639                Err(e) => panic!("Unexpected error from get_memory_map: {:?}", e),
640            }
641        }
642
643        // Trim anything that wasn't used
644        memory_map.raw_map.resize(memory_map_size, 0);
645
646        memory_map.verify();
647        memory_map
648    }
649
650
651    /// Get the implementation of a protocol offered by the given `handle`.
652    ///
653    /// For UEFI applications, `agent_handle` is the application's image handle. This
654    /// method does not offer all the options required by UEFI drivers.
655    ///
656    /// # Errors
657    /// Fails if `handle` is not valid, or it does not implement the specified protocol.
658    ///
659    /// # Panics
660    /// Panics if the firmware does not behave according to the spec.
661    pub fn get_protocol<T: proto::Protocol>(
662        &self,
663        handle: Handle,
664        agent_handle: Handle,
665    ) -> core::result::Result<&T, Status> {
666        let mut protocol = core::ptr::null::<T>();
667        unsafe {
668            (self.open_protocol_)(
669                handle,
670                &T::PROTOCOL_ID,
671                core::ptr::addr_of_mut!(protocol).cast(),
672                agent_handle,
673                Handle::NULL,
674                OpenProtocolAttributes::Get,
675            )
676            .into_result()?;
677            Ok(protocol.as_ref().unwrap())
678        }
679    }
680
681    /// Signal to UEFI that the OS is now taking over.
682    ///
683    /// If this function exits successfully, the OS is now in charge of memory management,
684    /// and it is no longer safe to call any functions on [`BootServices`].
685    ///
686    /// In order to ensure that the OS has an accurate picture of the system, the caller
687    /// must pass the [`key`](MemoryMap::key) from a prior call to
688    /// [`BootServices::get_memory_map`]. If it does not match the latest value, this
689    /// function returns with an error and the caller will have to fetch a new map before
690    /// trying again.
691    ///
692    /// # Safety
693    /// If this function exits successfully, then this object is no longer valid. The
694    /// pointer to this table should be removed from the [`SystemTable`] and any other
695    /// copies should be deleted.
696    ///
697    /// If the function exits with an error, then it is only safe to call memory services
698    /// like [`allocate_pages`](Self::allocate_pages) and
699    /// [`get_memory_map`](Self::get_memory_map). Any other boot services may have been
700    /// unloaded already.
701    ///
702    /// # Errors
703    /// Will fail with [`Status::InvalidParameter`] if the `memory_map_key` does not match
704    /// the latest value.
705    pub unsafe fn exit_boot_services(
706        &self,
707        image_handle: Handle,
708        memory_map_key: usize,
709    ) -> Result {
710        (self.exit_boot_services_)(image_handle, memory_map_key).into_result()
711    }
712}
713
714
715#[repr(C)]
716#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717pub struct GUID(u32, u16, u16, [u8; 8]);
718
719impl GUID {
720    pub const fn from(n: u128) -> Self {
721        #[allow(clippy::cast_possible_truncation)]
722        GUID(
723            (n >> 96) as u32,
724            (n >> 80) as u16,
725            (n >> 64) as u16,
726            (n as u64).to_be_bytes(),
727        )
728    }
729}
730
731impl From<u128> for GUID {
732    fn from(n: u128) -> Self {
733        GUID::from(n)
734    }
735}
736
737#[cfg(test)]
738mod test_guid {
739    use super::*;
740
741    #[test]
742    fn test_from_u128() {
743        let guid = GUID::from(0x01020304_0506_0708_0910_111213141516);
744        assert_eq!(guid.0, 0x0102_0304);
745        assert_eq!(guid.1, 0x0506);
746        assert_eq!(guid.2, 0x0708);
747        assert_eq!(guid.3, *b"\x09\x10\x11\x12\x13\x14\x15\x16");
748    }
749
750    #[test]
751    fn test_equality() {
752        let guid_a = GUID::from(0xcf04d973_15f7_400b_b53b_82929911d09c);
753        let guid_b = GUID::from(0xcf04d973_15f7_400b_b53b_82929911d09c);
754        let guid_c = GUID::from(0x028c338c_0b14_4687_9ad7_14cba520b645);
755        assert_eq!(guid_a, guid_b);
756        assert_ne!(guid_a, guid_c);
757        assert_ne!(guid_b, guid_c);
758    }
759}
760
761#[repr(C)]
762pub struct ConfigurationTable {
763    pub vendor_guid: GUID,
764    vendor_table: usize,
765}
766
767#[rustfmt::skip]
768impl ConfigurationTable {
769    pub const ACPI_20_GUID:    GUID = GUID::from(0x8868e871_e4f1_11d3_bc22_0080c73c8881);
770    pub const ACPI_GUID:       GUID = GUID::from(0xeb9d2d30_2d88_11d3_9a16_0090273fc14d);
771    pub const SAL_SYSTEM_GUID: GUID = GUID::from(0xeb9d2d32_2d88_11d3_9a16_0090273fc14d);
772    pub const SMBIOS_GUID:     GUID = GUID::from(0xeb9d2d31_2d88_11d3_9a16_0090273fc14d);
773    pub const SMBIOS3_GUID:    GUID = GUID::from(0xf2fd1544_9794_4a2c_992e_e5bbcf20e394);
774    pub const MPS_GUID:        GUID = GUID::from(0xeb9d2d2f_2d88_11d3_9a16_0090273fc14d);
775    // TODO: ... more defined in sect. 4.6
776}
777
778#[repr(C)]
779pub struct MemoryDescriptor {
780    pub memory_type: MemoryType,
781    // NOTE: Addresses represented as u64 even on 32-bit systems
782    pub physical_start: u64,
783    pub virtual_start: u64,
784    pub page_count: u64,
785    pub attributes: MemoryAttributes,
786}
787
788// NOTE: Not strictly part of UEFI API. Might belong in another module.
789pub struct MemoryMap {
790    pub raw_map: Vec<u8>,
791    pub key: usize,
792    pub descriptor_size: usize,
793    pub descriptor_version: u32,
794}
795
796impl MemoryMap {
797    pub const MIN_VERSION: u32 = 1;
798
799    pub fn new() -> Self {
800        MemoryMap {
801            raw_map: Vec::new(),
802            key: 0,
803            descriptor_size: 0,
804            descriptor_version: 0,
805        }
806    }
807
808    pub fn verify(&self) {
809        self.verify_version();
810        self.verify_descriptor_size();
811        self.verify_map();
812    }
813
814    #[allow(clippy::missing_panics_doc)]
815    pub fn verify_version(&self) {
816        assert!(
817            self.descriptor_version >= Self::MIN_VERSION,
818            "Descriptor version {} less than required version {}",
819            self.descriptor_version,
820            Self::MIN_VERSION,
821        );
822    }
823
824    #[allow(clippy::missing_panics_doc)]
825    pub fn verify_descriptor_size(&self) {
826        assert!(
827            self.descriptor_size >= size_of::<MemoryDescriptor>(),
828            "Descriptor size {} shorter than required {} bytes",
829            self.descriptor_size,
830            size_of::<MemoryDescriptor>(),
831        );
832
833        assert!(
834            self.descriptor_size.is_multiple_of(align_of::<MemoryDescriptor>()),
835            "Descriptor size {} not a multiple of the MemoryDescriptor struct alignment \
836             {}",
837            self.descriptor_size,
838            align_of::<MemoryDescriptor>(),
839        );
840    }
841
842    #[allow(clippy::missing_panics_doc)]
843    pub fn verify_map(&self) {
844        assert!(
845            self.raw_map.len().is_multiple_of(self.descriptor_size),
846            "Memory map total size {} is not a multiple of descriptor size {}",
847            self.raw_map.len(),
848            self.descriptor_size,
849        );
850
851        assert!(
852            self.raw_map.as_ptr().align_offset(align_of::<MemoryDescriptor>()) == 0,
853            "Memory map at {:p} not aligned properly for dereferencing. Required: {:x}",
854            self.raw_map.as_ptr(),
855            align_of::<MemoryDescriptor>(),
856        );
857    }
858
859    /// Iterate over memory descriptors contained in the map.
860    pub fn iter(&self) -> impl Iterator<Item = &MemoryDescriptor> {
861        #![allow(clippy::cast_ptr_alignment)]
862
863        // SAFETY: We check the pointer alignment in the verify() call. Since
864        // MemoryDescriptor is only composed of unsigned integer types, it is safe to
865        // interpret any sequence of bytes as a MemoryDescriptor.
866        self.verify();
867        self.raw_map
868            .as_slice()
869            .chunks_exact(self.descriptor_size)
870            .map(|raw| unsafe { &*raw.as_ptr().cast::<MemoryDescriptor>() })
871    }
872}
873
874impl Default for MemoryMap {
875    fn default() -> Self {
876        Self::new()
877    }
878}
879
880c_enum! {
881    pub enum MemoryType(u32) {
882        /// Memory that is never available for use
883        Reserved            = 0,
884        /// Memory used for UEFI application code.
885        LoaderCode          = 1,
886        /// Memory allocated by UEFI applications.
887        LoaderData          = 2,
888        /// Memory used for drivers that provide [`BootServices`].
889        BootServicesCode    = 3,
890        /// Memory allocated by drivers that provide [`BootServices`].
891        BootServicesData    = 4,
892        /// Memory used for drivers that provide [`RuntimeServices`].
893        RuntimeServicesCode = 5,
894        /// Memory allocated by drivers that provide [`RuntimeServices`].
895        RuntimeServicesData = 6,
896        /// Free memory.
897        Conventional        = 7,
898        /// Damaged memory modules.
899        Unusable            = 8,
900        /// Memory that can be used after the OS initializes ACPI.
901        ACPIReclaim         = 9,
902        /// Memory that must be preserved in ACPI states S1–S3.
903        ACPINonVolatile     = 10,
904        /// Memory mapped to device I/O.
905        MappedIO            = 11,
906        /// Memory mapped to I/O ports.
907        MappedIOPortSpace   = 12,
908        /// Memory used by processor firmware code.
909        ProcessorCode       = 13,
910        /// Free nonvolatile memory.
911        Persistent          = 14,
912
913        /// Beginning of range (inclusive) for OEM-specific memory types
914        MinOEMDefined       = 0x7000_0000,
915        /// End of range (inclusive) for OEM-specific memory types
916        MaxOEMDefined       = 0x7fff_ffff,
917        /// Beginning of range (inclusive) for operating system-specific memory types
918        MinOSDefined        = 0x8000_0000,
919        /// End of range (inclusive) for operating system-specific memory types
920        MaxOSDefined        = 0xffff_ffff,
921    }
922}
923
924c_enum! {
925    pub enum AllocateType(u32) {
926        AnyAddress   = 0,
927        MaxAddress   = 1,
928        ExactAddress = 2,
929    }
930}
931
932bitfield! {
933    pub struct MemoryAttributes(u64) {
934        [0] pub supports_uncacheable,
935        [1] pub supports_write_combining,
936        [2] pub supports_write_through,
937        [4] pub supports_write_back,
938        [5] pub supports_uncacheable_sem,
939        [12] pub supports_write_protect,
940        [13] pub supports_read_protect,
941        [14] pub supports_exec_protect,
942        [15] pub nonvolatile,
943        [16] pub more_reliable,
944        [17] pub supports_read_only,
945        [18] pub specific_purpose,
946        [19] pub supports_cpu_crypto,
947        [63] pub runtime,
948    }
949}
950
951
952c_enum! {
953    pub enum OpenProtocolAttributes(u32) {
954        ByHandle  = 1 << 0,
955        Get       = 1 << 1,
956        Test      = 1 << 2,
957        ByChild   = 1 << 3,
958        ByDriver  = 1 << 4,
959        Exclusive = 1 << 5,
960    }
961}
962
963
964pub mod proto {
965    use super::{Handle, MemoryType, Result, Status, SystemTable, GUID};
966    use core::ffi::c_void;
967    use tartan_bitfield::bitfield;
968    use tartan_c_enum::c_enum;
969
970    pub trait Protocol {
971        const PROTOCOL_ID: GUID;
972    }
973
974
975    #[repr(C)]
976    #[derive(Debug)]
977    pub struct SimpleTextInput {
978        reset: usize,
979        read_key_stroke: usize,
980        wait_for_key: usize,
981        set_state: usize,
982        register_key_notify: usize,
983        unregister_key_notify: usize,
984    }
985
986    #[repr(C)]
987    pub struct SimpleTextOutput {
988        pub reset: unsafe extern "C" fn(
989            this: &SimpleTextOutput,
990            extended_verification: bool,
991        ) -> Status,
992        pub output_string:
993            unsafe extern "C" fn(this: &SimpleTextOutput, string: *const u16) -> Status,
994        pub test_string:
995            unsafe extern "C" fn(this: &SimpleTextOutput, string: *const u16) -> Status,
996        query_mode: usize,
997        set_mode: usize,
998        set_attribute: usize,
999        clear_screen: usize,
1000        set_cursor_position: usize,
1001        enable_cursor: usize,
1002        pub mode: SimpleTextOutputMode,
1003    }
1004
1005    #[repr(C)]
1006    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
1007    pub struct SimpleTextOutputMode {
1008        pub max_mode: i32,
1009        pub mode: i32,
1010        pub attribute: i32,
1011        pub cursor_column: i32,
1012        pub cursor_row: i32,
1013        pub cursor_visible: bool,
1014    }
1015
1016
1017    #[repr(C)]
1018    #[derive(Debug)]
1019    pub struct LoadedImage<'a> {
1020        pub revision: u32,
1021        pub parent_handle: Handle,
1022        pub system_table: *const SystemTable<'a>,
1023
1024        pub device_handle: Handle,
1025        device_path_protocol: usize, // TODO: type
1026        _reserved: usize,
1027
1028        pub load_options_size: u32,
1029        pub load_options: *const c_void,
1030
1031        pub image_base: *mut c_void,
1032        pub image_size: u64,
1033        pub image_code_type: MemoryType,
1034        pub image_data_type: MemoryType,
1035        pub unload: unsafe extern "C" fn(handle: Handle) -> Status,
1036    }
1037
1038    impl Protocol for LoadedImage<'_> {
1039        const PROTOCOL_ID: GUID = GUID::from(0x5b1b31a1_9562_11d2_8e3f_00a0c969723b);
1040    }
1041
1042    impl LoadedImage<'_> {
1043        pub const MIN_REVISION: u32 = 0x1000;
1044    }
1045
1046
1047    /// Protocol for accessing a file system supported by UEFI (typically FAT).
1048    #[repr(C)]
1049    pub struct SimpleFileSystem {
1050        /// Implemented revision of the `SimpleFileSystem` protocol.
1051        pub revision: u64,
1052        open_volume_: unsafe extern "C" fn(
1053            this: &SimpleFileSystem,
1054            root: &mut *const File,
1055        ) -> Status,
1056    }
1057
1058    impl Protocol for SimpleFileSystem {
1059        const PROTOCOL_ID: GUID = GUID::from(0x964e5b22_6459_11d2_8e39_00a0c969723b);
1060    }
1061
1062    impl SimpleFileSystem {
1063        /// Minimum supported [`SimpleFileSystem::revision`]. Future versions are
1064        /// guaranteed to be backwards-compatible.
1065        pub const MIN_REVISION: u32 = 0x0001_0000;
1066
1067        /// Get a handle to the root of the file system.
1068        ///
1069        /// # Errors
1070        /// This method can fail for many reasons, including standard I/O issues like
1071        /// device errors or resource exhaustion. It will also fail if the file system on
1072        /// the device is not supported by the UEFI implementation.
1073        ///
1074        /// # Panics
1075        /// Panics if the firmware does not behave according to the spec.
1076        pub fn open_volume(&self) -> core::result::Result<&File, Status> {
1077            let mut root = core::ptr::null::<File>();
1078            unsafe {
1079                (self.open_volume_)(self, &mut root).into_result()?;
1080                Ok(root.as_ref().unwrap())
1081            }
1082        }
1083    }
1084
1085
1086    /// Protocol for accessing a file or directory.
1087    #[repr(C)]
1088    pub struct File {
1089        /// Implemented revision of the File protocol.
1090        pub revision: u64,
1091
1092        open_: unsafe extern "C" fn(
1093            this: &File,
1094            file: &mut *const File,
1095            path: *const u16,
1096            mode: FileMode,
1097            attributes: FileAttributes,
1098        ) -> Status,
1099        close_: unsafe extern "C" fn(this: &File) -> Status,
1100        delete_: unsafe extern "C" fn(this: &File) -> Status,
1101        read_: unsafe extern "C" fn(
1102            this: &File,
1103            count: &mut usize,
1104            buffer: *mut c_void,
1105        ) -> Status,
1106        write_: unsafe extern "C" fn(
1107            this: &File,
1108            count: &mut usize,
1109            buffer: *const c_void,
1110        ) -> Status,
1111        get_position_: unsafe extern "C" fn(this: &File, position: &mut u64) -> Status,
1112        set_position_: unsafe extern "C" fn(this: &File, position: u64) -> Status,
1113        get_info: usize,
1114        set_info: usize,
1115        flush_: unsafe extern "C" fn(this: &File) -> Status,
1116
1117        // Only available if revision >= 0x0002_0000
1118        open_v2: usize,
1119        read_v2: usize,
1120        write_v2: usize,
1121        flush_v2: usize,
1122    }
1123
1124    impl File {
1125        /// Minimum supported [`SimpleFileSystem::revision`]. Future versions are
1126        /// guaranteed to be backwards-compatible.
1127        pub const MIN_REVISION: u32 = 0x0001_0000;
1128
1129        /// Get a handle to a new file, relative to the directory represented by the
1130        /// current instance.
1131        ///
1132        /// `path` uses the Windows path format without a drive name, e.g. `\FOO\BAR.TXT`
1133        /// or `..\QUUX.DAT`.
1134        ///
1135        /// `attributes` is only used if the file is created.
1136        ///
1137        /// # Errors
1138        /// This method can fail for many reasons, including standard I/O issues like
1139        /// device errors or resource exhaustion. It can also fail if access is denied or
1140        /// there was an attempt to write to read-only media.
1141        ///
1142        /// # Panics
1143        /// Panics if `path` is empty or does not end in a null character.
1144        pub fn open(
1145            &self,
1146            path: &[u16],
1147            mode: FileMode,
1148            attributes: FileAttributes,
1149        ) -> core::result::Result<&File, Status> {
1150            assert!(!path.is_empty(), "Path cannot be empty");
1151            assert!(*path.last().unwrap() == 0, "Path must be null-terminated");
1152
1153            let mut file = core::ptr::null::<File>();
1154            unsafe {
1155                (self.open_)(self, &mut file, path.as_ptr(), mode, attributes)
1156                    .into_result()?;
1157                Ok(file.as_ref().unwrap())
1158            }
1159        }
1160
1161        /// Flush and close the file or directory represented by the current instance.
1162        ///
1163        /// # Panics
1164        /// Panics if the firmware does not behave according to the spec.
1165        pub fn close(&self) {
1166            unsafe {
1167                // The UEFI spec says this cannot fail
1168                assert!((self.close_)(self) == Status::Success);
1169            }
1170        }
1171
1172        /// Delete the file or directory represented by the current instance.
1173        ///
1174        /// # Errors
1175        /// This method cannot fail with an error, but it will return
1176        /// [`Status::WarnDeleteFailure`] if the file could not be deleted.
1177        pub fn delete(&self) -> Status {
1178            unsafe { (self.delete_)(self) }
1179        }
1180
1181        /// Read file contents or a directory entry into the buffer.
1182        ///
1183        /// If this is a file, it will read up to `buffer.len()` bytes. If it is a
1184        /// directory, it will read a single directory entry if that entry can fit in the
1185        /// buffer.
1186        ///
1187        /// On success, returns the number of bytes actually read.
1188        ///
1189        /// # Errors
1190        /// This method can fail for many reasons, including standard I/O issues like
1191        /// device errors or resource exhaustion. In addition, it will return:
1192        ///   * [`Status::BufferTooSmall`] if this is a directory and the next entry could
1193        ///     not fit into the buffer.
1194        ///   * [`Status::DeviceError`] if the current position was already EOF.
1195        pub fn read(&self, buffer: &mut [u8]) -> core::result::Result<usize, Status> {
1196            let mut count = buffer.len();
1197            unsafe {
1198                (self.read_)(self, &mut count, buffer.as_mut_ptr().cast())
1199                    .into_result()?;
1200            }
1201            Ok(count)
1202        }
1203
1204        /// Write the contents of `buffer` out to the current position.
1205        ///
1206        /// On success, returns the number of bytes actually written, which will always
1207        /// be the full buffer.
1208        ///
1209        /// # Errors
1210        /// This method can fail for many reasons, including standard I/O issues like
1211        /// device errors or resource exhaustion. It addition, it will return:
1212        ///    * `Status::Unsupported` if this is a directory.
1213        ///    * `Status::AccessDenied` if the file is in read-only mode.
1214        pub fn write(&self, buffer: &[u8]) -> core::result::Result<usize, Status> {
1215            let mut count = buffer.len();
1216            unsafe {
1217                (self.write_)(self, &mut count, buffer.as_ptr().cast()).into_result()?;
1218            }
1219            Ok(count)
1220        }
1221
1222        /// Get the handle's current position in the file.
1223        ///
1224        /// # Errors
1225        /// This function will fail with:
1226        ///    * [`Status::DeviceError`] if the file has been deleted.
1227        ///    * [`Status::Unsupported`] if this is a directory.
1228        pub fn get_position(&self) -> core::result::Result<u64, Status> {
1229            let mut position = 0_u64;
1230            unsafe {
1231                (self.get_position_)(self, &mut position).into_result()?;
1232            }
1233            Ok(position)
1234        }
1235
1236        /// Set the handle's current position in the file.
1237        ///
1238        /// If the position is `u64::MAX`, this will seek to the end of the file.
1239        /// Otherwise, it seeks to the absolute position in bytes. If the position is
1240        /// greater than the current file size, the file will grow to the given size.
1241        ///
1242        /// # Errors
1243        /// This function will fail with:
1244        ///    * [`Status::DeviceError`] if the file has been deleted.
1245        ///    * [`Status::Unsupported`] if this is a directory.
1246        pub fn set_position(&self, position: u64) -> Result {
1247            unsafe { (self.set_position_)(self, position).into_result() }
1248        }
1249
1250        /// Write any buffered changes.
1251        ///
1252        /// # Errors
1253        /// This method can fail for many reasons, including standard I/O issues like
1254        /// device errors or resource exhaustion. It can also fail if access is denied or
1255        /// there was an attempt to write to read-only media.
1256        pub fn flush(&self) -> Result {
1257            unsafe { (self.flush_)(self).into_result() }
1258        }
1259    }
1260
1261
1262    c_enum! {
1263        /// Controls how a file is opened.
1264        ///
1265        /// The UEFI spec defines separate read/write/create flags, but they are only
1266        /// valid in specific combinations, so this enum defines those combinations.
1267        pub enum FileMode(u64) {
1268            /// File will be read, and should exist already.
1269            Read = 0x1,
1270            /// File will be read and written, and should exist already.
1271            ReadWrite = 0x3,
1272            /// File will be read and written, and will be created if it does not exist.
1273            ReadWriteCreate = 0x8000_0000_0000_0003,
1274        }
1275    }
1276
1277    bitfield! {
1278        /// Basic properties of a file or directory as defined by the FAT format.
1279        pub struct FileAttributes(u64) {
1280            /// Indicates the file should not be written.
1281            [0] read_only,
1282            /// Indicates the file should not be visible under normal circumstances.
1283            [1] hidden,
1284            /// Indicates the file is important to system operation.
1285            [2] system,
1286            /// Indicates that this is a folder.
1287            [4] directory,
1288            /// Indicates the file should needs to be backed up.
1289            [5] archive,
1290        }
1291    }
1292}