Skip to content

uefi: Add HiiKeywordHandler and HiiConfigAccess protocol #1684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions uefi-raw/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
## Added
- Added `AllocateType`.
- Added `PciRootBridgeIoProtocol`.
- Added `ConfigKeywordHandlerProtocol`.
- Added `HiiConfigAccessProtocol`.


# uefi-raw - 0.11.0 (2025-05-04)
174 changes: 174 additions & 0 deletions uefi-raw/src/protocol/hii/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Bindings for HII protocols relating to system configuration.
use core::fmt::Debug;

use crate::{Char16, Guid, Status, guid};

/// EFI_CONFIG_KEYWORD_HANDLER_PROTOCOL
#[derive(Debug)]
#[repr(C)]
pub struct ConfigKeywordHandlerProtocol {
pub set_data: unsafe extern "efiapi" fn(
this: *mut Self,
keyword_string: *const Char16,
progress: *mut *const Char16,
progress_err: *mut u32,
) -> Status,
pub get_data: unsafe extern "efiapi" fn(
this: *const Self,
namespace_id: *const Char16,
keyword_string: *const Char16,
progress: *mut *const Char16,
progress_err: *mut u32,
results: *mut *const Char16,
) -> Status,
}

impl ConfigKeywordHandlerProtocol {
pub const GUID: Guid = guid!("0a8badd5-03b8-4d19-b128-7b8f0edaa596");
}

newtype_enum! {
/// Type of action taken by the form browser
#[derive(Default)]
pub enum EfiBrowserAction: u32 => {
/// Called before the browser changes the value in the display (for questions which have a value)
/// or takes an action (in the case of an action button or cross-reference).
/// If EFI_SUCCESS is returned, the browser uses the value returned by Callback().
CHANGING = 0,
/// Called after the browser has changed its internal copy of the question value and displayed it (if appropriate).
/// For action buttons, this is called after processing. Errors are ignored.
CHANGED = 1,
/// Called after the browser has read the current question value but before displaying it.
/// If EFI_SUCCESS is returned, the updated value is used.
RETRIEVE = 2,
/// Called for each question on a form prior to its value being retrieved or displayed.
/// If a question appears on more than one form, this may be called more than once.
FORM_OPEN = 3,
/// Called for each question on a form after processing any submit actions for that form.
/// If a question appears on multiple forms, this will be called more than once.
FORM_CLOSE = 4,
/// Called after the browser submits the modified question value.
/// ActionRequest is ignored.
SUBMITTED = 5,
/// Represents the standard default action, selecting a default value based on lower-priority methods.
DEFAULT_STANDARD = 0x1000,
/// Represents the manufacturing default action, selecting a default value relevant to manufacturing.
DEFAULT_MANUFACTURING = 0x1001,
/// Represents the safe default action, selecting the safest possible default value.
DEFAULT_SAFE = 0x1002,
/// Represents platform-defined default values within a range of possible store identifiers.
DEFAULT_PLATFORM = 0x2000,
/// Represents hardware-defined default values within a range of possible store identifiers.
DEFAULT_HARDWARE = 0x3000,
/// Represents firmware-defined default values within a range of possible store identifiers.
DEFAULT_FIRMWARE = 0x4000,
}
}

