From eb0ae3bbfe01ec0c56c839a5a1defd7e641eddb0 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 24 Aug 2024 14:14:52 -0400 Subject: [PATCH] uefi: Move various types to the `uefi::runtime` module The types are publicly re-exported from `uefi::table::runtime`, so there should be no breaking change to the public API. --- uefi/src/runtime.rs | 394 +++++++++++++++++++++++++++++++++++++- uefi/src/table/runtime.rs | 392 +------------------------------------ 2 files changed, 394 insertions(+), 392 deletions(-) diff --git a/uefi/src/runtime.rs b/uefi/src/runtime.rs index db92de505..61d5625cb 100644 --- a/uefi/src/runtime.rs +++ b/uefi/src/runtime.rs @@ -8,6 +8,7 @@ use crate::data_types::PhysicalAddress; use crate::table::{self, Revision}; use crate::{CStr16, Error, Result, Status, StatusExt}; +use core::fmt::{self, Debug, Display, Formatter}; use core::mem; use core::ptr::{self, NonNull}; use uefi_raw::table::boot::MemoryDescriptor; @@ -20,14 +21,11 @@ use { #[cfg(all(feature = "unstable", feature = "alloc"))] use alloc::alloc::Global; -pub use crate::table::runtime::{ - CapsuleInfo, Daylight, Time, TimeCapabilities, TimeError, TimeParams, VariableStorageInfo, -}; pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader}; -pub use uefi_raw::table::runtime::{ResetType, VariableAttributes, VariableVendor}; - -#[cfg(feature = "alloc")] -pub use crate::table::runtime::VariableKey; +pub use uefi_raw::table::runtime::{ + ResetType, TimeCapabilities, VariableAttributes, VariableVendor, +}; +pub use uefi_raw::time::Daylight; fn runtime_services_raw_panicking() -> NonNull { let st = table::system_table_raw_panicking(); @@ -512,3 +510,385 @@ pub unsafe fn set_virtual_address_map( Ok(()) } + +/// Date and time representation. +#[derive(Copy, Clone, Eq, PartialEq)] +#[repr(transparent)] +pub struct Time(uefi_raw::time::Time); + +/// Input parameters for [`Time::new`]. +#[derive(Copy, Clone, Debug)] +pub struct TimeParams { + /// Year in the range `1900..=9999`. + pub year: u16, + + /// Month in the range `1..=12`. + pub month: u8, + + /// Day in the range `1..=31`. + pub day: u8, + + /// Hour in the range `0.=23`. + pub hour: u8, + + /// Minute in the range `0..=59`. + pub minute: u8, + + /// Second in the range `0..=59`. + pub second: u8, + + /// Fraction of a second represented as nanoseconds in the range + /// `0..=999_999_999`. + pub nanosecond: u32, + + /// Offset in minutes from UTC in the range `-1440..=1440`, or + /// local time if `None`. + pub time_zone: Option, + + /// Daylight savings time information. + pub daylight: Daylight, +} + +/// Error returned by [`Time`] methods. A bool value of `true` means +/// the specified field is outside its valid range. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct TimeError { + pub year: bool, + pub month: bool, + pub day: bool, + pub hour: bool, + pub minute: bool, + pub second: bool, + pub nanosecond: bool, + pub timezone: bool, + pub daylight: bool, +} + +#[cfg(feature = "unstable")] +impl core::error::Error for TimeError {} + +impl Display for TimeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.year { + writeln!(f, "year not within `1900..=9999`")?; + } + if self.month { + writeln!(f, "month not within `1..=12")?; + } + if self.day { + writeln!(f, "day not within `1..=31`")?; + } + if self.hour { + writeln!(f, "hour not within `0..=23`")?; + } + if self.minute { + writeln!(f, "minute not within `0..=59`")?; + } + if self.second { + writeln!(f, "second not within `0..=59`")?; + } + if self.nanosecond { + writeln!(f, "nanosecond not within `0..=999_999_999`")?; + } + if self.timezone { + writeln!( + f, + "time_zone not `Time::UNSPECIFIED_TIMEZONE` nor within `-1440..=1440`" + )?; + } + if self.daylight { + writeln!(f, "unknown bits set for daylight")?; + } + Ok(()) + } +} + +impl Time { + /// Unspecified Timezone/local time. + const UNSPECIFIED_TIMEZONE: i16 = uefi_raw::time::Time::UNSPECIFIED_TIMEZONE; + + /// Create a `Time` value. If a field is not in the valid range, + /// [`TimeError`] is returned. + pub fn new(params: TimeParams) -> core::result::Result { + let time = Self(uefi_raw::time::Time { + year: params.year, + month: params.month, + day: params.day, + hour: params.hour, + minute: params.minute, + second: params.second, + pad1: 0, + nanosecond: params.nanosecond, + time_zone: params.time_zone.unwrap_or(Self::UNSPECIFIED_TIMEZONE), + daylight: params.daylight, + pad2: 0, + }); + + time.is_valid().map(|_| time) + } + + /// Create an invalid `Time` with all fields set to zero. This can + /// be used with [`FileInfo`] to indicate a field should not be + /// updated when calling [`File::set_info`]. + /// + /// [`FileInfo`]: uefi::proto::media::file::FileInfo + /// [`File::set_info`]: uefi::proto::media::file::File::set_info + #[must_use] + pub const fn invalid() -> Self { + Self(uefi_raw::time::Time::invalid()) + } + + /// `Ok()` if all fields are within valid ranges, `Err(TimeError)` otherwise. + pub fn is_valid(&self) -> core::result::Result<(), TimeError> { + let mut err = TimeError::default(); + if !(1900..=9999).contains(&self.year()) { + err.year = true; + } + if !(1..=12).contains(&self.month()) { + err.month = true; + } + if !(1..=31).contains(&self.day()) { + err.day = true; + } + if self.hour() > 23 { + err.hour = true; + } + if self.minute() > 59 { + err.minute = true; + } + if self.second() > 59 { + err.second = true; + } + if self.nanosecond() > 999_999_999 { + err.nanosecond = true; + } + if self.time_zone().is_some() && !((-1440..=1440).contains(&self.time_zone().unwrap())) { + err.timezone = true; + } + // All fields are false, i.e., within their valid range. + if err == TimeError::default() { + Ok(()) + } else { + Err(err) + } + } + + /// Query the year. + #[must_use] + pub const fn year(&self) -> u16 { + self.0.year + } + + /// Query the month. + #[must_use] + pub const fn month(&self) -> u8 { + self.0.month + } + + /// Query the day. + #[must_use] + pub const fn day(&self) -> u8 { + self.0.day + } + + /// Query the hour. + #[must_use] + pub const fn hour(&self) -> u8 { + self.0.hour + } + + /// Query the minute. + #[must_use] + pub const fn minute(&self) -> u8 { + self.0.minute + } + + /// Query the second. + #[must_use] + pub const fn second(&self) -> u8 { + self.0.second + } + + /// Query the nanosecond. + #[must_use] + pub const fn nanosecond(&self) -> u32 { + self.0.nanosecond + } + + /// Query the time offset in minutes from UTC, or None if using local time. + #[must_use] + pub const fn time_zone(&self) -> Option { + if self.0.time_zone == Self::UNSPECIFIED_TIMEZONE { + None + } else { + Some(self.0.time_zone) + } + } + + /// Query the daylight savings time information. + #[must_use] + pub const fn daylight(&self) -> Daylight { + self.0.daylight + } +} + +impl Debug for Time { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{:04}-{:02}-{:02} ", + self.0.year, self.0.month, self.0.day + )?; + write!( + f, + "{:02}:{:02}:{:02}.{:09}", + self.0.hour, self.0.minute, self.0.second, self.0.nanosecond + )?; + if self.0.time_zone == Self::UNSPECIFIED_TIMEZONE { + write!(f, ", Timezone=local")?; + } else { + write!(f, ", Timezone={}", self.0.time_zone)?; + } + write!(f, ", Daylight={:?}", self.0.daylight) + } +} + +impl Display for Time { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Error returned from failing to convert a byte slice into a [`Time`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TimeByteConversionError { + /// One or more fields of the converted [`Time`] is invalid. + InvalidFields(TimeError), + /// The byte slice is not large enough to hold a [`Time`]. + InvalidSize, +} + +impl Display for TimeByteConversionError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::InvalidFields(error) => write!(f, "{error}"), + Self::InvalidSize => write!( + f, + "the byte slice is not large enough to hold a Time struct" + ), + } + } +} + +impl TryFrom<&[u8]> for Time { + type Error = TimeByteConversionError; + + fn try_from(bytes: &[u8]) -> core::result::Result { + if size_of::() <= bytes.len() { + let year = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); + let month = bytes[2]; + let day = bytes[3]; + let hour = bytes[4]; + let minute = bytes[5]; + let second = bytes[6]; + let nanosecond = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); + let time_zone = match i16::from_le_bytes(bytes[12..14].try_into().unwrap()) { + Self::UNSPECIFIED_TIMEZONE => None, + num => Some(num), + }; + let daylight = Daylight::from_bits(bytes[14]).ok_or( + TimeByteConversionError::InvalidFields(TimeError { + daylight: true, + ..Default::default() + }), + )?; + + let time_params = TimeParams { + year, + month, + day, + hour, + minute, + second, + nanosecond, + time_zone, + daylight, + }; + + Self::new(time_params).map_err(TimeByteConversionError::InvalidFields) + } else { + Err(TimeByteConversionError::InvalidSize) + } + } +} + +/// Unique key for a variable. +#[cfg(feature = "alloc")] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct VariableKey { + pub(crate) name: Vec, + /// Unique identifier for the vendor. + pub vendor: VariableVendor, +} + +#[cfg(feature = "alloc")] +impl VariableKey { + /// Name of the variable. + pub fn name(&self) -> core::result::Result<&CStr16, crate::data_types::FromSliceWithNulError> { + CStr16::from_u16_with_nul(&self.name) + } +} + +#[cfg(feature = "alloc")] +impl Display for VariableKey { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "VariableKey {{ name: ")?; + + match self.name() { + Ok(name) => write!(f, "\"{name}\"")?, + Err(err) => write!(f, "Err({err:?})")?, + } + + write!(f, ", vendor: ")?; + + if self.vendor == VariableVendor::GLOBAL_VARIABLE { + write!(f, "GLOBAL_VARIABLE")?; + } else { + write!(f, "{}", self.vendor.0)?; + } + + write!(f, " }}") + } +} + +/// Information about UEFI variable storage space returned by +/// [`query_variable_info`]. Note that the data here is +/// limited to a specific type of variable (as specified by the +/// `attributes` argument to `query_variable_info`). +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct VariableStorageInfo { + /// Maximum size in bytes of the storage space available for + /// variables of the specified type. + pub maximum_variable_storage_size: u64, + + /// Remaining size in bytes of the storage space available for + /// variables of the specified type. + pub remaining_variable_storage_size: u64, + + /// Maximum size of an individual variable of the specified type. + pub maximum_variable_size: u64, +} + +/// Information about UEFI variable storage space returned by +/// [`query_capsule_capabilities`]. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CapsuleInfo { + /// The maximum size in bytes that [`update_capsule`] + /// can support as input. Note that the size of an update capsule is composed of + /// all [`CapsuleHeader`]s and [CapsuleBlockDescriptor]s. + pub maximum_capsule_size: u64, + + /// The type of reset required for the capsule update. + pub reset_type: ResetType, +} diff --git a/uefi/src/table/runtime.rs b/uefi/src/table/runtime.rs index 0012206ee..5b14808a3 100644 --- a/uefi/src/table/runtime.rs +++ b/uefi/src/table/runtime.rs @@ -1,5 +1,8 @@ //! UEFI services available at runtime, even after the OS boots. +pub use crate::runtime::{ + CapsuleInfo, Time, TimeByteConversionError, TimeError, TimeParams, VariableStorageInfo, +}; pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader}; pub use uefi_raw::table::runtime::{ ResetType, TimeCapabilities, VariableAttributes, VariableVendor, @@ -7,14 +10,15 @@ pub use uefi_raw::table::runtime::{ pub use uefi_raw::time::Daylight; pub use uefi_raw::PhysicalAddress; +#[cfg(feature = "alloc")] +pub use crate::runtime::VariableKey; + use super::Revision; use crate::{CStr16, Error, Result, Status, StatusExt}; -use core::fmt::{self, Debug, Display, Formatter}; -use core::mem::{size_of, MaybeUninit}; +use core::mem::MaybeUninit; use core::ptr; #[cfg(feature = "alloc")] use { - crate::data_types::FromSliceWithNulError, crate::Guid, alloc::boxed::Box, alloc::{vec, vec::Vec}, @@ -331,388 +335,6 @@ impl super::Table for RuntimeServices { const SIGNATURE: u64 = 0x5652_4553_544e_5552; } -/// Date and time representation. -#[derive(Copy, Clone, Eq, PartialEq)] -#[repr(transparent)] -pub struct Time(uefi_raw::time::Time); - -/// Input parameters for [`Time::new`]. -#[derive(Copy, Clone, Debug)] -pub struct TimeParams { - /// Year in the range `1900..=9999`. - pub year: u16, - - /// Month in the range `1..=12`. - pub month: u8, - - /// Day in the range `1..=31`. - pub day: u8, - - /// Hour in the range `0.=23`. - pub hour: u8, - - /// Minute in the range `0..=59`. - pub minute: u8, - - /// Second in the range `0..=59`. - pub second: u8, - - /// Fraction of a second represented as nanoseconds in the range - /// `0..=999_999_999`. - pub nanosecond: u32, - - /// Offset in minutes from UTC in the range `-1440..=1440`, or - /// local time if `None`. - pub time_zone: Option, - - /// Daylight savings time information. - pub daylight: Daylight, -} - -/// Error returned by [`Time`] methods. A bool value of `true` means -/// the specified field is outside its valid range. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct TimeError { - pub year: bool, - pub month: bool, - pub day: bool, - pub hour: bool, - pub minute: bool, - pub second: bool, - pub nanosecond: bool, - pub timezone: bool, - pub daylight: bool, -} - -#[cfg(feature = "unstable")] -impl core::error::Error for TimeError {} - -impl Display for TimeError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.year { - writeln!(f, "year not within `1900..=9999`")?; - } - if self.month { - writeln!(f, "month not within `1..=12")?; - } - if self.day { - writeln!(f, "day not within `1..=31`")?; - } - if self.hour { - writeln!(f, "hour not within `0..=23`")?; - } - if self.minute { - writeln!(f, "minute not within `0..=59`")?; - } - if self.second { - writeln!(f, "second not within `0..=59`")?; - } - if self.nanosecond { - writeln!(f, "nanosecond not within `0..=999_999_999`")?; - } - if self.timezone { - writeln!( - f, - "time_zone not `Time::UNSPECIFIED_TIMEZONE` nor within `-1440..=1440`" - )?; - } - if self.daylight { - writeln!(f, "unknown bits set for daylight")?; - } - Ok(()) - } -} - -impl Time { - /// Unspecified Timezone/local time. - const UNSPECIFIED_TIMEZONE: i16 = uefi_raw::time::Time::UNSPECIFIED_TIMEZONE; - - /// Create a `Time` value. If a field is not in the valid range, - /// [`TimeError`] is returned. - pub fn new(params: TimeParams) -> core::result::Result { - let time = Self(uefi_raw::time::Time { - year: params.year, - month: params.month, - day: params.day, - hour: params.hour, - minute: params.minute, - second: params.second, - pad1: 0, - nanosecond: params.nanosecond, - time_zone: params.time_zone.unwrap_or(Self::UNSPECIFIED_TIMEZONE), - daylight: params.daylight, - pad2: 0, - }); - - time.is_valid().map(|_| time) - } - - /// Create an invalid `Time` with all fields set to zero. This can - /// be used with [`FileInfo`] to indicate a field should not be - /// updated when calling [`File::set_info`]. - /// - /// [`FileInfo`]: uefi::proto::media::file::FileInfo - /// [`File::set_info`]: uefi::proto::media::file::File::set_info - #[must_use] - pub const fn invalid() -> Self { - Self(uefi_raw::time::Time::invalid()) - } - - /// `Ok()` if all fields are within valid ranges, `Err(TimeError)` otherwise. - pub fn is_valid(&self) -> core::result::Result<(), TimeError> { - let mut err = TimeError::default(); - if !(1900..=9999).contains(&self.year()) { - err.year = true; - } - if !(1..=12).contains(&self.month()) { - err.month = true; - } - if !(1..=31).contains(&self.day()) { - err.day = true; - } - if self.hour() > 23 { - err.hour = true; - } - if self.minute() > 59 { - err.minute = true; - } - if self.second() > 59 { - err.second = true; - } - if self.nanosecond() > 999_999_999 { - err.nanosecond = true; - } - if self.time_zone().is_some() && !((-1440..=1440).contains(&self.time_zone().unwrap())) { - err.timezone = true; - } - // All fields are false, i.e., within their valid range. - if err == TimeError::default() { - Ok(()) - } else { - Err(err) - } - } - - /// Query the year. - #[must_use] - pub const fn year(&self) -> u16 { - self.0.year - } - - /// Query the month. - #[must_use] - pub const fn month(&self) -> u8 { - self.0.month - } - - /// Query the day. - #[must_use] - pub const fn day(&self) -> u8 { - self.0.day - } - - /// Query the hour. - #[must_use] - pub const fn hour(&self) -> u8 { - self.0.hour - } - - /// Query the minute. - #[must_use] - pub const fn minute(&self) -> u8 { - self.0.minute - } - - /// Query the second. - #[must_use] - pub const fn second(&self) -> u8 { - self.0.second - } - - /// Query the nanosecond. - #[must_use] - pub const fn nanosecond(&self) -> u32 { - self.0.nanosecond - } - - /// Query the time offset in minutes from UTC, or None if using local time. - #[must_use] - pub const fn time_zone(&self) -> Option { - if self.0.time_zone == Self::UNSPECIFIED_TIMEZONE { - None - } else { - Some(self.0.time_zone) - } - } - - /// Query the daylight savings time information. - #[must_use] - pub const fn daylight(&self) -> Daylight { - self.0.daylight - } -} - -impl Debug for Time { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!( - f, - "{:04}-{:02}-{:02} ", - self.0.year, self.0.month, self.0.day - )?; - write!( - f, - "{:02}:{:02}:{:02}.{:09}", - self.0.hour, self.0.minute, self.0.second, self.0.nanosecond - )?; - if self.0.time_zone == Self::UNSPECIFIED_TIMEZONE { - write!(f, ", Timezone=local")?; - } else { - write!(f, ", Timezone={}", self.0.time_zone)?; - } - write!(f, ", Daylight={:?}", self.0.daylight) - } -} - -impl Display for Time { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/// Error returned from failing to convert a byte slice into a [`Time`]. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TimeByteConversionError { - /// One or more fields of the converted [`Time`] is invalid. - InvalidFields(TimeError), - /// The byte slice is not large enough to hold a [`Time`]. - InvalidSize, -} - -impl Display for TimeByteConversionError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::InvalidFields(error) => write!(f, "{error}"), - Self::InvalidSize => write!( - f, - "the byte slice is not large enough to hold a Time struct" - ), - } - } -} - -impl TryFrom<&[u8]> for Time { - type Error = TimeByteConversionError; - - fn try_from(bytes: &[u8]) -> core::result::Result { - if size_of::() <= bytes.len() { - let year = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); - let month = bytes[2]; - let day = bytes[3]; - let hour = bytes[4]; - let minute = bytes[5]; - let second = bytes[6]; - let nanosecond = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); - let time_zone = match i16::from_le_bytes(bytes[12..14].try_into().unwrap()) { - Self::UNSPECIFIED_TIMEZONE => None, - num => Some(num), - }; - let daylight = Daylight::from_bits(bytes[14]).ok_or( - TimeByteConversionError::InvalidFields(TimeError { - daylight: true, - ..Default::default() - }), - )?; - - let time_params = TimeParams { - year, - month, - day, - hour, - minute, - second, - nanosecond, - time_zone, - daylight, - }; - - Self::new(time_params).map_err(TimeByteConversionError::InvalidFields) - } else { - Err(TimeByteConversionError::InvalidSize) - } - } -} - -/// Unique key for a variable. -#[cfg(feature = "alloc")] -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct VariableKey { - pub(crate) name: Vec, - /// Unique identifier for the vendor. - pub vendor: VariableVendor, -} - -#[cfg(feature = "alloc")] -impl VariableKey { - /// Name of the variable. - pub fn name(&self) -> core::result::Result<&CStr16, FromSliceWithNulError> { - CStr16::from_u16_with_nul(&self.name) - } -} - -#[cfg(feature = "alloc")] -impl Display for VariableKey { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "VariableKey {{ name: ")?; - - match self.name() { - Ok(name) => write!(f, "\"{name}\"")?, - Err(err) => write!(f, "Err({err:?})")?, - } - - write!(f, ", vendor: ")?; - - if self.vendor == VariableVendor::GLOBAL_VARIABLE { - write!(f, "GLOBAL_VARIABLE")?; - } else { - write!(f, "{}", self.vendor.0)?; - } - - write!(f, " }}") - } -} - -/// Information about UEFI variable storage space returned by -/// [`RuntimeServices::query_variable_info`]. Note that the data here is -/// limited to a specific type of variable (as specified by the -/// `attributes` argument to `query_variable_info`). -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct VariableStorageInfo { - /// Maximum size in bytes of the storage space available for - /// variables of the specified type. - pub maximum_variable_storage_size: u64, - - /// Remaining size in bytes of the storage space available for - /// variables of the specified type. - pub remaining_variable_storage_size: u64, - - /// Maximum size of an individual variable of the specified type. - pub maximum_variable_size: u64, -} - -/// Information about UEFI variable storage space returned by -/// [`RuntimeServices::query_capsule_capabilities`]. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct CapsuleInfo { - /// The maximum size in bytes that [`RuntimeServices::update_capsule`] - /// can support as input. Note that the size of an update capsule is composed of - /// all [`CapsuleHeader`]s and [CapsuleBlockDescriptor]s. - pub maximum_capsule_size: u64, - - /// The type of reset required for the capsule update. - pub reset_type: ResetType, -} - #[cfg(test)] mod tests { use super::*;