newtype_enum! {
/// Represents actions requested by the Forms Browser in response to user interactions.
#[derive(Default)]
pub enum EfiBrowserActionRequest: usize => {
/// No special behavior is taken by the Forms Browser.
NONE = 0,
/// The Forms Browser will exit and request the platform to reset.
RESET = 1,
/// The Forms Browser will save all modified question values to storage and exit.
SUBMIT = 2,
/// The Forms Browser will discard all modified question values and exit.
EXIT = 3,
/// The Forms Browser will write all modified question values on the selected form to storage and exit the form.
FORM_SUBMIT_EXIT = 4,
/// The Forms Browser will discard the modified question values on the selected form and exit the form.
FORM_DISCARD_EXIT = 5,
/// The Forms Browser will write all modified current question values on the selected form to storage.
FORM_APPLY = 6,
/// The Forms Browser will discard the current question values on the selected form and replace them with the original values.
FORM_DISCARD = 7,
/// The user performed a hardware or software configuration change, requiring controller reconnection.
/// The Forms Browser calls `DisconnectController()` followed by `ConnectController()`.
RECONNECT = 8,
/// The Forms Browser will write the current modified question value on the selected form to storage.
QUESTION_APPLY = 9,
}
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EfiHiiTime {
pub hour: u8,
pub minute: u8,
pub second: u8,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EfiHiiDate {
pub year: u16,
pub month: u8,
pub day: u8,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EfiHiiRef {
pub question_id: EfiQuestionId,
pub form_id: EfiFormId,
pub guid: Guid,
pub string_id: EfiStringId,
}

#[repr(C)]
#[derive(Copy, Clone)]
pub union EfiIfrTypeValue {
pub u8: u8, // EFI_IFR_TYPE_NUM_SIZE_8
pub u16: u16, // EFI_IFR_TYPE_NUM_SIZE_16
pub u32: u32, // EFI_IFR_TYPE_NUM_SIZE_32
pub u64: u64, // EFI_IFR_TYPE_NUM_SIZE_64
pub b: bool, // EFI_IFR_TYPE_BOOLEAN
pub time: EfiHiiTime, // EFI_IFR_TYPE_TIME
pub date: EfiHiiDate, // EFI_IFR_TYPE_DATE
pub string: EfiStringId, // EFI_IFR_TYPE_STRING, EFI_IFR_TYPE_ACTION
pub hii_ref: EfiHiiRef, // EFI_IFR_TYPE_REF
}
impl core::fmt::Debug for EfiIfrTypeValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EfiIfrTypeValue").finish()
}
}

pub type EfiQuestionId = u16;
pub type EfiFormId = u16;
pub type EfiStringId = u16;

/// EFI_HII_CONFIG_ACCESS_PROTOCOL
#[derive(Debug)]
#[repr(C)]
pub struct HiiConfigAccessProtocol {
pub extract_config: unsafe extern "efiapi" fn(
this: *const Self,
request: *const Char16,
progress: *mut *const Char16,
results: *mut *const Char16,
) -> Status,
pub route_config: unsafe extern "efiapi" fn(
this: *const Self,
configuration: *const Char16,
progress: *mut *const Char16,
) -> Status,
pub callback: unsafe extern "efiapi" fn(
this: *const Self,
action: EfiBrowserAction,
question_id: u16,
value_type: u8,
value: *mut EfiIfrTypeValue,
action_request: *mut EfiBrowserActionRequest,
) -> Status,
}

impl HiiConfigAccessProtocol {
pub const GUID: Guid = guid!("330d4706-f2a0-4e4f-a369-b66fa8d54385");
}
1 change: 1 addition & 0 deletions uefi-raw/src/protocol/hii/mod.rs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

//! HII Protocols
pub mod config;
pub mod database;

use crate::{Char16, Guid};
3 changes: 3 additions & 0 deletions uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@
- Added `ConfigTableEntry::MEMORY_ATTRIBUTES_GUID` and `ConfigTableEntry::IMAGE_SECURITY_DATABASE_GUID`.
- Added `proto::usb::io::UsbIo`.
- Added `proto::pci::PciRootBridgeIo`.
- Added `proto::hii::config::ConfigKeywordHandler`.
- Added `proto::hii::config::HiiConfigAccess`.
- Added `proto::hii::config_str::ConfigurationString`.

## Changed
- **Breaking:** `boot::stall` now take `core::time::Duration` instead of `usize`.
30 changes: 30 additions & 0 deletions uefi/src/proto/hii/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! HII Configuration protocols.
use uefi_macros::unsafe_protocol;
use uefi_raw::protocol::hii::config::{ConfigKeywordHandlerProtocol, HiiConfigAccessProtocol};

/// The HII Keyword Handler Protocol.
///
/// # UEFI Spec Description
///
/// This protocol provides the mechanism to set and get the values associated
/// with a keyword exposed through a x-UEFI- prefixed configuration language namespace.
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(ConfigKeywordHandlerProtocol::GUID)]
pub struct ConfigKeywordHandler(ConfigKeywordHandlerProtocol);

/// The HII Configuration Access Protocol.
///
/// # UEFI Spec Description
///
/// This protocol is responsible for facilitating access to configuration data from HII.
/// It is typically invoked by the HII Configuration Routing Protocol for handling
/// configuration requests. Forms browsers also interact with this protocol through
/// the `Callback()` function.
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(HiiConfigAccessProtocol::GUID)]
pub struct HiiConfigAccess(HiiConfigAccessProtocol);
261 changes: 261 additions & 0 deletions uefi/src/proto/hii/config_str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! UEFI Configuration String parsing according to Spec 35.2.1
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::slice;
use core::str::{self, FromStr};
use uguid::Guid;

use crate::proto::device_path::DevicePath;
use crate::{CStr16, Char16};

/// A helper struct to split and parse a UEFI Configuration String.
///
/// Configuration strings consist of key-value pairs separated by `&`. Keys
/// and values are separated by `=`. This struct provides an iterator for
/// easy traversal of the key-value pairs.
///
/// For reasons of developer sanity, this is operating on &str instead of &CStr16.
#[derive(Debug)]
pub struct ConfigurationStringSplitter<'a> {
bfr: &'a str,
}

impl<'a> ConfigurationStringSplitter<'a> {
/// Creates a new splitter instance for a given configuration string buffer.
#[must_use]
pub const fn new(bfr: &'a str) -> Self {
Self { bfr }
}
}

impl<'a> Iterator for ConfigurationStringSplitter<'a> {
type Item = (&'a str, Option<&'a str>);

fn next(&mut self) -> Option<Self::Item> {
if self.bfr.is_empty() {
return None;
}
let (keyval, remainder) = self
.bfr
.split_once('&')
.unwrap_or((self.bfr, &self.bfr[0..0]));
self.bfr = remainder;
let (key, value) = keyval
.split_once('=')
.map(|(key, value)| (key, Some(value)))
.unwrap_or((keyval, None));
Some((key, value))
}
}

/// Enum representing different sections of a UEFI Configuration Header.
///
/// These sections include GUID, Name, and Path elements, which provide
/// routing and identification information for UEFI components.
#[derive(Debug, PartialEq, Eq)]
pub enum ConfigHdrSection {
/// UEFI ConfigurationString {GuidHdr} element
Guid,
/// UEFI ConfigurationString {NameHdr} element
Name,
/// UEFI ConfigurationString {PathHdr} element
Path,
}

/// Enum representing possible parsing errors encountered when processing
/// UEFI Configuration Strings.
#[derive(Debug)]
pub enum ParseError {
/// Error while parsing the UEFI {ConfigHdr} configuration string section.
ConfigHdr(ConfigHdrSection),
/// Error while parsing the UEFI {BlockName} configuration string section.
BlockName,
/// Error while parsing the UEFI {BlockConfig} configuration string section.
BlockConfig,
}

/// Represents an individual element within a UEFI Configuration String.
///
/// Each element contains an offset, width, and value, defining the data
/// stored at specific memory locations within the configuration.
#[derive(Debug, Default)]
pub struct ConfigurationStringElement {
/// Byte offset in the configuration block
pub offset: u64,
/// Length of the value starting at offset
pub width: u64,
/// Value bytes
pub value: Vec<u8>,
// TODO
// nvconfig: HashMap<String, Vec<u8>>,
}

/// A full UEFI Configuration String representation.
///
/// This structure contains routing information such as GUID and device path,
/// along with the parsed configuration elements.
#[derive(Debug)]
pub struct ConfigurationString {
/// GUID used for identifying the configuration
pub guid: Guid,
/// Name field (optional identifier)
pub name: String,
/// Associated UEFI device path
pub device_path: Box<DevicePath>,
/// Parsed UEFI {ConfigElement} sections
pub elements: Vec<ConfigurationStringElement>,
}

impl ConfigurationString {
fn try_parse_with<T, F: FnOnce() -> Option<T>>(
err: ParseError,
parse_fn: F,
) -> Result<T, ParseError> {
parse_fn().ok_or(err)
}

/// Parses a hexadecimal string into an iterator of bytes.
///
/// # Arguments
///
/// * `hex` - The hexadecimal string representing binary data.
///
/// # Returns
///
/// An iterator over bytes.
pub fn parse_bytes_from_hex(hex: &str) -> impl Iterator<Item = u8> {
hex.as_bytes().chunks(2).map(|chunk| {
let chunk = str::from_utf8(chunk).unwrap_or_default();
u8::from_str_radix(chunk, 16).unwrap_or_default()
})
}

/// Converts a hexadecimal string representation into a numeric value.
///
/// # Arguments
///
/// * `data` - The hexadecimal string to convert.
///
/// # Returns
///
/// An `Option<u64>` representing the parsed number.
#[must_use]
pub fn parse_number_from_hex(data: &str) -> Option<u64> {
let data: Vec<_> = Self::parse_bytes_from_hex(data).collect();
match data.len() {
8 => Some(u64::from_be_bytes(data.try_into().unwrap())),
4 => Some(u32::from_be_bytes(data.try_into().unwrap()) as u64),
2 => Some(u16::from_be_bytes(data.try_into().unwrap()) as u64),
1 => Some(u8::from_be_bytes(data.try_into().unwrap()) as u64),
_ => None,
}
}

/// Converts a hexadecimal string into a UTF-16 string.
///
/// # Arguments
///
/// * `data` - The hexadecimal representation of a string.
///
/// # Returns
///
/// An `Option<String>` containing the parsed string.
#[must_use]
pub fn parse_string_from_hex(data: &str) -> Option<String> {
if data.len() % 2 != 0 {
return None;
}
let mut data: Vec<_> = Self::parse_bytes_from_hex(data).collect();
data.chunks_exact_mut(2).for_each(|c| c.swap(0, 1));
data.extend_from_slice(&[0, 0]);
let data: &[Char16] =
unsafe { slice::from_raw_parts(data.as_slice().as_ptr().cast(), data.len() / 2) };
Some(CStr16::from_char16_with_nul(data).ok()?.to_string())
}

/// Parses a hexadecimal string into a UEFI GUID.
///
/// # Arguments
///
/// * `data` - The hexadecimal GUID representation.
///
/// # Returns
///
/// An `Option<Guid>` containing the parsed GUID.
#[must_use]
pub fn parse_guid_from_hex(data: &str) -> Option<Guid> {
let v: Vec<_> = Self::parse_bytes_from_hex(data).collect();
Some(Guid::from_bytes(v.try_into().ok()?))
}
}

impl FromStr for ConfigurationString {
type Err = ParseError;

fn from_str(bfr: &str) -> Result<Self, Self::Err> {
let mut splitter = ConfigurationStringSplitter::new(bfr).peekable();

let guid = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Guid), || {
let v = splitter.next()?;
let v = (v.0 == "GUID").then_some(v.1).flatten()?;
Self::parse_guid_from_hex(v)
})?;
let name = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Name), || {
let v = splitter.next()?;
let v = (v.0 == "NAME").then_some(v.1).flatten()?;
Self::parse_string_from_hex(v)
})?;
let device_path =
Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Path), || {
let v = splitter.next()?.1?;
let v: Vec<_> = Self::parse_bytes_from_hex(v).collect();
let v = <&DevicePath>::try_from(v.as_slice()).ok()?;
Some(v.to_boxed())
})?;

let mut elements = Vec::new();
loop {
let offset = match splitter.next() {
Some(("OFFSET", Some(data))) => {
Self::parse_number_from_hex(data).ok_or(ParseError::BlockName)?
}
None => break,
_ => return Err(ParseError::BlockName),
};
let width = match splitter.next() {
Some(("WIDTH", Some(data))) => {
Self::parse_number_from_hex(data).ok_or(ParseError::BlockName)?
}
_ => return Err(ParseError::BlockName),
};
let value = match splitter.next() {
Some(("VALUE", Some(data))) => Self::parse_bytes_from_hex(data).collect(),
_ => return Err(ParseError::BlockConfig),
};

while let Some(next) = splitter.peek() {
if next.0 == "OFFSET" {
break;
}
let _ = splitter.next(); // drop nvconfig entries for now
}

elements.push(ConfigurationStringElement {
offset,
width,
value,
});
}

Ok(Self {
guid,
name,
device_path,
elements,
})
}
}
7 changes: 7 additions & 0 deletions uefi/src/proto/hii/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! HII Protocols
pub mod config;
#[cfg(feature = "alloc")]
pub mod config_str;
1 change: 1 addition & 0 deletions uefi/src/proto/mod.rs
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ pub mod console;
pub mod debug;
pub mod device_path;
pub mod driver;
pub mod hii;
pub mod loaded_image;
pub mod media;
pub mod misc;