diff --git a/Cargo.toml b/Cargo.toml index 30387adb9..5dc2cbc47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ patina_mtrr = { version = "1.0.0" } patina_paging = { version = "^9.0.3" } patina_performance = { version = "12.1.0", path = "components/patina_performance" } patina_stacktrace = { version = "12.1.0", path = "core/patina_stacktrace" } +patina_uefi_services = { path = "components/patina_uefi_services" } proc-macro2 = { version = "1" } quote = { version = "1" } r-efi = { version = "5.0.0", default-features = false } diff --git a/components/patina_samples/Cargo.toml b/components/patina_samples/Cargo.toml index 47e84b197..be8bb71bf 100644 --- a/components/patina_samples/Cargo.toml +++ b/components/patina_samples/Cargo.toml @@ -10,6 +10,11 @@ description = "Sample UEFI components for the DXE Core." [dependencies] log = { workspace = true } patina = { workspace = true } +patina_uefi_services = { workspace = true } +r-efi = { workspace = true } + +[dev-dependencies] +patina_uefi_services = { workspace = true, features = ["mockall"] } [features] std = [] diff --git a/components/patina_samples/src/component.rs b/components/patina_samples/src/component.rs index 951ff24d2..5b2cb464e 100644 --- a/components/patina_samples/src/component.rs +++ b/components/patina_samples/src/component.rs @@ -1,12 +1,16 @@ -//! Sample Component Implementations +//! Sample Patina Components //! -//! This module contains example component implementations demonstrating various -//! Patina component patterns. -//! -//! ## License +//! This module contains example components demonstrating various Patina patterns and services. //! //! Copyright (c) Microsoft Corporation. //! //! SPDX-License-Identifier: Apache-2.0 -//! + +pub mod console_usage; +pub mod event_usage; pub mod hello_world; +pub mod image_usage; +pub mod misc_usage; +pub mod protocol_usage; +pub mod runtime_usage; +pub mod variable_usage; diff --git a/components/patina_samples/src/component/console_usage.rs b/components/patina_samples/src/component/console_usage.rs new file mode 100644 index 000000000..007013dd9 --- /dev/null +++ b/components/patina_samples/src/component/console_usage.rs @@ -0,0 +1,142 @@ +//! Console Services Usage Examples +//! +//! This module demonstrates how to use ConsoleServices for text output and console management. +//! +//! ## Examples Included +//! +//! - **BasicConsoleOutput**: Simple text output to console +//! - **FormattedConsoleOutput**: Formatted output with cursor positioning +//! - **InteractiveConsole**: Console manipulation (clear, cursor control) +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +extern crate alloc; + +use patina::{ + component::{IntoComponent, service::Service}, + error::Result, +}; +use patina_uefi_services::service::console::ConsoleServices; + +/// Example component demonstrating basic console output. +/// +/// This component shows the simplest use case: writing text to the console. +/// Note that the ConsoleServices dependency is requested via the component's entry point. +#[derive(IntoComponent)] +pub struct BasicConsoleOutput; + +impl BasicConsoleOutput { + /// Component entry point that outputs a simple message to the console. + /// + /// # Arguments + /// + /// * `console` - The console services for text output + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, console: Service) -> Result<()> { + console.output_string("Hello from Patina Console Services!\r\n")?; + console.output_string("This is a basic console output example.\r\n")?; + + Ok(()) + } +} + +/// Example component demonstrating formatted console output with cursor positioning. +/// +/// This component shows how to use cursor positioning and formatted output. +#[derive(IntoComponent)] +pub struct FormattedConsoleOutput; + +impl FormattedConsoleOutput { + /// Component entry point that demonstrates formatted console output. + /// + /// # Arguments + /// + /// * `console` - The console services for text output and cursor control + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, console: Service) -> Result<()> { + // Output a header + console.output_string("\r\n=== Formatted Console Example ===\r\n\r\n")?; + + // Create a simple table with cursor positioning + console.set_cursor_position(0, 5)?; + console.output_string("Column 1")?; + + console.set_cursor_position(20, 5)?; + console.output_string("Column 2")?; + + console.set_cursor_position(40, 5)?; + console.output_string("Column 3")?; + + // Add data rows + console.set_cursor_position(0, 6)?; + console.output_string("Data A")?; + + console.set_cursor_position(20, 6)?; + console.output_string("Data B")?; + + console.set_cursor_position(40, 6)?; + console.output_string("Data C")?; + + // Get final cursor position to show where we ended up + let (_col, row) = console.get_cursor_position()?; + console.set_cursor_position(0, row + 2)?; + console.output_string("Table complete!\r\n")?; + + Ok(()) + } +} + +/// Example component demonstrating interactive console manipulation. +/// +/// This component shows how to use console management features like +/// clearing the screen, cursor visibility, and positioning. +#[derive(IntoComponent)] +pub struct InteractiveConsole; + +impl InteractiveConsole { + /// Component entry point that demonstrates console manipulation. + /// + /// # Arguments + /// + /// * `console` - The console services for full console control + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, console: Service) -> Result<()> { + // Clear the screen first + console.clear_screen()?; + + // Show cursor manipulation + console.output_string("Demonstrating cursor control...\r\n")?; + + // Hide cursor + console.enable_cursor(false)?; + console.output_string("Cursor is now hidden\r\n")?; + + // Show cursor again + console.enable_cursor(true)?; + console.output_string("Cursor is now visible\r\n")?; + + // Draw a simple pattern using cursor positioning + console.output_string("\r\nDrawing a pattern:\r\n\r\n")?; + for i in 0..5 { + console.set_cursor_position(i * 4, 10 + i)?; + console.output_string("*")?; + } + + // Return cursor to bottom + console.set_cursor_position(0, 16)?; + console.output_string("\r\nConsole demonstration complete!\r\n")?; + + Ok(()) + } +} diff --git a/components/patina_samples/src/component/event_usage.rs b/components/patina_samples/src/component/event_usage.rs new file mode 100644 index 000000000..fb01800e1 --- /dev/null +++ b/components/patina_samples/src/component/event_usage.rs @@ -0,0 +1,278 @@ +//! Event Service Usage Examples +//! +//! Demonstrates usage patterns for UEFI event and timer operations through the +//! [`patina_uefi_services::service::event::EventServices`] trait. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! +use core::time::Duration; +use patina::{ + boot_services::{event::EventType, tpl::Tpl}, + component::{IntoComponent, prelude::Service}, + error::Result, +}; +use patina_uefi_services::service::event::EventServices; + +/// Example component demonstrating basic event creation and signaling patterns. +/// +/// This component creates an event, manually signals it, and waits for it to trigger. +/// It demonstrates the core event lifecycle: create, signal, wait, and close. +/// +/// **Note**: Timer events require a hardware timer architecture protocol to function. +/// This example uses manual signaling which works in all environments. +#[derive(IntoComponent)] +pub struct BasicEventExample; + +impl BasicEventExample { + fn manually_signal_an_event(&self, event_services: &Service) -> Result<()> { + log::info!("Creating an event..."); + + // Create an event with NOTIFY_WAIT flag so it can be used with wait_for_event + let event = event_services.create_event(EventType::NOTIFY_WAIT, Tpl::NOTIFY)?; + + // Verify event is not signaled + match event_services.check_event(event) { + Ok(()) => log::warn!("Event unexpectedly signaled"), + Err(_) => log::info!("Event is correctly not signaled yet"), + } + + log::info!("Manually signaling the event..."); + + // Manually signal the event (in real usage, this might be done by another component or callback) + event_services.signal_event(event)?; + + log::info!("Waiting for the event to be signaled..."); + + // Wait for the event to be signaled + let mut events = [event]; + let index = event_services.wait_for_event(&mut events)?; + + log::info!("The event was signaled (index: {})", index); + + // Clean up the event + event_services.close_event(event)?; + + Ok(()) + } + + fn timer_event(&self, event_services: &Service) -> Result<()> { + log::info!("Creating a timer event..."); + + // Create a timer event at CALLBACK TPL + let event = event_services.create_event(EventType::TIMER, Tpl::CALLBACK)?; + + // Set timer to trigger after 1 second (10,000,000 * 100ns units) + event_services.set_timer(event, patina::boot_services::event::EventTimerType::Relative, 10_000_000)?; + + log::info!("Waiting for the timer event to trigger..."); + + // Wait for the event to be signaled + let mut events = [event]; + let index = event_services.wait_for_event(&mut events)?; + + log::info!("Timer event triggered (index: {})", index); + + Ok(()) + } + + fn entry_point(self, event_services: Service) -> Result<()> { + self.manually_signal_an_event(&event_services)?; + self.timer_event(&event_services)?; + + Ok(()) + } +} + +/// Example component demonstrating multiple event handling and polling patterns. +/// +/// Shows how to create multiple events, poll their status without blocking, +/// and wait for any one of them to signal. +/// +/// **Note**: This example uses timer events which require a hardware timer architecture +/// protocol to be registered in the platform. Without it, `wait_for_event` will hang +/// because timer ticks never occur to signal the events. +#[derive(IntoComponent)] +pub struct MultiEventExample; + +impl MultiEventExample { + fn entry_point(self, event_services: Service) -> Result<()> { + log::info!("Creating multiple timer events..."); + + // Create three timer events with different intervals + // IMPORTANT: Timer events that will be waited on need the NOTIFY_WAIT flag + let event1 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + let event2 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + let event3 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + + // Set different timer intervals (100ms, 200ms, 300ms) + event_services.set_timer( + event1, + patina::boot_services::event::EventTimerType::Relative, + 1_000_000, // 100ms + )?; + event_services.set_timer( + event2, + patina::boot_services::event::EventTimerType::Relative, + 2_000_000, // 200ms + )?; + event_services.set_timer( + event3, + patina::boot_services::event::EventTimerType::Relative, + 3_000_000, // 300ms + )?; + + // Check event status without blocking + match event_services.check_event(event1) { + Ok(()) => log::info!("Event 1 already signaled"), + Err(_) => log::info!("Event 1 not yet signaled"), + } + + // Wait for the first event to trigger + let mut events = [event1, event2, event3]; + let index = event_services.wait_for_event(&mut events)?; + + log::info!("First event to trigger: index {}", index); + + // Clean up all events + event_services.close_event(event1)?; + event_services.close_event(event2)?; + event_services.close_event(event3)?; + + log::info!("Multi-event example completed successfully"); + Ok(()) + } +} + +/// Example component demonstrating a system event (Exit Boot Services) notification. +/// +/// System events are automatically signaled by the firmware at specific milestones +/// (e.g., ExitBootServices, SetVirtualAddressMap). +#[derive(IntoComponent)] +pub struct FirmwareEventExample; + +impl FirmwareEventExample { + fn entry_point(self, event_services: Service) -> Result<()> { + log::info!("Creating a system notification event..."); + + // Create an event with the signal flag + let ebs_event = event_services + .create_system_event(EventType::NOTIFY_SIGNAL | EventType::SIGNAL_EXIT_BOOT_SERVICES, Tpl::NOTIFY)?; + + log::info!("Event created successfully: {:?}", ebs_event); + log::info!("This event will be automatically signaled when ExitBootServices is called"); + + // Check the current state + match event_services.check_event(ebs_event) { + Ok(()) => log::info!("Exit Boot Services event is already signaled"), + Err(_) => log::info!("Exit Boot Services event is not yet signaled (expected before ExitBootServices)"), + } + + // Clean up + event_services.close_event(ebs_event)?; + + log::info!("System event example completed successfully"); + Ok(()) + } +} + +/// Example component demonstrating duration-based timer operations. +/// +/// - Shows how to use the EventServices with Duration for convenient time-based operations. +/// - Demonstrates manual timer creation using different duration units. +#[derive(IntoComponent)] +pub struct DurationBasedTimerExample; + +impl DurationBasedTimerExample { + fn entry_point(self, event_services: Service) -> Result<()> { + log::info!("Starting duration-based timer example..."); + + // Example 1: Create a timer using Duration (100ms) + log::info!("Creating 100ms timer using Duration..."); + let event1 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + let duration1 = Duration::from_millis(100); + let timer_100ns = duration1.as_nanos() / 100; // Convert to 100ns units + event_services.set_timer(event1, patina::boot_services::event::EventTimerType::Relative, timer_100ns as u64)?; + + let mut events = [event1]; + event_services.wait_for_event(&mut events)?; + log::info!("100ms timer completed"); + event_services.close_event(event1)?; + + // Example 2: Create a timer using microseconds (50ms = 50,000μs) + log::info!("Creating 50ms timer using microseconds..."); + let event2 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + let duration2 = Duration::from_micros(50_000); + let timer_100ns = duration2.as_nanos() / 100; + event_services.set_timer(event2, patina::boot_services::event::EventTimerType::Relative, timer_100ns as u64)?; + + let mut events = [event2]; + event_services.wait_for_event(&mut events)?; + log::info!("50ms timer completed"); + event_services.close_event(event2)?; + + // Example 3: Very short timer using microseconds (10ms = 10,000μs) + log::info!("Creating 10ms timer..."); + let event3 = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + let duration3 = Duration::from_micros(10_000); + let timer_100ns = duration3.as_nanos() / 100; + event_services.set_timer(event3, patina::boot_services::event::EventTimerType::Relative, timer_100ns as u64)?; + + let mut events = [event3]; + event_services.wait_for_event(&mut events)?; + log::info!("10ms timer completed"); + event_services.close_event(event3)?; + + log::info!("Duration-based timer example completed successfully"); + log::info!("Note: UEFI timers operate in 100ns units as per the UEFI Specification"); + Ok(()) + } +} + +/// Example component demonstrating periodic timer events. +/// +/// Shows how to set up a periodic timer that triggers repeatedly at regular intervals, +/// useful for polling operations or periodic maintenance tasks. +#[derive(IntoComponent)] +pub struct PeriodicTimerExample; + +impl PeriodicTimerExample { + fn entry_point(self, event_services: Service) -> Result<()> { + log::info!("Creating periodic timer event..."); + + // Create a timer event + // IMPORTANT: Timer events that will be waited on need the NOTIFY_WAIT flag + let event = event_services.create_event(EventType::TIMER | EventType::NOTIFY_WAIT, Tpl::CALLBACK)?; + + // Set as periodic timer (triggers every 500ms) + event_services.set_timer(event, patina::boot_services::event::EventTimerType::Periodic, 5_000_000)?; + + log::info!("Periodic timer set to trigger every 500ms"); + + // Wait for the timer to trigger 3 times + for i in 1..=3 { + let mut events = [event]; + event_services.wait_for_event(&mut events)?; + log::info!("Periodic timer triggered (iteration {})", i); + } + + // Cancel the periodic timer + log::info!("Canceling periodic timer..."); + event_services.set_timer(event, patina::boot_services::event::EventTimerType::Cancel, 0)?; + + // Verify timer is canceled by checking if event is signaled + match event_services.check_event(event) { + Ok(()) => log::info!("Event still signaled after cancel (will clear on next wait)"), + Err(_) => log::info!("Event not signaled after cancel"), + } + + // Close the event now that the demo is done + event_services.close_event(event)?; + + log::info!("Periodic timer example completed successfully"); + Ok(()) + } +} diff --git a/components/patina_samples/src/component/image_usage.rs b/components/patina_samples/src/component/image_usage.rs new file mode 100644 index 000000000..8cac5e1e3 --- /dev/null +++ b/components/patina_samples/src/component/image_usage.rs @@ -0,0 +1,124 @@ +//! Image Services Usage Examples +//! +//! This module demonstrates how to use ImageServices for loading and executing UEFI images. +//! +//! ## Examples Included +//! +//! - **LoadImageFromBuffer**: Loading an image from memory buffer +//! - **LoadAndExecuteImage**: Loading and executing an image +//! +//! ## Safety Note +//! +//! Image services involve loading and executing code, which inherently requires +//! unsafe operations. These examples demonstrate safe patterns for using these services +//! but they are minimal examples of simply using the interfaces available. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +extern crate alloc; + +use alloc::vec::Vec; +use patina::{ + component::{IntoComponent, service::Service}, + error::Result, +}; +use patina_uefi_services::{service::image::ImageServices, types::Handle}; + +/// Example component demonstrating loading an image from a memory buffer. +/// +/// This component shows how to load a UEFI image that is already present +/// in memory (as opposed to loading from a device path). +#[derive(IntoComponent)] +pub struct LoadImageFromBuffer; + +impl LoadImageFromBuffer { + /// Component entry point that loads an image from a buffer. + /// + /// # Arguments + /// + /// * `image_services` - The image services for loading images + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, image_services: Service) -> Result<()> { + // In a real scenario, you would have actual image data here + // For this example, just the API usage pattern is demonstrated + + // Get the current image handle to use as parent + // (In practice, this would come from your component's context) + let parent_handle = Handle::null(); + + // Example: Load an image from a buffer + // In a real implementation, image_buffer would contain a valid PE/COFF image + let image_buffer: Vec = Vec::new(); + let buffer_size = image_buffer.len(); + + // SAFETY: We're passing a null device path and empty buffer for demonstration. + // + // In a real implementation, you would either provide: + // 1. A valid device path (device_path != null) with source_buffer = None, OR + // 2. A null device path with a valid image buffer in source_buffer + let _loaded_image_handle = unsafe { + image_services.load_image( + parent_handle, + core::ptr::null_mut(), // No device path (loading from buffer) + Some(image_buffer), + buffer_size, + ) + }; + + // In a real component, you would check the result and handle the loaded image + // For this example, we're just demonstrating the API pattern + + Ok(()) + } +} + +/// Example component demonstrating loading and executing an image. +/// +/// This component shows the complete workflow of loading an image and +/// then starting its execution. +#[derive(IntoComponent)] +pub struct LoadAndExecuteImage; + +impl LoadAndExecuteImage { + /// Component entry point that loads and executes an image. + /// + /// # Arguments + /// + /// * `image_services` - The image services for loading and executing images + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, image_services: Service) -> Result<()> { + // Get parent image handle (in practice, from component context) + let parent_handle = Handle::null(); + + // Example image buffer (in practice, this would be a valid UEFI image) + let image_buffer: Vec = Vec::new(); + let buffer_size = image_buffer.len(); + + // Step 1: Load the image + // SAFETY: See safety note in LoadImageFromBuffer + let loaded_image_handle = unsafe { + image_services.load_image(parent_handle, core::ptr::null_mut(), Some(image_buffer), buffer_size)? + }; + + // Step 2: Start the image + // This transfers control to the loaded image's entry point + let exit_data = image_services.start_image(loaded_image_handle)?; + + // Step 3: Handle exit data if any was provided + if !exit_data.is_empty() { + // Process exit data from the image + // In a real component, you would interpret this data based on + // your specific requirements + } + + Ok(()) + } +} diff --git a/components/patina_samples/src/component/misc_usage.rs b/components/patina_samples/src/component/misc_usage.rs new file mode 100644 index 000000000..63973cb1b --- /dev/null +++ b/components/patina_samples/src/component/misc_usage.rs @@ -0,0 +1,304 @@ +//! Miscellaneous UEFI Services Usage Examples +//! +//! This module demonstrates how to use various miscellaneous UEFI services including timing, +//! memory utilities, system utilities, and configuration table management. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +extern crate alloc; + +use core::ffi::c_void; +use patina::{BinaryGuid, component::IntoComponent, component::prelude::Service, error::Result}; +use patina_uefi_services::{ + component::misc::{StandardMemoryUtilityServices, StandardSystemUtilityServices}, + service::misc::{ConfigurationServices, MemoryUtilityServices, SystemUtilityServices, TimingServices}, +}; + +// +// Timing Services Examples +// + +/// Basic watchdog timer usage. +/// +/// This component demonstrates how to configure the system watchdog timer +/// to reset the system if it becomes unresponsive. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::WatchdogTimerExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(WatchdogTimerExample); +/// ``` +#[derive(IntoComponent)] +pub struct WatchdogTimerExample; + +impl WatchdogTimerExample { + fn entry_point(self, timing: Service) -> Result<()> { + // Set a 5-minute (300 second) watchdog timer + // The system will reset if we don't complete our work within this time + timing.set_watchdog_timer(300)?; + + // Perform some critical operations... + // (In a real component, this would be your actual work) + + // Disable the watchdog timer when done + timing.set_watchdog_timer(0)?; + + Ok(()) + } +} + +/// Precise delay operations using stall. +/// +/// This component demonstrates how to use the stall function for precise +/// timing delays in microseconds. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::PreciseDelayExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(PreciseDelayExample); +/// ``` +#[derive(IntoComponent)] +pub struct PreciseDelayExample; + +impl PreciseDelayExample { + fn entry_point(self, timing: Service) -> Result<()> { + // Wait 1 millisecond (1000 microseconds) + timing.stall(1000)?; + + // Wait 100 microseconds + timing.stall(100)?; + + // Note: stall is a blocking operation - use sparingly + // For longer delays, consider using event timers instead + + Ok(()) + } +} + +// +// Memory Utility Services Examples +// + +/// Safe memory copying operations. +/// +/// This component demonstrates type-safe memory copying using the MemoryUtilityServices. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::SafeMemoryCopyExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(SafeMemoryCopyExample); +/// ``` +#[derive(IntoComponent)] +pub struct SafeMemoryCopyExample; + +impl SafeMemoryCopyExample { + fn entry_point(self, mem_utils: Service) -> Result<()> { + // Note that `mem_utils.copy_mem()` is a type-safe memory copy + let source = 0x12345678u32; + let mut destination = 0u32; + + mem_utils.copy_mem(&mut destination, &source); + + // Verify the copy + assert_eq!(destination, 0x12345678); + + Ok(()) + } +} + +/// Memory buffer filling operations. +/// +/// This component demonstrates how to fill memory buffers with specific values. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::MemoryFillExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(MemoryFillExample); +/// ``` +#[derive(IntoComponent)] +pub struct MemoryFillExample; + +impl MemoryFillExample { + fn entry_point(self, mem_utils: Service) -> Result<()> { + // Create a buffer and fill it with zeros + let mut buffer = [0xFFu8; 64]; + mem_utils.set_mem(&mut buffer, 0x00); + + // Verify all bytes are zero + assert!(buffer.iter().all(|&b| b == 0x00)); + + // Fill with a different value + mem_utils.set_mem(&mut buffer, 0xAA); + + // Verify all bytes are 0xAA + assert!(buffer.iter().all(|&b| b == 0xAA)); + + Ok(()) + } +} + +// +// System Utility Services Examples +// + +/// Monotonic counter usage for unique identifiers. +/// +/// This component demonstrates how to use the monotonic counter to generate +/// unique sequence numbers during the current boot. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::MonotonicCounterExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(MonotonicCounterExample); +/// ``` +#[derive(IntoComponent)] +pub struct MonotonicCounterExample; + +impl MonotonicCounterExample { + fn entry_point(self, sys_utils: Service) -> Result<()> { + // Get three sequential counter values + let count1 = sys_utils.get_next_monotonic_count()?; + let count2 = sys_utils.get_next_monotonic_count()?; + let count3 = sys_utils.get_next_monotonic_count()?; + + // Verify they are monotonically increasing + assert!(count2 > count1); + assert!(count3 > count2); + + Ok(()) + } +} + +/// CRC32 calculation for data integrity. +/// +/// This component demonstrates how to calculate CRC32 checksums for data +/// integrity verification. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::Crc32CalculationExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(Crc32CalculationExample); +/// ``` +#[derive(IntoComponent)] +pub struct Crc32CalculationExample; + +impl Crc32CalculationExample { + fn entry_point(self, sys_utils: Service) -> Result<()> { + // Calculate CRC32 for a simple value + let data = 0x12345678u32; + let crc = sys_utils.calculate_crc_32(&data)?; + + // Calculate CRC for a larger structure + let complex_data = (0x11223344u32, 0x55667788u32); + let complex_crc = sys_utils.calculate_crc_32(&complex_data)?; + + // CRCs should differ for different data + assert_ne!(crc, complex_crc); + + Ok(()) + } +} + +// +// Configuration Services Examples +// + +/// Installing a configuration table. +/// +/// This component demonstrates how to install a configuration table that can +/// be accessed by other components or the operating system. +/// +/// ## Safety Note +/// +/// Configuration tables must remain valid for the lifetime of the system. +/// The data should be allocated in a persistent memory region. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::ConfigurationTableExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(ConfigurationTableExample); +/// ``` +#[derive(IntoComponent)] +pub struct ConfigurationTableExample; + +impl ConfigurationTableExample { + fn entry_point(self, config: Service) -> Result<()> { + // Define a custom GUID for the configuration table + let custom_guid = + BinaryGuid::from_fields(0x12345678, 0x1234, 0x5678, 0x12, 0x34, &[0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + + // For this example, we'll just use a static value for the table data + let table_data = 0x12345678u32; + + // Install the configuration table (unsafe - table data must remain valid) + unsafe { + config.install_configuration_table_unchecked(&custom_guid, &table_data as *const u32 as *mut c_void)?; + } + + // The table is now accessible via the system table + + Ok(()) + } +} + +/// Removing a configuration table. +/// +/// This component demonstrates how to remove a previously installed +/// configuration table, +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::misc_usage::RemoveConfigurationTableExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(RemoveConfigurationTableExample); +/// ``` +#[derive(IntoComponent)] +pub struct RemoveConfigurationTableExample; + +impl RemoveConfigurationTableExample { + fn entry_point(self, config: Service) -> Result<()> { + // Define the GUID of the table to remove + let custom_guid = + BinaryGuid::from_fields(0x12345678, 0x1234, 0x5678, 0x12, 0x34, &[0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + + // Remove the configuration table + config.remove_configuration_table(&custom_guid)?; + + Ok(()) + } +} diff --git a/components/patina_samples/src/component/protocol_usage.rs b/components/patina_samples/src/component/protocol_usage.rs new file mode 100644 index 000000000..82bee40d3 --- /dev/null +++ b/components/patina_samples/src/component/protocol_usage.rs @@ -0,0 +1,290 @@ +//! Protocol Service Usage Examples +//! +//! Demonstrates usage patterns for UEFI protocol operations through the +//! [`patina_uefi_services::service::protocol::ProtocolServices`] trait. +//! +//! This example uses a test protocol to demonstrate protocol installation, lookup, +//! and uninstallation without interfering with real protocols on the system. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! +extern crate alloc; + +use alloc::boxed::Box; +use core::ffi::c_void; +use patina::{ + BinaryGuid, + boot_services::protocol_handler::HandleSearchType, + component::{IntoComponent, prelude::Service}, + error::Result, +}; +use patina_uefi_services::{service::protocol::ProtocolServices, types::Handle}; +use r_efi::efi; + +// Define a test protocol GUID that won't conflict (ideally) with other protocols +// Using a custom GUID: {12345678-1234-5678-9ABC-DEF012345678} +static TEST_PROTOCOL_GUID: BinaryGuid = + BinaryGuid::from_fields(0x12345678, 0x1234, 0x5678, 0x9A, 0xBC, &[0xDE, 0xF0, 0x12, 0x34, 0x56, 0x78]); + +/// Test protocol interface structure. +/// +/// A simple protocol interface for demonstration purposes. +#[repr(C)] +struct TestProtocol { + revision: u32, + get_value: extern "efiapi" fn(*const TestProtocol) -> u32, + set_value: extern "efiapi" fn(*mut TestProtocol, u32), + value: u32, +} + +impl TestProtocol { + /// Creates a new test protocol instance. + fn new(initial_value: u32) -> Self { + extern "efiapi" fn get_value(this: *const TestProtocol) -> u32 { + // SAFETY: The caller must ensure that the `this` pointer points to a valid TestProtocol when called via + // protocol interface. In this case, the pointer is properly aligned as it originates from a + // Box. + unsafe { (*this).value } + } + + extern "efiapi" fn set_value(this: *mut TestProtocol, new_value: u32) { + // SAFETY: The caller must ensure that the `this` pointer points to a valid, mutable TestProtocol when + // called. In this case, the pointer is properly aligned and exclusively owned during the call. + unsafe { (*this).value = new_value } + } + + Self { revision: 1, get_value, set_value, value: initial_value } + } +} + +/// Example component demonstrating basic protocol installation and lookup. +/// +/// Shows how to install a protocol on a handle, locate it, and clean it up properly. +#[derive(IntoComponent)] +pub struct BasicProtocolExample; + +impl BasicProtocolExample { + fn entry_point(self, protocol_services: Service) -> Result<()> { + log::info!("Starting basic protocol example..."); + + // Create a test protocol instance on the heap + let mut test_protocol = Box::new(TestProtocol::new(42)); + let protocol_ptr = test_protocol.as_mut() as *mut TestProtocol as *mut c_void; + + // Create a new handle and install the protocol + let mut handle = Handle::null(); + log::info!("Installing test protocol on new handle..."); + + // SAFETY: We own the protocol instance (via Box), the pointer is valid and properly aligned. + // The interface type matches the protocol (NATIVE_INTERFACE for standard protocols). + // The protocol will remain valid until we explicitly uninstall it. + unsafe { + protocol_services.install_protocol_interface( + &mut handle, + &TEST_PROTOCOL_GUID, + efi::NATIVE_INTERFACE, + protocol_ptr, + )?; + } + + log::info!("Protocol installed on handle: {:?}", handle); + + // Verify that the protocol can be found on the handle + log::info!("Querying handle for protocol..."); + + // SAFETY: The handle contains the installed protocol. The pointer is casted to match the original + // type. The protocol remains valid as we haven't uninstalled it. + let found_ptr = unsafe { protocol_services.handle_protocol(handle, &TEST_PROTOCOL_GUID)? }; + + if found_ptr == protocol_ptr { + log::info!("Successfully located protocol on handle"); + } else { + log::warn!("Protocol pointer mismatch!"); + } + + // Clean up: uninstall the protocol + log::info!("Uninstalling protocol..."); + + // SAFETY: The protocol pointer matches what was installed, the handle is valid, and no other + // code is using the protocol (we're about to drop it). + unsafe { + protocol_services.uninstall_protocol_interface(handle, &TEST_PROTOCOL_GUID, protocol_ptr)?; + } + + log::info!("Protocol uninstalled successfully"); + + // Cleanup the protocol instance (Box will drop it) + drop(test_protocol); + + log::info!("Basic protocol example completed successfully"); + Ok(()) + } +} + +/// Example component demonstrating protocol location across multiple handles. +/// +/// Shows how to install protocols on multiple handles and locate all instances. +#[derive(IntoComponent)] +pub struct MultiHandleProtocolExample; + +impl MultiHandleProtocolExample { + fn entry_point(self, protocol_services: Service) -> Result<()> { + log::info!("Starting multi-handle protocol example..."); + + // Create multiple protocol instances + let mut protocol1 = Box::new(TestProtocol::new(100)); + let mut protocol2 = Box::new(TestProtocol::new(200)); + let mut protocol3 = Box::new(TestProtocol::new(300)); + + let ptr1 = protocol1.as_mut() as *mut TestProtocol as *mut c_void; + let ptr2 = protocol2.as_mut() as *mut TestProtocol as *mut c_void; + let ptr3 = protocol3.as_mut() as *mut TestProtocol as *mut c_void; + + // Install protocols on separate handles + let mut handle1 = Handle::null(); + let mut handle2 = Handle::null(); + let mut handle3 = Handle::null(); + + log::info!("Installing test protocols on three handles..."); + + // SAFETY: Each protocol instance is owned via Box and remains valid until uninstalled. + // Each pointer is properly aligned and points to a valid TestProtocol structure. + // The interface type matches the protocol requirements. + unsafe { + protocol_services.install_protocol_interface( + &mut handle1, + &TEST_PROTOCOL_GUID, + efi::NATIVE_INTERFACE, + ptr1, + )?; + protocol_services.install_protocol_interface( + &mut handle2, + &TEST_PROTOCOL_GUID, + efi::NATIVE_INTERFACE, + ptr2, + )?; + protocol_services.install_protocol_interface( + &mut handle3, + &TEST_PROTOCOL_GUID, + efi::NATIVE_INTERFACE, + ptr3, + )?; + } + + log::info!("Protocols installed on handles: {:?}, {:?}, {:?}", handle1, handle2, handle3); + + // Locate all handles with our test protocol + log::info!("Locating all handles with test protocol..."); + let handles = protocol_services.locate_handle_buffer(HandleSearchType::ByProtocol(&TEST_PROTOCOL_GUID))?; + + log::info!("Found {} handles with test protocol", handles.len()); + + // Verify we found our handles + let mut found_count = 0; + for handle in &handles { + if *handle == handle1 || *handle == handle2 || *handle == handle3 { + found_count += 1; + log::info!(" Found expected handle: {:?}", handle); + } + } + + if found_count >= 3 { + log::info!("Successfully located all installed protocols"); + } else { + log::warn!("Only found {} of 3 expected handles", found_count); + } + + // Clean up all protocols + log::info!("Cleaning up all protocols..."); + + // SAFETY: Each pointer matches what was installed on the corresponding handle. + unsafe { + protocol_services.uninstall_protocol_interface(handle1, &TEST_PROTOCOL_GUID, ptr1)?; + protocol_services.uninstall_protocol_interface(handle2, &TEST_PROTOCOL_GUID, ptr2)?; + protocol_services.uninstall_protocol_interface(handle3, &TEST_PROTOCOL_GUID, ptr3)?; + } + + // Drop protocol instances + drop(protocol1); + drop(protocol2); + drop(protocol3); + + log::info!("Multi-handle protocol example completed successfully"); + Ok(()) + } +} + +/// Example component demonstrating protocol location without prior knowledge of handles. +/// +/// Shows how to use `locate_protocol` to find the first instance of a protocol. +#[derive(IntoComponent)] +pub struct LocateProtocolExample; + +impl LocateProtocolExample { + fn entry_point(self, protocol_services: Service) -> Result<()> { + log::info!("Starting locate protocol example..."); + + // Create and install a test protocol + let mut test_protocol = Box::new(TestProtocol::new(777)); + let protocol_ptr = test_protocol.as_mut() as *mut TestProtocol as *mut c_void; + + let mut handle = Handle::null(); + log::info!("Installing test protocol..."); + + // SAFETY: Protocol instance is owned via Box, pointer is valid and properly aligned. + // The interface type matches the protocol. Protocol remains valid until uninstalled. + unsafe { + protocol_services.install_protocol_interface( + &mut handle, + &TEST_PROTOCOL_GUID, + efi::NATIVE_INTERFACE, + protocol_ptr, + )?; + } + + log::info!("Protocol installed on handle: {:?}", handle); + + // Locate the protocol without knowing the handle + log::info!("Locating protocol by GUID..."); + + // SAFETY: We search for an installed protocol by GUID. The returned pointer will be valid + // as long as the protocol remains installed (which it is until we uninstall it). + let located_ptr = unsafe { protocol_services.locate_protocol(&TEST_PROTOCOL_GUID, None)? }; + + // Verify we got the right protocol + if located_ptr == protocol_ptr { + log::info!("Successfully located protocol instance"); + + // Access the protocol + // SAFETY: The pointer came from locate_protocol, points to our valid TestProtocol, + // is properly aligned, and the protocol is still installed so the data is valid. + let protocol_ref = unsafe { &*(located_ptr as *const TestProtocol) }; + let value = (protocol_ref.get_value)(protocol_ref); + log::info!("Protocol value: {}", value); + + if value == 777 { + log::info!("Protocol contains expected value"); + } + } else { + log::warn!("Located protocol pointer does not match installed pointer"); + } + + // Clean up + log::info!("Uninstalling protocol..."); + + // SAFETY: The protocol pointer matches what we installed. Handle and GUID are valid. + // No other code is using the protocol as we're about to drop it. + unsafe { + protocol_services.uninstall_protocol_interface(handle, &TEST_PROTOCOL_GUID, protocol_ptr)?; + } + + drop(test_protocol); + + log::info!("Locate protocol example completed successfully"); + Ok(()) + } +} diff --git a/components/patina_samples/src/component/runtime_usage.rs b/components/patina_samples/src/component/runtime_usage.rs new file mode 100644 index 000000000..705ff9109 --- /dev/null +++ b/components/patina_samples/src/component/runtime_usage.rs @@ -0,0 +1,302 @@ +//! Runtime Services Usage Examples +//! +//! This module demonstrates how to use UEFI Runtime Services including time/date management +//! and system reset operations. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +extern crate alloc; + +use patina::{component::IntoComponent, component::prelude::Service, error::Result}; +use patina_uefi_services::service::runtime::{RuntimeResetServices, RuntimeTimeServices}; +use r_efi::{efi, system}; + +// +// Runtime Time Services Examples +// + +/// Reading the current system time. +/// +/// This component demonstrates how to read the current time and date from +/// the system's real-time clock (RTC). +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::ReadSystemTimeExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(ReadSystemTimeExample); +/// ``` +#[derive(IntoComponent)] +pub struct ReadSystemTimeExample; + +impl ReadSystemTimeExample { + fn entry_point(self, time: Service) -> Result<()> { + // Get the current system time + let current_time = time.get_time()?; + + // The time structure contains: + // - Year, Month, Day + // - Hour, Minute, Second, Nanosecond + // - TimeZone, Daylight savings information + + // Example: Access time components + let _year = current_time.year; + let _month = current_time.month; + let _day = current_time.day; + let _hour = current_time.hour; + let _minute = current_time.minute; + let _second = current_time.second; + + Ok(()) + } +} + +/// Setting the system time. +/// +/// This component demonstrates how to update the system's real-time clock +/// with a new time and date. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::SetSystemTimeExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(SetSystemTimeExample); +/// ``` +#[derive(IntoComponent)] +pub struct SetSystemTimeExample; + +impl SetSystemTimeExample { + fn entry_point(self, time: Service) -> Result<()> { + // Create a new time structure for January 1, 2025, 12:00:00 + let new_time = system::Time { + year: 2025, + month: 1, + day: 1, + hour: 12, + minute: 0, + second: 0, + nanosecond: 0, + timezone: efi::UNSPECIFIED_TIMEZONE, + daylight: 0, + pad1: 0, + pad2: 0, + }; + + // Set the system time + time.set_time(&new_time)?; + + // Verify the time was set + let current_time = time.get_time()?; + assert_eq!(current_time.year, 2025); + assert_eq!(current_time.month, 1); + + Ok(()) + } +} + +/// Managing wakeup alarm timers. +/// +/// This component demonstrates how to configure and query the system's +/// wakeup alarm timer functionality. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::WakeupAlarmExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(WakeupAlarmExample); +/// ``` +#[derive(IntoComponent)] +pub struct WakeupAlarmExample; + +impl WakeupAlarmExample { + fn entry_point(self, time: Service) -> Result<()> { + // Query the current wakeup alarm status + let (enabled, pending, _alarm_time) = time.get_wakeup_time()?; + + // Check if alarm is enabled and pending + let _is_active = enabled && pending; + + // Set a new wakeup alarm for tomorrow at 6:00 AM + let wakeup_time = system::Time { + year: 2025, + month: 1, + day: 2, + hour: 6, + minute: 0, + second: 0, + nanosecond: 0, + timezone: efi::UNSPECIFIED_TIMEZONE, + daylight: 0, + pad1: 0, + pad2: 0, + }; + + time.set_wakeup_time(true, Some(wakeup_time))?; + + // Disable the wakeup alarm + time.set_wakeup_time(false, None)?; + + Ok(()) + } +} + +// +// Runtime Reset Services Examples +// + +/// Performing a cold system reset. +/// +/// This component demonstrates how to perform a cold reset of the entire system. +/// +/// ## Safety Note +/// +/// This function never returns on success - the system will reset immediately. +/// Ensure all critical data is saved before calling this function. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::ColdResetExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(ColdResetExample); +/// ``` +#[derive(IntoComponent)] +pub struct ColdResetExample; + +impl ColdResetExample { + fn entry_point(self, reset: Service) -> Result<()> { + // Perform a cold reset + // Note: In production firmware, this never returns! + #[cfg(test)] + { + reset.reset_system(system::RESET_COLD, efi::Status::SUCCESS, None)?; + Ok(()) + } + + #[cfg(not(test))] + reset.reset_system(system::RESET_COLD, efi::Status::SUCCESS, None) + } +} + +/// Performing a warm system reset. +/// +/// This component demonstrates how to perform a warm reset, which is faster +/// than a cold reset and may preserve some system state. +/// +/// ## Safety Note +/// +/// This function never returns on success - the system will reset immediately. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::WarmResetExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(WarmResetExample); +/// ``` +#[derive(IntoComponent)] +pub struct WarmResetExample; + +impl WarmResetExample { + fn entry_point(self, reset: Service) -> Result<()> { + // Perform a warm reset + // Note: In production firmware, this never returns! + #[cfg(test)] + { + reset.reset_system(system::RESET_WARM, efi::Status::SUCCESS, None)?; + Ok(()) + } + + #[cfg(not(test))] + reset.reset_system(system::RESET_WARM, efi::Status::SUCCESS, None) + } +} + +/// System shutdown operation. +/// +/// This component demonstrates how to shut down the system cleanly. +/// +/// ## Safety Note +/// +/// This function never returns on success - the system will shut down immediately. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::SystemShutdownExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(SystemShutdownExample); +/// ``` +#[derive(IntoComponent)] +pub struct SystemShutdownExample; + +impl SystemShutdownExample { + fn entry_point(self, reset: Service) -> Result<()> { + // Perform system shutdown + // Note: In production firmware, this never returns! + #[cfg(test)] + { + reset.reset_system(system::RESET_SHUTDOWN, efi::Status::SUCCESS, None)?; + Ok(()) + } + + #[cfg(not(test))] + reset.reset_system(system::RESET_SHUTDOWN, efi::Status::SUCCESS, None) + } +} + +/// System reset with additional data. +/// +/// This component demonstrates how to perform a reset with additional data +/// that can be used by the system firmware or operating system. +/// +/// ## Safety Note +/// +/// This function never returns on success - the system will reset immediately. +/// +/// ## Example Usage +/// +/// ```ignore +/// use patina::component::IntoComponent; +/// use patina_samples::component::runtime_usage::ResetWithDataExample; +/// +/// // Register the component with the core +/// dxe_core.with_component(ResetWithDataExample); +/// ``` +#[derive(IntoComponent)] +pub struct ResetWithDataExample; + +impl ResetWithDataExample { + fn entry_point(self, reset: Service) -> Result<()> { + // Perform a reset operation + // Note: In a real implementation, you would pass reset data here. This + // example uses None for simplicity. + #[cfg(test)] + { + reset.reset_system(system::RESET_COLD, efi::Status::SUCCESS, None)?; + Ok(()) + } + + #[cfg(not(test))] + reset.reset_system(system::RESET_COLD, efi::Status::SUCCESS, None) + } +} diff --git a/components/patina_samples/src/component/variable_usage.rs b/components/patina_samples/src/component/variable_usage.rs new file mode 100644 index 000000000..b89969cc8 --- /dev/null +++ b/components/patina_samples/src/component/variable_usage.rs @@ -0,0 +1,135 @@ +//! Variable Service Usage Examples +//! +//! Demonstrates usage patterns for UEFI variable operations through the +//! [`patina_uefi_services::service::variable::UefiSpecVariableServices`] trait. +//! +//! Includes three example components showcasing variable reading, writing, +//! and enumeration operations. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! +extern crate alloc; + +use alloc::{string::String, vec, vec::Vec}; +use patina::{BinaryGuid, component::IntoComponent, component::prelude::Service, error::Result}; +use patina_uefi_services::service::variable::UefiSpecVariableServices; + +// UEFI Global Variable GUID +const GLOBAL_VARIABLE_GUID: BinaryGuid = + BinaryGuid::from_fields(0x8BE4DF61, 0x93CA, 0x11D2, 0xAA, 0x0D, &[0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C]); + +// Variable attribute constants +const EFI_VARIABLE_BOOTSERVICE_ACCESS: u32 = 0x00000002; +const EFI_VARIABLE_RUNTIME_ACCESS: u32 = 0x00000004; +const EFI_VARIABLE_NON_VOLATILE: u32 = 0x00000001; + +/// Example component demonstrating reading a standard UEFI variable. +/// +/// This component reads the BootOrder variable which contains the boot device order. +#[derive(IntoComponent)] +pub struct ReadBootOrder; + +impl ReadBootOrder { + /// Component entry point that reads the BootOrder variable. + /// + /// # Arguments + /// + /// * `variables` - The variable services for reading UEFI variables + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, variables: Service) -> Result<()> { + // Read the BootOrder variable from the Global Variable namespace + match (**variables).get_variable("BootOrder", &GLOBAL_VARIABLE_GUID) { + Ok((data, _attributes)) => { + // BootOrder is an array of UINT16 values + let _boot_order_count = data.len() / core::mem::size_of::(); + // Process boot order data... + Ok(()) + } + Err(patina::error::EfiError::NotFound) => { + // BootOrder not found is acceptable (system might not have it set yet) + Ok(()) + } + Err(e) => Err(e), + } + } +} + +/// Example component demonstrating writing a custom UEFI variable. +/// +/// This component creates a new variable with custom data and verifies it was written correctly. +#[derive(IntoComponent)] +pub struct WriteCustomVariable; + +impl WriteCustomVariable { + /// Component entry point that writes and verifies a custom variable. + /// + /// # Arguments + /// + /// * `variables` - The variable services for writing UEFI variables + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, variables: Service) -> Result<()> { + let variable_name = "MyCustomVariable"; + let variable_guid = &GLOBAL_VARIABLE_GUID; + + // Create some data to store + let data: Vec = vec![ + 0x50, 0x61, 0x74, 0x69, 0x6E, 0x61, // "Patina" in ASCII + ]; + + // Set variable attributes (boot services access, runtime access, non-volatile) + let attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE; + + // Write the variable + (**variables).set_variable(variable_name, variable_guid, attributes, &data)?; + + // Verify the write by reading it back + let (read_data, read_attributes) = (**variables).get_variable(variable_name, variable_guid)?; + + // Verify data matches + assert_eq!(data, read_data, "Variable data mismatch"); + assert_eq!(attributes, read_attributes, "Variable attributes mismatch"); + + Ok(()) + } +} + +/// Example component demonstrating enumeration of all UEFI variables. +/// +/// This component iterates through all variables in the system using the +/// get_next_variable_name API. +#[derive(IntoComponent)] +pub struct EnumerateVariables; + +impl EnumerateVariables { + /// Component entry point that enumerates all system variables. + /// + /// # Arguments + /// + /// * `variables` - The variable services for enumerating UEFI variables + /// + /// # Returns + /// + /// * `Result<()>` - Success or error status + pub fn entry_point(self, variables: Service) -> Result<()> { + // Start with an empty name to get the first variable + let mut variable_name = String::new(); + let mut vendor_guid = GLOBAL_VARIABLE_GUID; + + while (**variables).get_next_variable_name(&mut variable_name, &mut vendor_guid).is_ok() { + // Process the variable (name and guid are now updated in place) + // In a real implementation, you would use variable_name and vendor_guid here + } + + Ok(()) + } +} diff --git a/components/patina_samples/src/lib.rs b/components/patina_samples/src/lib.rs index 59eedf8bd..8fc0c1012 100644 --- a/components/patina_samples/src/lib.rs +++ b/components/patina_samples/src/lib.rs @@ -5,9 +5,24 @@ //! //! ## Examples //! +//! ### Basic Components +//! //! - [`component::hello_world::HelloStruct`]: Demonstrates a struct-based component with default entry point //! - [`component::hello_world::GreetingsEnum`]: Demonstrates an enum-based component with custom entry point //! +//! ### Service Usage +//! +//! #### Event Services +//! +//! - [`component::event_usage::BasicEventExample`]: Basic event creation, timer configuration, and cleanup +//! - [`component::event_usage::MultiEventExample`]: Multiple event handling and polling patterns +//! +//! #### Protocol Services +//! +//! - [`component::protocol_usage::BasicProtocolExample`]: Protocol installation and lookup on a handle +//! - [`component::protocol_usage::MultiHandleProtocolExample`]: Installing protocols across multiple handles +//! - [`component::protocol_usage::LocateProtocolExample`]: Locating protocols without prior handle knowledge +//! //! ## License //! //! Copyright (c) Microsoft Corporation. @@ -16,5 +31,6 @@ //! #![cfg_attr(not(feature = "std"), no_std)] #![feature(coverage_attribute)] +#![coverage(off)] pub mod component; diff --git a/components/patina_uefi_services/Cargo.toml b/components/patina_uefi_services/Cargo.toml new file mode 100644 index 000000000..d49d40e66 --- /dev/null +++ b/components/patina_uefi_services/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "patina_uefi_services" +resolver = "2" +version.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +description = "UEFI Services abstraction for Patina components" + +[dependencies] +patina = { workspace = true } +patina_macro = { workspace = true } +r-efi = { workspace = true } +zerocopy = { workspace = true, features = ["alloc", "derive"] } +mockall = { workspace = true, optional = true } +spin = { workspace = true } + +[dev-dependencies] +mockall = { workspace = true } + +[features] +default = [] +std = ["patina/std"] +mockall = ["dep:mockall", "std", "patina/mockall"] diff --git a/components/patina_uefi_services/src/component.rs b/components/patina_uefi_services/src/component.rs new file mode 100644 index 000000000..3a8262ab0 --- /dev/null +++ b/components/patina_uefi_services/src/component.rs @@ -0,0 +1,16 @@ +//! UEFI Services Component Implementations +//! +//! This module contains component implementations for the Patina UEFI Services. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +pub mod console; +pub mod event; +pub mod image; +pub mod misc; +pub mod protocol; +pub mod provider; +pub mod system_table; +pub mod variable; diff --git a/components/patina_uefi_services/src/component/console.rs b/components/patina_uefi_services/src/component/console.rs new file mode 100644 index 000000000..6aefed34b --- /dev/null +++ b/components/patina_uefi_services/src/component/console.rs @@ -0,0 +1,569 @@ +//! Console Services Component Implementation +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::service::{console::ConsoleServices, system_table::SystemTableService}; +use alloc::{boxed::Box, vec::Vec}; +use core::ffi::c_void; +use patina::{ + boot_services::{BootServices, StandardBootServices, event::EventType, tpl::Tpl}, + component::{IntoComponent, params::Commands, prelude::Service, service::IntoService}, + error::{EfiError, Result}, +}; +use r_efi::efi; +use spin::Mutex; + +/// Standard implementation of console services using UEFI Boot Services. +/// +/// This implementation provides UEFI console operations by delegating to the underlying `StandardBootServices` and +/// accessing the system table's console input and output protocols through the `SystemTableService`. +#[derive(IntoService)] +#[service(dyn ConsoleServices)] +pub struct StandardConsoleServices { + #[allow(dead_code)] + boot_services: StandardBootServices, + system_table_service: Service, +} + +impl StandardConsoleServices { + /// Creates a new `StandardConsoleServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services implementation + /// * `system_table_service` - Service providing access to the system table + pub fn new(boot_services: StandardBootServices, system_table_service: Service) -> Self { + Self { boot_services, system_table_service } + } +} + +impl ConsoleServices for StandardConsoleServices { + fn output_string(&self, text: &str) -> Result<()> { + let con_out = self.system_table_service.get_console_output()?; + + // Convert Rust string to UTF-16 + let mut utf16_string: Vec = text.encode_utf16().collect(); + utf16_string.push(0); // Null terminator + + let status = (con_out.output_string)(con_out, utf16_string.as_ptr() as *mut efi::Char16); + + if status == efi::Status::SUCCESS { Ok(()) } else { Err(EfiError::from(status)) } + } + + fn clear_screen(&self) -> Result<()> { + let con_out = self.system_table_service.get_console_output()?; + + let status = (con_out.clear_screen)(con_out); + + if status == efi::Status::SUCCESS { Ok(()) } else { Err(EfiError::from(status)) } + } + + fn set_cursor_position(&self, column: usize, row: usize) -> Result<()> { + let con_out = self.system_table_service.get_console_output()?; + + let status = (con_out.set_cursor_position)(con_out, column, row); + + if status == efi::Status::SUCCESS { Ok(()) } else { Err(EfiError::from(status)) } + } + + fn get_cursor_position(&self) -> Result<(usize, usize)> { + let con_out = self.system_table_service.get_console_output()?; + + // Access the cursor position from the mode structure + let mode = unsafe { con_out.mode.as_ref().ok_or(EfiError::DeviceError)? }; + + Ok((mode.cursor_column as usize, mode.cursor_row as usize)) + } + + fn enable_cursor(&self, visible: bool) -> Result<()> { + let con_out = self.system_table_service.get_console_output()?; + + let status = (con_out.enable_cursor)(con_out, if visible { efi::Boolean::TRUE } else { efi::Boolean::FALSE }); + + if status == efi::Status::SUCCESS { Ok(()) } else { Err(EfiError::from(status)) } + } + + fn read_key_stroke(&self) -> Result { + let con_in = self.system_table_service.get_console_input()?; + + let mut input_key = efi::protocols::simple_text_input::InputKey { scan_code: 0, unicode_char: 0 }; + + let status = (con_in.read_key_stroke)(con_in, &mut input_key); + + if status == efi::Status::SUCCESS { Ok(input_key.unicode_char) } else { Err(EfiError::from(status)) } + } + + fn is_key_available(&self) -> Result { + let con_in = self.system_table_service.get_console_input()?; + + // Use the WaitForEvent mechanism to check if a key is available + let status = self.boot_services.check_event(con_in.wait_for_key); + + match status { + Ok(_) => Ok(true), // Event is signaled, key is available + Err(efi::Status::NOT_READY) => Ok(false), // Event is not signaled, no key available + Err(e) => Err(EfiError::from(e)), // Some other error occurred + } + } + + fn reset_input(&self, extended_verification: bool) -> Result<()> { + let con_in = self.system_table_service.get_console_input()?; + + let status = + (con_in.reset)(con_in, if extended_verification { efi::Boolean::TRUE } else { efi::Boolean::FALSE }); + + if status == efi::Status::SUCCESS { Ok(()) } else { Err(EfiError::from(status)) } + } + + fn query_mode(&self) -> Result<(usize, usize)> { + let con_out = self.system_table_service.get_console_output()?; + + let mode = unsafe { con_out.mode.as_ref().ok_or(EfiError::DeviceError)? }; + + let mut columns: usize = 0; + let mut rows: usize = 0; + + let status = (con_out.query_mode)(con_out, mode.mode as usize, &mut columns, &mut rows); + + if status == efi::Status::SUCCESS { Ok((columns, rows)) } else { Err(EfiError::from(status)) } + } +} + +/// Component that provides `ConsoleServices` for other components. +/// +/// This component uses protocol notifications to ensure that console services are only registered +/// after both `simple_text_input` and `simple_text_output` protocols are installed. +#[derive(IntoComponent)] +pub struct ConsoleServicesProvider; + +/// Context passed to protocol notify callback +struct NotifyContext<'a> { + commands: Mutex>, + boot_services: StandardBootServices, + system_table_service: Service, + service_registered: Mutex, +} + +impl ConsoleServicesProvider { + /// Entry point for the `ConsoleServicesProvider` component. + /// + /// Sets up protocol notifications for `simple_text_input` and `simple_text_output` protocols. + /// The console services are only registered once both protocols are installed. + pub fn entry_point( + self, + commands: Commands, + boot_services: StandardBootServices, + system_table_service: Service, + ) -> Result<()> { + // Create shared context that will be passed to both callbacks + let context = Box::into_raw(Box::new(NotifyContext { + commands: Mutex::new(commands), + boot_services: boot_services.clone(), + system_table_service, + service_registered: Mutex::new(false), + })); + + // Create a single event that will be used for both protocol notifications + let event = boot_services.create_event( + EventType::NOTIFY_SIGNAL, + Tpl::NOTIFY, + Some(Self::protocol_notify_callback), + context as *mut c_void, + )?; + + // Register the same event for both protocols + // The callback will check if both are available each time it's triggered + boot_services.register_protocol_notify(&efi::protocols::simple_text_input::PROTOCOL_GUID, event)?; + + boot_services.register_protocol_notify(&efi::protocols::simple_text_output::PROTOCOL_GUID, event)?; + + Ok(()) + } + + /// Callback triggered when either protocol is installed. + /// Checks if both protocols are now available and registers the service if so. + extern "efiapi" fn protocol_notify_callback(_event: efi::Event, context: *mut c_void) { + if context.is_null() { + return; + } + + // SAFETY: We control the context pointer lifetime + let ctx = unsafe { &*(context as *const NotifyContext) }; + + // Check if we've already registered the service + if *ctx.service_registered.lock() { + return; + } + + // Check if both protocols are now available + let input_available = ctx.system_table_service.get_console_input().is_ok(); + let output_available = ctx.system_table_service.get_console_output().is_ok(); + + if input_available && output_available { + // Both protocols available - register the service + let console_services = + StandardConsoleServices::new(ctx.boot_services.clone(), ctx.system_table_service.clone()); + + ctx.commands.lock().add_service(console_services); + *ctx.service_registered.lock() = true; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::{boxed::Box, vec}; + use core::{cell::RefCell, ptr::NonNull}; + use patina::component::prelude::Service; + use r_efi::efi; + + // Mock console input protocol that captures calls and provides controlled responses + #[derive(Clone)] + struct MockConsoleInput { + wait_for_key: efi::Event, + } + + impl MockConsoleInput { + fn new() -> Self { + Self { wait_for_key: NonNull::dangling().as_ptr() } + } + + fn to_protocol(&self) -> efi::protocols::simple_text_input::Protocol { + extern "efiapi" fn mock_reset( + _this: *mut efi::protocols::simple_text_input::Protocol, + _extended_verification: efi::Boolean, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_read_key_stroke( + _this: *mut efi::protocols::simple_text_input::Protocol, + key: *mut efi::protocols::simple_text_input::InputKey, + ) -> efi::Status { + if !key.is_null() { + unsafe { + (*key).scan_code = 0; + (*key).unicode_char = 0x41; // 'A' + } + } + efi::Status::SUCCESS + } + + efi::protocols::simple_text_input::Protocol { + reset: mock_reset, + read_key_stroke: mock_read_key_stroke, + wait_for_key: self.wait_for_key, + } + } + } + + // Mock console output protocol that captures output and provides controlled responses + #[derive(Clone)] + struct MockConsoleOutput { + mode: RefCell, + } + + impl MockConsoleOutput { + fn new() -> Self { + Self { + mode: RefCell::new(efi::protocols::simple_text_output::Mode { + max_mode: 1, + mode: 0, + attribute: 0, + cursor_column: 0, + cursor_row: 0, + cursor_visible: efi::Boolean::TRUE, + }), + } + } + + fn to_protocol(&self) -> efi::protocols::simple_text_output::Protocol { + extern "efiapi" fn mock_output_string( + _this: *mut efi::protocols::simple_text_output::Protocol, + _string: *mut efi::Char16, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_clear_screen( + _this: *mut efi::protocols::simple_text_output::Protocol, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_set_cursor_position( + _this: *mut efi::protocols::simple_text_output::Protocol, + _column: usize, + _row: usize, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_enable_cursor( + _this: *mut efi::protocols::simple_text_output::Protocol, + _visible: efi::Boolean, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_query_mode( + _this: *mut efi::protocols::simple_text_output::Protocol, + _mode_number: usize, + columns: *mut usize, + rows: *mut usize, + ) -> efi::Status { + if !columns.is_null() && !rows.is_null() { + unsafe { + *columns = 80; + *rows = 25; + } + } + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_reset_stub( + _this: *mut efi::protocols::simple_text_output::Protocol, + _extended_verification: efi::Boolean, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_test_string_stub( + _this: *mut efi::protocols::simple_text_output::Protocol, + _string: *mut efi::Char16, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_set_mode_stub( + _this: *mut efi::protocols::simple_text_output::Protocol, + _mode_number: usize, + ) -> efi::Status { + efi::Status::SUCCESS + } + + extern "efiapi" fn mock_set_attribute_stub( + _this: *mut efi::protocols::simple_text_output::Protocol, + _attribute: usize, + ) -> efi::Status { + efi::Status::SUCCESS + } + + efi::protocols::simple_text_output::Protocol { + reset: mock_reset_stub, + output_string: mock_output_string, + test_string: mock_test_string_stub, + query_mode: mock_query_mode, + set_mode: mock_set_mode_stub, + set_attribute: mock_set_attribute_stub, + clear_screen: mock_clear_screen, + set_cursor_position: mock_set_cursor_position, + enable_cursor: mock_enable_cursor, + mode: self.mode.as_ptr(), + } + } + } + + // Mock SystemTableService that provides our mock console protocols + #[derive(Clone)] + struct MockSystemTableService { + console_input: MockConsoleInput, + console_output: MockConsoleOutput, + } + + impl MockSystemTableService { + fn new() -> Self { + Self { console_input: MockConsoleInput::new(), console_output: MockConsoleOutput::new() } + } + } + + impl SystemTableService for MockSystemTableService { + fn get_console_input(&self) -> Result<&'static mut efi::protocols::simple_text_input::Protocol> { + // Leak a fake static reference for testing + let protocol = Box::leak(Box::new(self.console_input.to_protocol())); + Ok(protocol) + } + + fn get_console_output(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol> { + // Leak a fake static reference for testing + let protocol = Box::leak(Box::new(self.console_output.to_protocol())); + Ok(protocol) + } + + fn get_standard_error(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol> { + self.get_console_output() // Reuse console output for stderr in tests + } + } + + fn create_test_console_service() -> (StandardConsoleServices, Box) { + let mock_system_table = Box::new(MockSystemTableService::new()); + let boot_services = StandardBootServices::new_uninit(); + let system_table_service = + Service::::mock(mock_system_table.clone() as Box); + + let console_services = StandardConsoleServices::new(boot_services, system_table_service); + (console_services, mock_system_table) + } + + #[test] + fn test_standard_console_services_creation() { + let uninit_boot_service = StandardBootServices::new_uninit(); + let uninit_system_table_service = Service::::new_uninit(); + let _console_services = StandardConsoleServices::new(uninit_boot_service, uninit_system_table_service); + // The test is just checking that a panic does not occur + } + + #[test] + fn test_console_services_provider_creation() { + let _provider = ConsoleServicesProvider; + // The test is just checking that a panic does not occur + } + + #[test] + fn test_output_string_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // Test outputting a simple string + let test_string = "Hello From Console Services!"; + let result = console_services.output_string(test_string); + + assert!(result.is_ok(), "output_string should succeed"); + } + + #[test] + fn test_output_string_with_special_characters() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // Test strings with special characters and unicode + let test_strings = vec![ + "Test with newlines\r\n", + "Unicode: äöüß 🦀", + "", // Empty string + "Tab\tCharacter", + ]; + + for test_string in test_strings { + let result = console_services.output_string(test_string); + assert!(result.is_ok(), "output_string should succeed for: {}", test_string); + } + } + + #[test] + fn test_clear_screen_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + let result = console_services.clear_screen(); + + assert!(result.is_ok(), "clear_screen should succeed"); + } + + #[test] + fn test_set_cursor_position_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + let test_positions = vec![(0, 0), (10, 5), (79, 24), (100, 50)]; + + for (col, row) in test_positions { + let result = console_services.set_cursor_position(col, row); + assert!(result.is_ok(), "set_cursor_position should succeed for ({}, {})", col, row); + } + } + + #[test] + fn test_get_cursor_position_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + let result = console_services.get_cursor_position(); + + assert!(result.is_ok(), "get_cursor_position should succeed"); + let (col, row) = result.unwrap(); + // The mock initializes cursor to (0, 0) + assert_eq!(col, 0, "Column should be 0"); + assert_eq!(row, 0, "Row should be 0"); + } + + #[test] + fn test_enable_cursor_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // Test enabling cursor + let result = console_services.enable_cursor(true); + assert!(result.is_ok(), "enable_cursor(true) should succeed"); + + // Test disabling cursor + let result = console_services.enable_cursor(false); + assert!(result.is_ok(), "enable_cursor(false) should succeed"); + } + + #[test] + fn test_read_key_stroke_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + let result = console_services.read_key_stroke(); + + assert!(result.is_ok(), "read_key_stroke should succeed"); + let key = result.unwrap(); + assert_eq!(key, 0x41, "Should return 'A' character"); + } + + #[test] + fn test_reset_input_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // Test reset without extended verification + let result = console_services.reset_input(false); + assert!(result.is_ok(), "reset_input(false) should succeed"); + + // Test reset with extended verification + let result = console_services.reset_input(true); + assert!(result.is_ok(), "reset_input(true) should succeed"); + } + + #[test] + fn test_query_mode_success() { + let (console_services, _mock_system_table) = create_test_console_service(); + + let result = console_services.query_mode(); + + assert!(result.is_ok(), "query_mode should succeed"); + let (cols, rows) = result.unwrap(); + assert_eq!(cols, 80, "Columns should match expected value"); + assert_eq!(rows, 25, "Rows should match expected value"); + } + + #[test] + fn test_string_encoding_roundtrip() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // Test that UTF-16 conversion is working as expected + let test_strings = vec![ + "ASCII only", + "Latin-1: café", + "Unicode: 🦀 Rust", + "Mixed: Hello 世界 🌍", + "Numbers: 0123456789", + "Special: !@#$%^&*()", + ]; + + for test_string in &test_strings { + let result = console_services.output_string(test_string); + assert!(result.is_ok(), "Should successfully output: {}", test_string); + } + } + + #[test] + fn test_null_terminator_handling() { + let (console_services, _mock_system_table) = create_test_console_service(); + + // The string should be null terminated + let result = console_services.output_string("Test"); + assert!(result.is_ok(), "Should successfully output string"); + + // An empty string should work + let result = console_services.output_string(""); + assert!(result.is_ok(), "Should successfully output empty string"); + } +} diff --git a/components/patina_uefi_services/src/component/event.rs b/components/patina_uefi_services/src/component/event.rs new file mode 100644 index 000000000..dbd803aa5 --- /dev/null +++ b/components/patina_uefi_services/src/component/event.rs @@ -0,0 +1,176 @@ +//! Event Services Component Implementation +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::{ + service::event::{EventServices, EventServicesClosureExt}, + types::Event, +}; +use alloc::{boxed::Box, collections::BTreeMap}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use patina::{ + BinaryGuid, + boot_services::{ + BootServices, StandardBootServices, + event::{EventNotifyCallback, EventTimerType, EventType}, + tpl::Tpl, + }, + component::{IntoComponent, params::Commands, service::IntoService}, + error::Result, +}; +use r_efi::efi; +use spin::Mutex; + +type EventCallback = Box; + +/// Global callback registry for event handlers +static CALLBACK_REGISTRY: Mutex> = Mutex::new(BTreeMap::new()); +static CALLBACK_ID_COUNTER: AtomicUsize = AtomicUsize::new(1); + +/// A generic callback wrapper that looks up and calls the appropriate closure +extern "efiapi" fn event_callback_wrapper(event: efi::Event, context: *mut core::ffi::c_void) { + let callback_id = context as usize; + + // Convert efi::Event to Event and call the closure from the registry + if let Some(callback) = CALLBACK_REGISTRY.lock().get_mut(&callback_id) { + callback(Event::new(event)); + } +} + +/// Standard implementation of event services using UEFI Boot Services. +#[derive(IntoService)] +#[service(dyn EventServices)] +pub struct StandardEventServices { + boot_services: StandardBootServices, +} + +impl StandardEventServices { + /// Creates a new StandardEventServices instance. + /// + /// # Arguments + /// + /// * `boot_services` - The UEFI Boot Services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl EventServices for StandardEventServices { + fn create_event(&self, event_type: EventType, notify_tpl: Tpl) -> Result { + self.boot_services.create_event(event_type, notify_tpl, None, ()).map(Event::new).map_err(|e| e.into()) + } + + fn close_event(&self, event: Event) -> Result<()> { + self.boot_services.close_event(event.as_raw()).map_err(|e| e.into()) + } + + fn set_timer(&self, event: Event, timer_type: EventTimerType, trigger_time: u64) -> Result<()> { + self.boot_services.set_timer(event.as_raw(), timer_type, trigger_time).map_err(|e| e.into()) + } + + fn wait_for_event(&self, events: &mut [Event]) -> Result { + // The Event slice is converted to efi::Event slice for the FFI call + // SAFETY: Event is #[repr(transparent)] wrapper around efi::Event, so the memory layout is identical + let raw_events = + unsafe { core::slice::from_raw_parts_mut(events.as_mut_ptr() as *mut efi::Event, events.len()) }; + self.boot_services.wait_for_event(raw_events).map_err(|e| e.into()) + } + + fn check_event(&self, event: Event) -> Result<()> { + self.boot_services.check_event(event.as_raw()).map_err(|e| e.into()) + } + + fn signal_event(&self, event: Event) -> Result<()> { + self.boot_services.signal_event(event.as_raw()).map_err(|e| e.into()) + } + + fn create_system_event(&self, event_type: EventType, notify_tpl: Tpl) -> Result { + self.boot_services.create_event(event_type, notify_tpl, None, ()).map(Event::new).map_err(|e| e.into()) + } +} + +impl EventServicesClosureExt for StandardEventServices { + fn create_system_event_with_callback( + &self, + event_type: EventType, + notify_tpl: Tpl, + callback: F, + event_group: &'static BinaryGuid, + ) -> Result + where + F: FnMut(Event) + Send + Sync + 'static, + { + // Generate a unique callback ID + let callback_id = CALLBACK_ID_COUNTER.fetch_add(1, Ordering::SeqCst); + + CALLBACK_REGISTRY.lock().insert(callback_id, Box::new(callback)); + + let efi_guid: &'static efi::Guid = event_group; + + // Create the event with our wrapper function and the callback ID as context + let callback_fn: EventNotifyCallback<*mut core::ffi::c_void> = event_callback_wrapper; + self.boot_services + .create_event_ex(event_type, notify_tpl, Some(callback_fn), callback_id as *mut core::ffi::c_void, efi_guid) + .map(Event::new) + .map_err(|e| e.into()) + } +} + +/// Component that provides event services to other Patina components. +#[derive(IntoComponent)] +pub struct EventServicesProvider; + +impl EventServicesProvider { + /// Component entry point that registers event services. + /// + /// # Arguments + /// + /// * `commands` - The commands storage for service registration + /// * `boot_services` - The UEFI Boot Services required for event operations + /// + /// # Returns + /// + /// Returns `Ok(())` on successful registration. + pub fn entry_point(self, mut commands: Commands, boot_services: StandardBootServices) -> Result<()> { + let event_services = StandardEventServices::new(boot_services); + commands.add_service(event_services); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use patina::{ + component::{params::Commands, prelude::Service}, + test::patina_test, + }; + + #[patina_test] + fn test_event_services_provider_creation() -> core::result::Result<(), &'static str> { + let _provider = EventServicesProvider; + // Component should be created successfully + Ok(()) + } + + #[patina_test] + fn test_standard_event_services_creation() -> core::result::Result<(), &'static str> { + let uninit_service = StandardBootServices::new_uninit(); + let _event_services = StandardEventServices::new(uninit_service); + // Service should be created successfully + Ok(()) + } + + #[patina_test] + fn test_entry_point() -> core::result::Result<(), &'static str> { + let _commands = Commands::mock(); + let _uninit_service = Service::::new_uninit(); + + let _provider = EventServicesProvider; + + // Entry point should execute without panicking + Ok(()) + } +} diff --git a/components/patina_uefi_services/src/component/image.rs b/components/patina_uefi_services/src/component/image.rs new file mode 100644 index 000000000..8e0848527 --- /dev/null +++ b/components/patina_uefi_services/src/component/image.rs @@ -0,0 +1,109 @@ +//! Image Services Component Implementation +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::{service::image::ImageServices, types::Handle}; +use alloc::vec::Vec; +use patina::{ + boot_services::{BootServices, StandardBootServices}, + component::{IntoComponent, params::Commands, service::IntoService}, + error::{EfiError, Result}, +}; +use r_efi::efi; + +/// Standard implementation of image services using UEFI Boot Services. +#[derive(IntoService)] +#[service(dyn ImageServices)] +pub struct StandardImageServices { + boot_services: StandardBootServices, +} + +impl StandardImageServices { + /// Creates a new `StandardImageServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services implementation + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl ImageServices for StandardImageServices { + unsafe fn load_image( + &self, + parent_image_handle: Handle, + device_path: *mut efi::protocols::device_path::Protocol, + source_buffer: Option>, + _source_size: usize, + ) -> Result { + // SAFETY: Caller guarantees that the device_path pointer is valid and properly formatted. + // We delegate to the underlying boot services which performs the actual unsafe operation. + // Convert Vec to &[u8] for the underlying service + let source_ref = source_buffer.as_deref(); + self.boot_services + .load_image(true, parent_image_handle.as_raw(), device_path, source_ref) + .map(Handle::new) + .map_err(EfiError::from) + } + + fn start_image(&self, image_handle: Handle) -> Result> { + match self.boot_services.start_image(image_handle.as_raw()) { + Ok(()) => Ok(Vec::new()), + Err((status, _exit_data)) => { + // If there's exit data, convert it to Vec and return it with error + // For now, we'll treat any start_image error as an EfiError + // The exit data, if present, could be extracted but for simplicity + // we'll focus on the error status + Err(EfiError::from(status)) + } + } + } + + fn exit(&self, image_handle: Handle, exit_status: efi::Status, _exit_data: Option>) -> Result<()> { + // For simplicity, we don't currently support exit data in the high-level service + // Pass None to the underlying boot services for now + self.boot_services.exit(image_handle.as_raw(), exit_status, None).map_err(EfiError::from) + } + + fn unload_image(&self, image_handle: Handle) -> Result<()> { + self.boot_services.unload_image(image_handle.as_raw()).map_err(EfiError::from) + } +} + +/// Component that provides `ImageServices` to the system. +/// +/// This component registers the `StandardImageServices` implementation, +/// making image operations available to other Patina Components. +#[derive(IntoComponent)] +pub struct ImageServicesProvider; + +impl ImageServicesProvider { + /// Entry point for the `ImageServicesProvider` component. + /// + /// This registers the `StandardImageServices` implementation, making it available + /// to other components that depend on `ImageServices`. + pub fn entry_point(self, mut commands: Commands, boot_services: StandardBootServices) -> Result<()> { + let image_services = StandardImageServices::new(boot_services); + commands.add_service(image_services); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_standard_image_services_creation() { + let boot_services = StandardBootServices::new_uninit(); + let _image_services = StandardImageServices::new(boot_services); + } + + #[test] + fn test_image_services_provider_creation() { + let _provider = ImageServicesProvider; + } +} diff --git a/components/patina_uefi_services/src/component/misc.rs b/components/patina_uefi_services/src/component/misc.rs new file mode 100644 index 000000000..4f24633d9 --- /dev/null +++ b/components/patina_uefi_services/src/component/misc.rs @@ -0,0 +1,259 @@ +//! Miscellaneous UEFI Services implementations. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::service::misc::{ConfigurationServices, MemoryUtilityServices, SystemUtilityServices, TimingServices}; +use alloc::boxed::Box; +use core::ffi::c_void; +use patina::{ + BinaryGuid, + boot_services::{BootServices, StandardBootServices}, + component::{IntoComponent, params::Commands}, + error::{EfiError, Result}, +}; +use patina_macro::IntoService; + +/// Standard implementation of `TimingServices` that delegates to `StandardBootServices`. +#[derive(IntoService)] +#[service(dyn TimingServices)] +pub struct StandardTimingServices { + boot_services: StandardBootServices, +} + +impl StandardTimingServices { + /// Creates a new `StandardTimingServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl TimingServices for StandardTimingServices { + fn set_watchdog_timer(&self, timeout: usize) -> Result<()> { + self.boot_services.set_watchdog_timer(timeout).map_err(EfiError::from) + } + + fn stall(&self, microseconds: usize) -> Result<()> { + self.boot_services.stall(microseconds).map_err(EfiError::from) + } +} + +/// Standard implementation of `MemoryUtilityServices` that delegates to `StandardBootServices`. +#[derive(IntoService)] +#[service(StandardMemoryUtilityServices)] +pub struct StandardMemoryUtilityServices { + boot_services: StandardBootServices, +} + +impl StandardMemoryUtilityServices { + /// Creates a new `StandardMemoryUtilityServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl MemoryUtilityServices for StandardMemoryUtilityServices { + fn copy_mem(&self, dest: &mut T, src: &T) { + self.boot_services.copy_mem(dest, src); + } + + unsafe fn copy_mem_unchecked(&self, dest: *mut c_void, src: *const c_void, length: usize) { + unsafe { + self.boot_services.copy_mem_unchecked(dest, src, length); + } + } + + fn set_mem(&self, buffer: &mut [u8], value: u8) { + self.boot_services.set_mem(buffer, value); + } +} + +/// Standard implementation of `SystemUtilityServices` that delegates to `StandardBootServices`. +#[derive(IntoService)] +#[service(StandardSystemUtilityServices)] +pub struct StandardSystemUtilityServices { + boot_services: StandardBootServices, +} + +impl StandardSystemUtilityServices { + /// Creates a new `StandardSystemUtilityServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl SystemUtilityServices for StandardSystemUtilityServices { + fn get_next_monotonic_count(&self) -> Result { + self.boot_services.get_next_monotonic_count().map_err(EfiError::from) + } + + fn calculate_crc_32(&self, data: &T) -> Result { + self.boot_services.calculate_crc_32(data).map_err(EfiError::from) + } + + unsafe fn calculate_crc_32_unchecked(&self, data: *const c_void, data_size: usize) -> Result { + unsafe { self.boot_services.calculate_crc_32_unchecked(data, data_size).map_err(EfiError::from) } + } +} + +/// Standard implementation of `ConfigurationServices` that delegates to `StandardBootServices`. +#[derive(IntoService)] +#[service(StandardConfigurationServices)] +pub struct StandardConfigurationServices { + boot_services: StandardBootServices, +} + +impl StandardConfigurationServices { + /// Creates a new `StandardConfigurationServices` instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } + + /// Installs a configuration table. + /// + /// This is considered a safe interface to install configuration tables because: + /// - `Box` is used to ensure proper ownership and lifetime management + /// - Type safety is provided through generics + /// - Raw pointers for interaction with underlying UEFI defined interfaces are + /// handled internal to the implementation + /// + /// The table data will be leaked (using `Box::into_raw`) to ensure it remains + /// valid for the lifetime of the system, as required by UEFI configuration tables. + /// + /// # Type Parameters + /// - `T` - The type of the configuration table data (must be `'static`) + /// + /// # Parameters + /// - `guid` - GUID that identifies the configuration table + /// - `table` - Boxed table data that will be installed + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(status)` on failure + /// + /// # Example + /// ```rust + /// use r_efi::efi; + /// use patina::{component::{IntoComponent, prelude::Service}, BinaryGuid}; + /// use patina_uefi_services::component::misc::StandardConfigurationServices; + /// use patina::boot_services::StandardBootServices; + /// + /// #[derive(Debug)] + /// struct MyConfigTable { + /// version: u32, + /// data: [u8; 16], + /// } + /// + /// #[derive(IntoComponent)] + /// struct MyComponent; + /// + /// impl MyComponent { + /// fn entry_point(self, config: Service) -> patina::error::Result<()> { + /// let guid = BinaryGuid::from_fields(0x12345678, 0x1234, 0x5678, 0x12, 0x34, &[0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + /// let table = Box::new(MyConfigTable { version: 1, data: [0; 16] }); + /// config.install_configuration_table(&guid, table)?; + /// Ok(()) + /// } + /// } + /// ``` + pub fn install_configuration_table(&self, guid: &BinaryGuid, table: Box) -> Result<()> { + // Convert Box to raw pointer and delegate to unchecked version + let table_ptr = Box::into_raw(table) as *mut c_void; + unsafe { self.install_configuration_table_unchecked(guid, table_ptr) } + } +} + +impl ConfigurationServices for StandardConfigurationServices { + fn remove_configuration_table(&self, guid: &BinaryGuid) -> Result<()> { + unsafe { self.install_configuration_table_unchecked(guid, core::ptr::null_mut()) } + } + + unsafe fn install_configuration_table_unchecked(&self, guid: &BinaryGuid, table: *mut c_void) -> Result<()> { + // SAFETY: Caller guarantees that the table pointer is valid or null. + let efi_guid = guid; + + unsafe { self.boot_services.install_configuration_table_unchecked(efi_guid, table).map_err(EfiError::from) } + } +} + +/// Component that provides `TimingServices` to the system. +#[derive(IntoComponent)] +pub struct TimingServicesProvider; + +impl TimingServicesProvider { + /// Component entry point. + pub fn entry_point(self, boot_services: StandardBootServices, mut commands: Commands) -> Result<()> { + let timing_services = StandardTimingServices::new(boot_services); + commands.add_service(timing_services); + Ok(()) + } +} + +/// Component that provides `MemoryUtilityServices` to the system. +#[derive(IntoComponent)] +pub struct MemoryUtilityServicesProvider; + +impl MemoryUtilityServicesProvider { + /// Component entry point. + pub fn entry_point(self, boot_services: StandardBootServices, mut commands: Commands) -> Result<()> { + let memory_utility_services = StandardMemoryUtilityServices::new(boot_services); + commands.add_service(memory_utility_services); + Ok(()) + } +} + +/// Component that provides `SystemUtilityServices` to the system. +#[derive(IntoComponent)] +pub struct SystemUtilityServicesProvider; + +impl SystemUtilityServicesProvider { + /// Component entry point. + pub fn entry_point(self, boot_services: StandardBootServices, mut commands: Commands) -> Result<()> { + let system_utility_services = StandardSystemUtilityServices::new(boot_services); + commands.add_service(system_utility_services); + Ok(()) + } +} + +/// Component that provides `ConfigurationServices` to the system. +#[derive(IntoComponent)] +pub struct ConfigurationServicesProvider; + +impl ConfigurationServicesProvider { + /// Component entry point. + pub fn entry_point(self, boot_services: StandardBootServices, mut commands: Commands) -> Result<()> { + let configuration_services = StandardConfigurationServices::new(boot_services); + commands.add_service(configuration_services); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_provider_creation() { + let _timing_provider = TimingServicesProvider; + let _memory_utility_provider = MemoryUtilityServicesProvider; + let _system_utility_provider = SystemUtilityServicesProvider; + let _configuration_provider = ConfigurationServicesProvider; + } +} diff --git a/components/patina_uefi_services/src/component/protocol.rs b/components/patina_uefi_services/src/component/protocol.rs new file mode 100644 index 000000000..0385907af --- /dev/null +++ b/components/patina_uefi_services/src/component/protocol.rs @@ -0,0 +1,152 @@ +//! Protocol Services Implementation +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! + +use crate::{service::protocol::ProtocolServices, types::Handle}; +use alloc::vec::Vec; +use core::ffi::c_void; +use patina::{ + BinaryGuid, + boot_services::{BootServices, StandardBootServices, protocol_handler::HandleSearchType}, + component::{IntoComponent, params::Commands}, + error::{EfiError, Result}, +}; +use patina_macro::IntoService; +use r_efi::efi; + +/// Standard implementation of `ProtocolServices` that delegates to `StandardBootServices`. +#[derive(IntoService)] +#[service(dyn ProtocolServices)] +pub struct StandardProtocolServices { + boot_services: StandardBootServices, +} + +impl StandardProtocolServices { + /// Creates a new StandardProtocolServices instance. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services to delegate to + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl ProtocolServices for StandardProtocolServices { + unsafe fn install_protocol_interface( + &self, + handle: &mut Handle, + protocol: &'static BinaryGuid, + _interface_type: efi::InterfaceType, + interface: *mut c_void, + ) -> Result<()> { + // SAFETY: Caller guarantees that the interface pointer is valid and matches the protocol type. + // We delegate to the underlying boot services which performs the actual unsafe operation. + + let efi_guid: &'static efi::Guid = protocol.as_efi_guid(); + + let new_handle = unsafe { + self.boot_services + .install_protocol_interface_unchecked( + if handle.is_null() { None } else { Some(handle.as_raw()) }, + efi_guid, + interface, + ) + .map_err(EfiError::from)? + }; + + // Update the handle if it was created + *handle = Handle::new(new_handle); + Ok(()) + } + unsafe fn uninstall_protocol_interface( + &self, + handle: Handle, + protocol: &'static BinaryGuid, + interface: *mut c_void, + ) -> Result<()> { + // SAFETY: Caller guarantees that this is safe to uninstall (handle and protocol match), + // and that no other code is using the protocol interface. + + let efi_guid: &'static efi::Guid = protocol; + + unsafe { + self.boot_services + .uninstall_protocol_interface_unchecked(handle.as_raw(), efi_guid, interface) + .map_err(EfiError::from) + } + } + + unsafe fn handle_protocol(&self, handle: Handle, protocol: &'static BinaryGuid) -> Result<*mut c_void> { + // SAFETY: Caller is responsible for casting the returned pointer to the correct type + // and ensuring the protocol is not used after being uninstalled. + + let efi_guid: &'static efi::Guid = protocol; + + unsafe { self.boot_services.handle_protocol_unchecked(handle.as_raw(), efi_guid).map_err(EfiError::from) } + } + + unsafe fn locate_protocol( + &self, + protocol: &'static BinaryGuid, + registration: Option<*mut c_void>, + ) -> Result<*mut c_void> { + // SAFETY: Caller is responsible for casting the returned pointer to the correct type + // and ensuring proper lifetime management of the protocol interface. + + let efi_guid: &'static efi::Guid = protocol; + + unsafe { + self.boot_services + .locate_protocol_unchecked(efi_guid, registration.unwrap_or(core::ptr::null_mut())) + .map_err(EfiError::from) + } + } + + fn locate_handle_buffer(&self, search_type: HandleSearchType) -> Result> { + let handles_box = self.boot_services.locate_handle_buffer(search_type).map_err(EfiError::from)?; + + // Convert from BootServicesBox to Vec, wrapping each handle in our Handle newtype + Ok(handles_box.iter().copied().map(Handle::new).collect()) + } +} + +/// Component that provides `ProtocolServices` to the system. +#[derive(IntoComponent)] +pub struct ProtocolServicesProvider; + +impl ProtocolServicesProvider { + /// Component entry point. + /// + /// # Arguments + /// + /// * `boot_services` - The underlying boot services + /// * `commands` - Commands interface for service registration + pub fn entry_point(self, boot_services: StandardBootServices, mut commands: Commands) -> Result<()> { + let protocol_services = StandardProtocolServices::new(boot_services); + commands.add_service(protocol_services); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_standard_protocol_services_creation() { + let boot_services = StandardBootServices::new_uninit(); + let _protocol_services = StandardProtocolServices::new(boot_services); + } + + #[test] + fn test_protocol_services_provider_creation() { + // Just test that that the provider component can be instantiated + let _provider = ProtocolServicesProvider; + } +} diff --git a/components/patina_uefi_services/src/component/provider.rs b/components/patina_uefi_services/src/component/provider.rs new file mode 100644 index 000000000..3c079ad80 --- /dev/null +++ b/components/patina_uefi_services/src/component/provider.rs @@ -0,0 +1,51 @@ +//! UEFI Services Provider Component +//! +//! This component provides all UEFI services by registering the appropriate service implementations. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::component::misc::{ + StandardConfigurationServices, StandardMemoryUtilityServices, StandardSystemUtilityServices, StandardTimingServices, +}; +use patina::{ + boot_services::StandardBootServices, + component::{IntoComponent, params::Commands}, + error::Result, +}; + +/// Component that provides all UEFI Services. +#[derive(IntoComponent)] +pub struct UefiServicesProvider; + +impl UefiServicesProvider { + /// Entry point for the UEFI Services provider component. + /// + /// This registers most UEFI service implementations, making them available + /// to other components that depend on UEFI services. + /// + /// Note: The following services are not registered here and installed in a dedicated component: Console, Event, + /// Image, Protocol, Runtime, System Table, Variable + /// + /// - Memory services are provided by `MemoryServicesProvider` using `MemoryManager`. + pub fn entry_point(self, mut commands: Commands, boot_services: StandardBootServices) -> Result<()> { + // Create and register timing services + let timing_services = StandardTimingServices::new(boot_services.clone()); + commands.add_service(timing_services); + + // Create and register memory utility services + let memory_utility_services = StandardMemoryUtilityServices::new(boot_services.clone()); + commands.add_service(memory_utility_services); + + // Create and register system utility services + let system_utility_services = StandardSystemUtilityServices::new(boot_services.clone()); + commands.add_service(system_utility_services); + + // Create and register the configuration services + let configuration_services = StandardConfigurationServices::new(boot_services); + commands.add_service(configuration_services); + + Ok(()) + } +} diff --git a/components/patina_uefi_services/src/component/system_table.rs b/components/patina_uefi_services/src/component/system_table.rs new file mode 100644 index 000000000..720809387 --- /dev/null +++ b/components/patina_uefi_services/src/component/system_table.rs @@ -0,0 +1,91 @@ +//! System Table Service Implementation +//! +//! Provides the actual implementation of system table access using boot services protocol location. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::service::system_table::SystemTableService; +use patina::{ + boot_services::{BootServices, StandardBootServices}, + component::{IntoComponent, params::Commands, service::IntoService}, + error::{EfiError, Result}, +}; +use r_efi::efi; + +/// Standard implementation of system table services. +/// +/// This implementation provides access to console protocols by locating them +/// through the boot services LocateProtocol interface rather than direct system table access. +#[derive(IntoService)] +#[service(dyn SystemTableService)] +pub struct StandardSystemTableService { + boot_services: StandardBootServices, +} + +impl StandardSystemTableService { + /// Creates a new StandardSystemTableService instance. + pub fn new(boot_services: StandardBootServices) -> Self { + Self { boot_services } + } +} + +impl SystemTableService for StandardSystemTableService { + fn get_console_input(&self) -> Result<&'static mut efi::protocols::simple_text_input::Protocol> { + // Use LocateProtocol to find the console input protocol + unsafe { + self.boot_services + .locate_protocol::(None) + .map_err(EfiError::from) + } + } + + fn get_console_output(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol> { + // Use LocateProtocol to find the console output protocol + unsafe { + self.boot_services + .locate_protocol::(None) + .map_err(EfiError::from) + } + } + + fn get_standard_error(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol> { + // In UEFI, standard error typically uses the same protocol as console output + // but may be on a different handle. For now, return the same as console output. + self.get_console_output() + } +} + +/// Component that produces `SystemTableService` for other components. +#[derive(IntoComponent)] +pub struct SystemTableServiceProvider; + +impl SystemTableServiceProvider { + /// Entry point for the `SystemTableServiceProvider` component. + pub fn entry_point(self, mut commands: Commands, boot_services: StandardBootServices) -> Result<()> { + let system_table_service = StandardSystemTableService::new(boot_services); + commands.add_service(system_table_service); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_standard_system_table_service_creation() { + // Test that StandardSystemTableService can be created + let boot_services = StandardBootServices::new_uninit(); + let _system_table_service = StandardSystemTableService::new(boot_services); + // This test just checks that creation does not panic + } + + #[test] + fn test_system_table_service_provider_creation() { + // Test that SystemTableServiceProvider can be created + let _provider = SystemTableServiceProvider; + // This test just checks that creation does not panic + } +} diff --git a/components/patina_uefi_services/src/component/variable.rs b/components/patina_uefi_services/src/component/variable.rs new file mode 100644 index 000000000..7762e30b4 --- /dev/null +++ b/components/patina_uefi_services/src/component/variable.rs @@ -0,0 +1,440 @@ +//! UEFI Specification Variable Services Implementation. +//! +//! The UEFI variable interface is an area that could benefit from an entirely new (and Rusty) interface. This +//! service is not intended to deviate from UEFI spec-like interfaces. They are not opinionated but rather to provide a +//! clean, object-safe interface for UEFI variable operations. An entirely new interface could be considered in the +//! future that enforces certain constraints in the interface such as UEFI variable policy that would be under a +//! different type than `UefiSpecVariableServices`. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::service::variable::UefiSpecVariableServices; +use alloc::{boxed::Box, string::String, vec, vec::Vec}; +use core::ffi::c_void; +use patina::{ + BinaryGuid, + boot_services::{BootServices, StandardBootServices, event::EventType, tpl::Tpl}, + component::{IntoComponent, params::Commands}, + error::{EfiError, Result}, + runtime_services::{RuntimeServices, StandardRuntimeServices, variable_services::VariableInfo}, +}; +use patina_macro::IntoService; +use r_efi::efi; +use spin::Mutex; + +/// Standard implementation of `UefiSpecVariableServices` that delegates to `StandardRuntimeServices`. +/// +/// This struct serves as an adapter between the high-level variable service interface +/// and the underlying UEFI runtime services, handling string conversions and error mapping. +#[derive(IntoService)] +#[service(dyn UefiSpecVariableServices)] +pub struct StandardVariableServices { + runtime_services: StandardRuntimeServices, +} + +impl StandardVariableServices { + /// Creates a new StandardVariableServices instance. + /// + /// # Arguments + /// + /// * `runtime_services` - The underlying runtime services implementation + pub fn new(runtime_services: StandardRuntimeServices) -> Self { + Self { runtime_services } + } +} + +impl UefiSpecVariableServices for StandardVariableServices { + fn get_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid) -> Result<(Vec, u32)> { + // Convert String to null-terminated UTF-16 + let name_utf16: Vec = variable_name.encode_utf16().chain([0]).collect(); + + match self.runtime_services.get_variable::>(&name_utf16, vendor_guid, None) { + Ok((data, attributes)) => Ok((data, attributes)), + Err(status) => Err(EfiError::from(status)), + } + } + + fn set_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid, attributes: u32, data: &[u8]) -> Result<()> { + // Convert String to null-terminated UTF-16 + let name_utf16: Vec = variable_name.encode_utf16().chain([0]).collect(); + + // Convert slice to Vec to satisfy Sized requirement + let data_vec = data.to_vec(); + + self.runtime_services.set_variable(&name_utf16, vendor_guid, attributes, &data_vec).map_err(EfiError::from) + } + + fn get_next_variable_name(&self, variable_name: &mut String, vendor_guid: &mut BinaryGuid) -> Result<()> { + // Convert current name to UTF-16 + let current_name_utf16: Vec = if variable_name.is_empty() { + vec![0] // Start with an empty null-terminated string + } else { + variable_name.encode_utf16().chain([0]).collect() + }; + + let mut next_name_utf16 = Vec::new(); + + let current_vendor_guid = **vendor_guid; + let mut next_vendor_guid = current_vendor_guid; + + // Get next variable name + unsafe { + self.runtime_services + .get_next_variable_name_unchecked( + ¤t_name_utf16, + ¤t_vendor_guid, + &mut next_name_utf16, + &mut next_vendor_guid, + ) + .map_err(EfiError::from)?; + } + + // Convert UTF-16 back to String (remove null terminator) + if let Some(null_pos) = next_name_utf16.iter().position(|&c| c == 0) { + next_name_utf16.truncate(null_pos); + } + + *variable_name = String::from_utf16(&next_name_utf16).map_err(|_| EfiError::InvalidParameter)?; + *vendor_guid = BinaryGuid::from(next_vendor_guid); + + Ok(()) + } + + fn query_variable_info(&self, attributes: u32) -> Result<(u64, u64, u64)> { + match self.runtime_services.query_variable_info(attributes) { + Ok(VariableInfo { + maximum_variable_storage_size, + remaining_variable_storage_size, + maximum_variable_size, + }) => Ok((maximum_variable_storage_size, remaining_variable_storage_size, maximum_variable_size)), + Err(status) => Err(EfiError::from(status)), + } + } +} + +/// Component that produces `UefiSpecVariableServices` for other components. +/// +/// This component uses protocol notifications to ensure that variable services are only registered +/// after both the Variable Architectural Protocol and Variable Write Architectural Protocol are installed. +#[derive(IntoComponent)] +pub struct VariableServicesProvider; + +/// Context passed to protocol notify callback +struct NotifyContext<'a> { + commands: Mutex>, + runtime_services: StandardRuntimeServices, + boot_services: StandardBootServices, + service_registered: Mutex, +} + +impl VariableServicesProvider { + /// Entry point for the `VariableServicesProvider` component. + /// + /// Sets up protocol notifications for the Variable Architectural Protocol and Variable Write + /// Architectural Protocol. The variable services are only registered once both protocols are installed. + pub fn entry_point( + self, + commands: Commands, + runtime_services: StandardRuntimeServices, + boot_services: StandardBootServices, + ) -> Result<()> { + // Variable Architectural Protocol GUID + const VARIABLE_ARCH_PROTOCOL_GUID: efi::Guid = + efi::Guid::from_fields(0x1e5668e2, 0x8481, 0x11d4, 0xbc, 0xf1, &[0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81]); + + // Variable Write Architectural Protocol GUID + const VARIABLE_WRITE_ARCH_PROTOCOL_GUID: efi::Guid = + efi::Guid::from_fields(0x6441f818, 0x6362, 0x4e44, 0xb5, 0x70, &[0x7d, 0xba, 0x31, 0xdd, 0x24, 0x53]); + + // Create shared context that will be passed to both callbacks + let context = Box::into_raw(Box::new(NotifyContext { + commands: Mutex::new(commands), + runtime_services, + boot_services: boot_services.clone(), + service_registered: Mutex::new(false), + })); + + // Create a single event that will be used for both protocol notifications + let event = boot_services.create_event( + EventType::NOTIFY_SIGNAL, + Tpl::NOTIFY, + Some(Self::protocol_notify_callback), + context as *mut c_void, + )?; + + // Register the same event for both protocols + // The callback will check if both are available each time it's triggered + boot_services.register_protocol_notify(&VARIABLE_ARCH_PROTOCOL_GUID, event)?; + + boot_services.register_protocol_notify(&VARIABLE_WRITE_ARCH_PROTOCOL_GUID, event)?; + + Ok(()) + } + + /// Callback triggered when either protocol is installed. + /// Checks if both protocols are now available and registers the service if so. + extern "efiapi" fn protocol_notify_callback(_event: efi::Event, context: *mut c_void) { + if context.is_null() { + return; + } + + // SAFETY: We control the context pointer lifetime + let ctx = unsafe { &*(context as *const NotifyContext) }; + + // Check if we've already registered the service + if *ctx.service_registered.lock() { + return; + } + + // Variable Architectural Protocol GUID + const VARIABLE_ARCH_PROTOCOL_GUID: efi::Guid = + efi::Guid::from_fields(0x1e5668e2, 0x8481, 0x11d4, 0xbc, 0xf1, &[0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81]); + + // Variable Write Architectural Protocol GUID + const VARIABLE_WRITE_ARCH_PROTOCOL_GUID: efi::Guid = + efi::Guid::from_fields(0x6441f818, 0x6362, 0x4e44, 0xb5, 0x70, &[0x7d, 0xba, 0x31, 0xdd, 0x24, 0x53]); + + // Check if both protocols are now available + let var_arch_available = ctx.boot_services.locate_protocol_marker(&VARIABLE_ARCH_PROTOCOL_GUID, None).is_ok(); + let var_write_arch_available = + ctx.boot_services.locate_protocol_marker(&VARIABLE_WRITE_ARCH_PROTOCOL_GUID, None).is_ok(); + + if var_arch_available && var_write_arch_available { + // Both protocols available - register the service + let variable_services = StandardVariableServices::new(ctx.runtime_services.clone()); + + ctx.commands.lock().add_service(variable_services); + *ctx.service_registered.lock() = true; + } + } +} + +#[cfg(all(test, feature = "mockall"))] +mod tests { + use super::*; + use patina::runtime_services::MockRuntimeServices; + use r_efi::efi; + + // Test constants + const TEST_VARIABLE_ATTRIBUTES: u32 = 0x07; + const TEST_VARIABLE_DATA: &[u8] = &[0x41, 0x42, 0x43]; + + /// Test-only wrapper for variable services that uses MockRuntimeServices + /// This enables testing without trying to use trait objects (which don't work with generic methods) + struct TestVariableServices { + runtime_services: MockRuntimeServices, + } + + impl TestVariableServices { + fn new(runtime_services: MockRuntimeServices) -> Self { + Self { runtime_services } + } + + fn get_variable(&self, variable_name: &str, vendor_guid: &efi::Guid) -> Result<(Vec, u32)> { + let name_utf16: Vec = variable_name.encode_utf16().chain(Some(0)).collect(); + match self.runtime_services.get_variable::>(&name_utf16, vendor_guid, None) { + Ok((data, attributes)) => Ok((data, attributes)), + Err(e) => Err(EfiError::from(e)), + } + } + + fn set_variable( + &self, + variable_name: &str, + vendor_guid: &efi::Guid, + attributes: u32, + data: &[u8], + ) -> Result<()> { + let name_utf16: Vec = variable_name.encode_utf16().chain(Some(0)).collect(); + let data_vec = data.to_vec(); + self.runtime_services.set_variable(&name_utf16, vendor_guid, attributes, &data_vec).map_err(EfiError::from) + } + + fn get_next_variable_name( + &self, + variable_name: &mut alloc::string::String, + vendor_guid: &mut efi::Guid, + ) -> Result<()> { + let current_name_utf16: Vec = variable_name.encode_utf16().chain(Some(0)).collect(); + match self.runtime_services.get_next_variable_name(¤t_name_utf16, vendor_guid) { + Ok((mut next_name, next_guid)) => { + // Remove null terminator from returned name + if let Some(null_pos) = next_name.iter().position(|&c| c == 0) { + next_name.truncate(null_pos); + } + *variable_name = alloc::string::String::from_utf16_lossy(&next_name); + *vendor_guid = next_guid; + Ok(()) + } + Err(e) => Err(EfiError::from(e)), + } + } + + fn query_variable_info(&self, attributes: u32) -> Result<(u64, u64, u64)> { + match self.runtime_services.query_variable_info(attributes) { + Ok(info) => Ok(( + info.maximum_variable_storage_size, + info.remaining_variable_storage_size, + info.maximum_variable_size, + )), + Err(e) => Err(EfiError::from(e)), + } + } + } + + #[test] + fn test_get_variable_not_found() { + let mut mock_runtime_services = MockRuntimeServices::new(); + + // Set up expectation for get_variable with UTF-16 encoded "NonExistent" + mock_runtime_services + .expect_get_variable::>() + .once() + .withf(|name, namespace, size_hint| { + let expected_name: Vec = "NonExistent".encode_utf16().chain([0]).collect(); + assert_eq!(name, expected_name.as_slice()); + let expected_guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + assert_eq!(namespace, &expected_guid); + assert_eq!(size_hint, &None); + true + }) + .returning(|_, _, _| Err(r_efi::efi::Status::NOT_FOUND)); + + let variable_services = TestVariableServices::new(mock_runtime_services); + + let result = variable_services.get_variable("NonExistent", &efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6])); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EfiError::NotFound); + } + + #[test] + fn test_get_variable_success() { + let mut mock_runtime_services = MockRuntimeServices::new(); + + // Set up expectation for successful get_variable + let test_data = TEST_VARIABLE_DATA.to_vec(); + mock_runtime_services + .expect_get_variable::>() + .once() + .withf(|name, namespace, size_hint| { + let expected_name: Vec = "TestVar".encode_utf16().chain([0]).collect(); + assert_eq!(name, expected_name.as_slice()); + let expected_guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + assert_eq!(namespace, &expected_guid); + assert_eq!(size_hint, &None); + true + }) + .returning(move |_, _, _| Ok((test_data.clone(), TEST_VARIABLE_ATTRIBUTES))); + + let variable_services = TestVariableServices::new(mock_runtime_services); + + let result = variable_services.get_variable("TestVar", &efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6])); + assert!(result.is_ok()); + let (data, attributes) = result.unwrap(); + assert_eq!(data.as_slice(), TEST_VARIABLE_DATA); + assert_eq!(attributes, TEST_VARIABLE_ATTRIBUTES); + } + + #[test] + fn test_set_variable_success() { + let mut mock_runtime_services = MockRuntimeServices::new(); + + // Set up expectation for successful set_variable + mock_runtime_services + .expect_set_variable::>() + .once() + .withf(|name, namespace, attributes, data| { + let expected_name: Vec = "TestVar".encode_utf16().chain([0]).collect(); + assert_eq!(name, expected_name.as_slice()); + let expected_guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + assert_eq!(namespace, &expected_guid); + assert_eq!(attributes, &TEST_VARIABLE_ATTRIBUTES); + assert_eq!(data.as_slice(), TEST_VARIABLE_DATA); + true + }) + .returning(|_, _, _, _| Ok(())); + + let variable_services = TestVariableServices::new(mock_runtime_services); + + let result = variable_services.set_variable( + "TestVar", + &efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]), + TEST_VARIABLE_ATTRIBUTES, + TEST_VARIABLE_DATA, + ); + assert!(result.is_ok()); + } + + #[test] + fn test_get_next_variable_name_success() { + use alloc::string::String; + + let mut mock_runtime_services = MockRuntimeServices::new(); + + // Set up expectation for successful get_next_variable_name + let next_name_utf16: Vec = "NextVar".encode_utf16().chain([0]).collect(); + let next_guid = efi::Guid::from_fields(1, 1, 1, 1, 1, &[1; 6]); + + mock_runtime_services + .expect_get_next_variable_name() + .once() + .withf(|prev_name, prev_namespace| { + let expected_prev_name: Vec = vec![0]; // Just null terminator for first call + assert_eq!(prev_name, expected_prev_name.as_slice()); + let expected_guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + assert_eq!(prev_namespace, &expected_guid); + true + }) + .returning(move |_, _| Ok((next_name_utf16.clone(), next_guid))); + + let variable_services = TestVariableServices::new(mock_runtime_services); + + let mut variable_name = String::new(); // Start with empty name + let mut vendor_guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + + let result = variable_services.get_next_variable_name(&mut variable_name, &mut vendor_guid); + assert!(result.is_ok()); + assert_eq!(variable_name, "NextVar"); + assert_eq!(vendor_guid, next_guid); + } + + #[test] + fn test_query_variable_info_success() { + let mut mock_runtime_services = MockRuntimeServices::new(); + + const MAX_STORAGE: u64 = 1024 * 1024; + const REMAINING_STORAGE: u64 = 512 * 1024; + const MAX_VAR_SIZE: u64 = 64 * 1024; + + mock_runtime_services + .expect_query_variable_info() + .once() + .withf(|attributes| { + assert_eq!(attributes, &TEST_VARIABLE_ATTRIBUTES); + true + }) + .returning(|_| { + Ok(VariableInfo { + maximum_variable_storage_size: MAX_STORAGE, + remaining_variable_storage_size: REMAINING_STORAGE, + maximum_variable_size: MAX_VAR_SIZE, + }) + }); + + let variable_services = TestVariableServices::new(mock_runtime_services); + + let result = variable_services.query_variable_info(TEST_VARIABLE_ATTRIBUTES); + assert!(result.is_ok()); + let (max_storage, remaining_storage, max_var_size) = result.unwrap(); + assert_eq!(max_storage, MAX_STORAGE); + assert_eq!(remaining_storage, REMAINING_STORAGE); + assert_eq!(max_var_size, MAX_VAR_SIZE); + } + + #[test] + fn test_variable_services_provider_creation() { + let _provider = VariableServicesProvider; + } +} diff --git a/components/patina_uefi_services/src/lib.rs b/components/patina_uefi_services/src/lib.rs new file mode 100644 index 000000000..05cd3935f --- /dev/null +++ b/components/patina_uefi_services/src/lib.rs @@ -0,0 +1,69 @@ +//! UEFI Services Component +//! +//! ## Usage +//! +//! To use UEFI services in your component, add the relevant service as a dependency in the component entry point +//! parameter list. For example, to use console services: +//! +//! ```rust,no_run +//! use patina::component::{IntoComponent, prelude::Service}; +//! use patina_uefi_services::service::console::ConsoleServices; +//! use patina::error::Result; +//! +//! #[derive(IntoComponent)] +//! struct MyComponent; +//! +//! impl MyComponent { +//! fn entry_point( +//! self, +//! console: Service +//! ) -> Result<()> { +//! console.clear_screen()?; +//! console.output_string("Hello from Patina!")?; +//! console.set_cursor_position(10, 5)?; +//! console.output_string("Positioned text!")?; +//! Ok(()) +//! } +//! } +//! ``` +//! +//! ## Integration +//! +//! To integrate this component into your Patina system, add the provider component: +//! +//! ```rust,no_run +//! use patina_uefi_services::UefiServicesProvider; +//! // In your main function or component setup: +//! let provider = UefiServicesProvider; +//! // ... register with your system ... +//! ``` +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +#![cfg_attr(all(not(feature = "std"), not(test), not(feature = "mockall")), no_std)] + +extern crate alloc; + +pub mod component; +pub mod service; +pub mod types; + +// Re-exported for easier access +pub use component::provider::UefiServicesProvider; + +#[cfg(all(test, feature = "std"))] +mod integration_tests { + use super::*; + use patina::{boot_services::StandardBootServices, component::params::Commands, component::prelude::Service}; + + #[test] + fn test_uefi_services_provider_integration() { + // Test that the UefiServicesProvider can be instantiated and called + let _provider = UefiServicesProvider; + let _commands = Commands::mock(); + let _boot_services = Service::::new_uninit(); + // Note: The test is considered successful if there is no panic + } +} diff --git a/components/patina_uefi_services/src/service.rs b/components/patina_uefi_services/src/service.rs new file mode 100644 index 000000000..f7ca89366 --- /dev/null +++ b/components/patina_uefi_services/src/service.rs @@ -0,0 +1,18 @@ +//! Patina UEFI Services +//! +//! This crate provides an abstraction to "UEFI services" for Patina components. The goal of these abstractions is to +//! provide safe, idiomatic access to UEFI Services, so even if the underlying details of how the UEFI services are +//! implemented change over time, Patina components can use a stable, well-defined interface. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +pub mod console; +pub mod event; +pub mod image; +pub mod misc; +pub mod protocol; +pub mod runtime; +pub mod system_table; +pub mod variable; diff --git a/components/patina_uefi_services/src/service/console.rs b/components/patina_uefi_services/src/service/console.rs new file mode 100644 index 000000000..8eda2905d --- /dev/null +++ b/components/patina_uefi_services/src/service/console.rs @@ -0,0 +1,184 @@ +//! Console Services Abstraction +//! +//! Trait definitions for a service that provides UEFI console operations. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use patina::error::Result; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Console input and output operations abstraction. +/// +/// Provides text input, text output, and basic console management. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait ConsoleServices { + /// Writes a string to the console output. + /// + /// # Arguments + /// * `text` - The text string to output to the console + /// + /// # Returns + /// * `Result<()>` - Result status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// console_services.output_string("Hello, UEFI!")?; + /// # Ok(()) + /// # } + /// ``` + fn output_string(&self, text: &str) -> Result<()>; + + /// Clears the console output screen. + /// + /// # Returns + /// * `Result<()>` - Result status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// console_services.clear_screen()?; + /// # Ok(()) + /// # } + /// ``` + fn clear_screen(&self) -> Result<()>; + + /// Sets the cursor position on the console. + /// + /// # Arguments + /// * `column` - Column position (0-based) + /// * `row` - Row position (0-based) + /// + /// # Returns + /// * `Result<()>` - Result status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// console_services.set_cursor_position(10, 5)?; + /// # Ok(()) + /// # } + /// ``` + fn set_cursor_position(&self, column: usize, row: usize) -> Result<()>; + + /// Gets the current cursor position. + /// + /// # Returns + /// * `Result<(usize, usize)>` - Tuple of (column, row) position + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// let (col, row) = console_services.get_cursor_position()?; + /// # Ok(()) + /// # } + /// ``` + fn get_cursor_position(&self) -> Result<(usize, usize)>; + + /// Enables or disables the cursor visibility. + /// + /// # Arguments + /// * `visible` - True to make cursor visible, false to hide it + /// + /// # Returns + /// * `Result<()>` - Result status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// console_services.enable_cursor(true)?; // Show cursor + /// console_services.enable_cursor(false)?; // Hide cursor + /// # Ok(()) + /// # } + /// ``` + fn enable_cursor(&self, visible: bool) -> Result<()>; + + /// Reads a keystroke from the console input. + /// + /// This function will block until a key is pressed. + /// + /// # Returns + /// * `Result` - The Unicode character code of the key pressed + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// let key = console_services.read_key_stroke()?; + /// if key == 0x0D { // Enter key + /// println!("Enter pressed!"); + /// } + /// # Ok(()) + /// # } + /// ``` + fn read_key_stroke(&self) -> Result; + + /// Checks if a keystroke is available without blocking. + /// + /// # Returns + /// * `Result` - True if a keystroke is available, false otherwise + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// if console_services.is_key_available()? { + /// let key = console_services.read_key_stroke()?; + /// // Process the key... + /// } + /// # Ok(()) + /// # } + /// ``` + fn is_key_available(&self) -> Result; + + /// Resets the console input buffer. + /// + /// # Arguments + /// * `extended_verification` - Perform extended verification of input device + /// + /// # Returns + /// * `Result<()>` - Result status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// console_services.reset_input(false)?; + /// # Ok(()) + /// # } + /// ``` + fn reset_input(&self, extended_verification: bool) -> Result<()>; + + /// Gets the current console mode information. + /// + /// # Returns + /// * `Result<(usize, usize)>` - Tuple of (columns, rows) for current mode + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::console::ConsoleServices; + /// # use patina::error::Result; + /// # fn example(console_services: &dyn ConsoleServices) -> Result<()> { + /// let (cols, rows) = console_services.query_mode()?; + /// # Ok(()) + /// # } + /// ``` + fn query_mode(&self) -> Result<(usize, usize)>; +} diff --git a/components/patina_uefi_services/src/service/event.rs b/components/patina_uefi_services/src/service/event.rs new file mode 100644 index 000000000..faf04499d --- /dev/null +++ b/components/patina_uefi_services/src/service/event.rs @@ -0,0 +1,149 @@ +//! Event Services Abstraction +//! +//! A Patina service abstraction for UEFI event and timer operations. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::types::Event; +use patina::{ + BinaryGuid, + boot_services::{ + event::{EventTimerType, EventType}, + tpl::Tpl, + }, + error::Result, +}; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Provides UEFI event and timer operations through a memory-safe interface. +/// +/// ## Memory Safety +/// +/// Provides type-safe event handle management and automatic cleanup. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait EventServices { + /// Creates a new UEFI event with the specified type and notification TPL. + /// + /// # Arguments + /// + /// * `event_type` - The type of event to create (timer, notification, etc.) + /// * `notify_tpl` - The Task Priority Level for event notifications + /// + /// # Returns + /// + /// Returns an `Event` handle that can be used with other event operations. + /// + /// # Examples + /// + /// ```rust,no_run + /// # use patina_uefi_services::service::event::EventServices; + /// # use patina::boot_services::{event::EventType, tpl::Tpl}; + /// # fn example(event_services: &dyn EventServices) -> patina::error::Result<()> { + /// let event = event_services.create_event(EventType::TIMER, Tpl::APPLICATION)?; + /// // Use event... + /// event_services.close_event(event)?; + /// # Ok(()) + /// # } + /// ``` + fn create_event(&self, event_type: EventType, notify_tpl: Tpl) -> Result; + + /// Closes and deallocates a UEFI event. + /// + /// # Arguments + /// + /// * `event` - The event handle to close + /// + /// # Safety + /// + /// After calling this method, the event handle becomes invalid and must not be used. + fn close_event(&self, event: Event) -> Result<()>; + + /// Configures a timer event with the specified timing parameters. + /// + /// # Arguments + /// + /// * `event` - The timer event to configure + /// * `timer_type` - Type of timer (relative, periodic, or cancel) + /// * `trigger_time` - Time in 100ns units for the timer to trigger + fn set_timer(&self, event: Event, timer_type: EventTimerType, trigger_time: u64) -> Result<()>; + + /// Waits for one or more events to be signaled. + /// + /// # Arguments + /// + /// * `events` - Mutable slice of events to wait for + /// + /// # Returns + /// + /// Returns the index of the event that was signaled. + /// + /// # Safety + /// + /// The events slice is modified during the wait operation to track event states. + fn wait_for_event(&self, events: &mut [Event]) -> Result; + + /// Checks if an event is currently signaled without blocking. + /// + /// # Arguments + /// + /// * `event` - The event to check + /// + /// # Returns + /// + /// Returns `Ok(())` if the event is signaled, or an error if not signaled or invalid. + fn check_event(&self, event: Event) -> Result<()>; + + /// Manually signals an event. + /// + /// # Arguments + /// + /// * `event` - The event to signal + fn signal_event(&self, event: Event) -> Result<()>; + + /// Creates a system event that is automatically signaled at system milestones. + /// + /// # Arguments + /// + /// * `event_type` - The type of system event to create + /// * `notify_tpl` - The Task Priority Level for event notifications + /// + /// # Returns + /// + /// Returns an `Event` handle for the system event. + fn create_system_event(&self, event_type: EventType, notify_tpl: Tpl) -> Result; +} + +/// An extension trait for `EventServices` that provides closure-based event creation. +/// +/// Note: This trait is separate from `EventServices` because generic methods are not compatible with dynamic +/// dispatch (dyn trait objects). +pub trait EventServicesClosureExt: EventServices { + /// Creates a system event with a closure callback. + /// + /// This is a convenience method that creates an event and registers it with a system event group for automatic + /// signaling at system milestones. + /// + /// # Arguments + /// + /// * `event_type` - The type of system event to create + /// * `notify_tpl` - The Task Priority Level for event notifications + /// * `callback` - Closure to execute when the event is signaled + /// * `event_group` - Optional event group for system events + /// + /// # Returns + /// + /// Returns an `Event` handle for the system event. + fn create_system_event_with_callback( + &self, + event_type: EventType, + notify_tpl: Tpl, + callback: F, + event_group: &'static BinaryGuid, + ) -> Result + where + F: FnMut(Event) + Send + Sync + 'static; +} diff --git a/components/patina_uefi_services/src/service/image.rs b/components/patina_uefi_services/src/service/image.rs new file mode 100644 index 000000000..fc477911d --- /dev/null +++ b/components/patina_uefi_services/src/service/image.rs @@ -0,0 +1,96 @@ +//! Image Services Abstraction +//! +//! Trait definitions for UEFI image related operations. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use crate::types::Handle; +use alloc::vec::Vec; +use patina::error::Result; +use r_efi::efi; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Image loading and execution operations. +/// +/// # Note +/// +/// Because the concept of "image services" is inherently tied to supporting Platform Initialization (PI) Spec based +/// images, this service interface does accept a Device Path protocol directly as opposed to an abstraction. As the +/// abstraction is expected to not be needed in a Pure Rust Patina-based dispatch process. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait ImageServices { + /// Loads the image described by the given Device Path protocol instance into memory. + /// + /// # Arguments + /// * `parent_image_handle` - Handle of the parent image that is loading this image + /// * `device_path` - The device path from which to load the image + /// * `source_buffer` - Optional buffer containing the image data to load + /// * `source_size` - Size of the source buffer + /// + /// # Returns + /// * `Result` - Handle to the loaded image on success + /// + /// # Safety + /// + /// The caller must ensure: + /// - The `device_path` pointer, if not null, points to a valid and properly formatted UEFI Device Path Protocol structure + /// - The device path structure remains valid for the duration of this call + /// - If `source_buffer` is provided, `source_size` accurately reflects the buffer's length + /// - The device path is properly null-terminated according to UEFI Device Path Protocol specifications + unsafe fn load_image( + &self, + parent_image_handle: Handle, + device_path: *mut efi::protocols::device_path::Protocol, + source_buffer: Option>, + source_size: usize, + ) -> Result; + + /// Transfers control to a loaded image's entry point. + /// + /// Starts execution of a previously loaded image. If the image returns + /// with exit data, it will be included in the result. + /// + /// # Arguments + /// + /// * `image_handle` - Handle to the image to start + /// + /// # Returns + /// + /// Empty Vec if successful with no exit data, or Vec containing exit data + /// if the image provided exit information. + fn start_image(&self, image_handle: Handle) -> Result>; + + /// Terminates a loaded EFI image and returns control to boot services. + /// + /// Used by a loaded image to exit and return control to the entity that + /// started it. Can optionally provide exit data. + /// + /// # Arguments + /// + /// * `image_handle` - Handle of the currently running image + /// * `exit_status` - Exit status to return + /// * `exit_data` - Optional exit data to provide to caller + /// + /// # Returns + /// + /// This function typically does not return as it transfers control. + fn exit(&self, image_handle: Handle, exit_status: efi::Status, exit_data: Option>) -> Result<()>; + + /// Unloads an image from memory. + /// + /// Unloads a previously loaded image, freeing its memory and resources. + /// The image must not be currently executing. + /// + /// # Arguments + /// + /// * `image_handle` - Handle to the image to unload + /// + /// # Returns + /// + /// Success if the image was unloaded successfully. + fn unload_image(&self, image_handle: Handle) -> Result<()>; +} diff --git a/components/patina_uefi_services/src/service/misc.rs b/components/patina_uefi_services/src/service/misc.rs new file mode 100644 index 000000000..eeb5043c5 --- /dev/null +++ b/components/patina_uefi_services/src/service/misc.rs @@ -0,0 +1,302 @@ +//! Miscellaneous Services Abstractions +//! +//! This module provides trait definitions for various UEFI utility functions. +//! +//! These services are broken into cohesive functional areas to allow Patina Components to declare specific +//! dependencies so what they actually depend on is more apparent in their dependency-injected parameter list. +//! +//! ## Service Groups +//! +//! - [`TimingServices`] - Watchdog timer and delay operations +//! - [`MemoryUtilityServices`] - Memory copying and filling operations +//! - [`SystemUtilityServices`] - System counters and data integrity calculations +//! - [`ConfigurationServices`] - System configuration table management +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use core::ffi::c_void; +use patina::{BinaryGuid, error::Result}; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Timing-related UEFI services for watchdog timers and delays. +/// +/// This service group provides timing control operations including watchdog timer management and precise delay +/// functions. +/// +/// ## Design Notes +/// +/// This trait is intended to be object-safe to support dynamic dispatch. +/// +/// ## Example Usage +/// +/// ```rust +/// use patina::component::{IntoComponent, prelude::Service}; +/// use patina_uefi_services::service::misc::TimingServices; +/// use patina::error::Result; +/// +/// #[derive(IntoComponent)] +/// struct MyTimingComponent; +/// +/// impl MyTimingComponent { +/// fn entry_point(self, timing: Service) -> Result<()> { +/// // Set a 5-second watchdog timer +/// timing.set_watchdog_timer(5)?; +/// // Delay for 1000 microseconds +/// timing.stall(1000)?; +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait TimingServices { + /// Sets the system's watchdog timer. + /// + /// The watchdog timer provides a mechanism to reset the system if it becomes unresponsive. Setting timeout to 0 + /// disables the watchdog timer. + /// + /// # Parameters + /// - `timeout` - Timeout value in seconds (0 disables the timer) + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(status)` on failure + fn set_watchdog_timer(&self, timeout: usize) -> Result<()>; + + /// Induces a fine-grained delay. + /// + /// This function stalls the processor for at least the specified number of microseconds. This is a blocking + /// operation. + /// + /// # Parameters + /// - `microseconds` - Number of microseconds to delay + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(status)` on failure + fn stall(&self, microseconds: usize) -> Result<()>; +} + +/// Memory utility services for copying and filling memory. +/// +/// This service group provides memory manipulation utilities including safe memory copying and buffer filling +/// operations. +/// +/// ## Design Notes +/// +/// This trait is object-safe to support dynamic dispatch through `Service`. +/// +/// ## Usage Notes +/// +/// This service provides very basic memory operations. It is made available to simply provide a safe wrapper to the +/// equivalent function in the boot services table if that specific need arises in a Patina component. If that is not +/// the case, use native Rust mechanisms in place of these functions in Patina components. +/// +/// ## Example Usage +/// +/// ```rust +/// use patina::component::{IntoComponent, prelude::Service}; +/// use patina_uefi_services::component::misc::StandardMemoryUtilityServices; +/// use patina_uefi_services::service::misc::MemoryUtilityServices; +/// use patina::error::Result; +/// +/// #[derive(IntoComponent)] +/// struct MyMemoryComponent; +/// +/// impl MyMemoryComponent { +/// fn entry_point(self, mem_utils: Service) -> Result<()> { +/// let src = 42u32; +/// let mut dest = 0u32; +/// // Copy memory safely +/// mem_utils.copy_mem(&mut dest, &src); +/// // Fill a buffer with zeros +/// let mut buffer = [0xFFu8; 16]; +/// mem_utils.set_mem(&mut buffer, 0); +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait MemoryUtilityServices { + /// Copies the contents of one buffer to another buffer. + /// + /// This is a safe memory copy operation that ensures type safety and + /// proper size calculations. + /// + /// # Parameters + /// - `dest` - Destination buffer (mutable reference) + /// - `src` - Source buffer (immutable reference) + fn copy_mem(&self, dest: &mut T, src: &T); + + /// Copies memory from source to destination (unsafe version). + /// + /// This provides direct access to the underlying unsafe memory copy + /// operation when needed for performance or specific use cases. + /// + /// # Safety + /// - `dest` and `src` must be valid pointers to continuous memory + /// - `length` must not exceed the size of either buffer + /// - Buffers must not overlap (undefined behavior) + /// + /// # Parameters + /// - `dest` - Destination pointer + /// - `src` - Source pointer + /// - `length` - Number of bytes to copy + unsafe fn copy_mem_unchecked(&self, dest: *mut c_void, src: *const c_void, length: usize); + + /// Fills a buffer with a specified value. + /// + /// This function sets all bytes in the buffer to the specified value, + /// similar to the C library `memset` function. + /// + /// # Parameters + /// - `buffer` - Buffer to fill (mutable slice) + /// - `value` - Byte value to fill the buffer with + fn set_mem(&self, buffer: &mut [u8], value: u8); +} + +/// System utility services for counters and data integrity. +/// +/// This service group provides system-level utilities including monotonic counters and CRC32 calculations. +/// +/// ## Usage Notes +/// +/// This service provides very basic memory operations. It is made available to simply provide a safe wrapper to the +/// equivalent function in the boot services table if that specific need arises in a Patina component. If that is not +/// the case, use native Rust mechanisms in place of these functions in Patina components. +/// +/// ## Example Usage +/// +/// ```rust +/// use patina::component::{IntoComponent, prelude::Service}; +/// use patina_uefi_services::component::misc::StandardSystemUtilityServices; +/// use patina_uefi_services::service::misc::SystemUtilityServices; +/// use patina::error::Result; +/// +/// #[derive(IntoComponent)] +/// struct MySystemComponent; +/// +/// impl MySystemComponent { +/// fn entry_point(self, sys_utils: Service) -> Result<()> { +/// // Get a unique counter value +/// let count = sys_utils.get_next_monotonic_count()?; +/// // Calculate CRC32 of some data +/// let data = 0x12345678u32; +/// let crc = sys_utils.calculate_crc_32(&data)?; +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait SystemUtilityServices { + /// Returns a monotonically increasing count for the platform. + /// + /// This function returns a 64-bit value that is guaranteed to increase + /// monotonically during the current boot. This can be used for unique + /// identifiers or sequence numbers. + /// + /// # Returns + /// - `Ok(count)` - The next monotonic count value + /// - `Err(status)` on failure + fn get_next_monotonic_count(&self) -> Result; + + /// Computes and returns a 32-bit CRC for data. + /// + /// This function calculates a CRC32 checksum for the provided data, + /// which can be used for data integrity verification. + /// + /// # Parameters + /// - `data` - Data to calculate CRC32 for + /// + /// # Returns + /// - `Ok(crc32)` - The calculated CRC32 value + /// - `Err(status)` on failure + fn calculate_crc_32(&self, data: &T) -> Result; + + /// Computes CRC32 for raw data (unsafe version). + /// + /// This provides direct access to the underlying unsafe CRC32 calculation + /// when needed for performance or specific use cases. + /// + /// # Safety + /// - `data` must be a valid pointer to continuous memory + /// - `data_size` must not exceed the actual size of the data + /// + /// # Parameters + /// - `data` - Pointer to data + /// - `data_size` - Size of data in bytes + /// + /// # Returns + /// - `Ok(crc32)` - The calculated CRC32 value + /// - `Err(status)` on failure + unsafe fn calculate_crc_32_unchecked(&self, data: *const c_void, data_size: usize) -> Result; +} + +/// Configuration services for system configuration table management. +/// +/// This service group provides configuration table management operations. +/// +/// ## Example Usage +/// +/// ```rust +/// use patina::component::{IntoComponent, prelude::Service}; +/// use patina_uefi_services::service::misc::ConfigurationServices; +/// use patina::error::Result; +/// use patina::BinaryGuid; +/// +/// #[derive(IntoComponent)] +/// struct MyConfigComponent; +/// +/// impl MyConfigComponent { +/// fn entry_point(self, config: Service) -> Result<()> { +/// let guid = BinaryGuid::from_fields(0x12345678, 0x1234, 0x5678, 0x12, 0x34, &[0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); +/// let table_data = 0x12345678u32; +/// // Install a configuration table (unsafe example) +/// unsafe { +/// config.install_configuration_table_unchecked( +/// &guid, +/// &table_data as *const u32 as *mut core::ffi::c_void +/// )?; +/// } +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait ConfigurationServices { + /// Removes a configuration table entry from the EFI System Table. + /// + /// # Parameters + /// - `guid` - GUID that identifies the configuration table to remove + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(status)` on failure (e.g., if the table doesn't exist) + fn remove_configuration_table(&self, guid: &BinaryGuid) -> Result<()>; + + /// Adds, updates, or removes a configuration table entry from the EFI System Table (unsafe). + /// + /// This is the low-level unsafe interface. For type-safe installation of configuration + /// tables, consider using the `StandardConfigurationServices` concrete type directly + /// which provides a safe `install_configuration_table` method. + /// + /// Configuration tables provide a way to pass data structures between + /// different phases of the boot process or to the operating system. + /// + /// # Safety + /// - The table pointer must match the expected type for the GUID + /// - The table data must remain valid for the lifetime of the system + /// - Passing a null pointer removes the configuration table entry + /// + /// # Parameters + /// - `guid` - GUID that identifies the configuration table + /// - `table` - Pointer to the configuration table data + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(status)` on failure + unsafe fn install_configuration_table_unchecked(&self, guid: &BinaryGuid, table: *mut c_void) -> Result<()>; +} diff --git a/components/patina_uefi_services/src/service/protocol.rs b/components/patina_uefi_services/src/service/protocol.rs new file mode 100644 index 000000000..a4c31a7df --- /dev/null +++ b/components/patina_uefi_services/src/service/protocol.rs @@ -0,0 +1,144 @@ +//! Protocol Services Abstraction +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! + +use crate::types::Handle; +use alloc::vec::Vec; +use core::ffi::c_void; +use patina::{BinaryGuid, boot_services::protocol_handler::HandleSearchType, error::Result}; +use r_efi::efi; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Protocol management operations abstraction. +/// +/// It is recommended Patina components do not use protocols directly at all. A Patina service should be added instead +/// and Patina Components depend on the service. In the case the protocol must be used or a Patina component is wrapping +/// protocol access to provide a service, this service group provides protocol access to components. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait ProtocolServices { + /// Installs a protocol interface on a device handle. + /// + /// Creates a new handle or adds a protocol to an existing handle. + /// This is typically used by drivers to publish their services. + /// + /// # Arguments + /// + /// * `handle` - Mutable reference to handle (null creates new handle) + /// * `protocol` - GUID of the protocol being installed (must be a static constant) + /// * `interface_type` - Type of interface (typically `NATIVE_INTERFACE`) + /// * `interface` - Pointer to the protocol interface structure + /// + /// # Safety + /// + /// The caller must ensure: + /// - The `interface` pointer is valid and points to a properly initialized protocol interface structure + /// - The interface structure matches the type expected for the given protocol GUID + /// - The interface structure remains valid for the lifetime it will be installed + /// - The interface pointer is properly aligned for the protocol type + /// + /// # Notes + /// + /// This is a low-level interface for protocol installation. For type-safe installation, + /// prefer using the higher-level `BootServices` methods when available. + unsafe fn install_protocol_interface( + &self, + handle: &mut Handle, + protocol: &'static BinaryGuid, + interface_type: efi::InterfaceType, + interface: *mut c_void, + ) -> Result<()>; + + /// Removes a protocol interface from a device handle. + /// + /// Uninstalls a previously installed protocol interface. + /// + /// # Arguments + /// + /// * `handle` - Handle containing the protocol to remove + /// * `protocol` - GUID of the protocol to remove (must be a static constant) + /// * `interface` - Pointer to the interface that was installed + /// + /// # Safety + /// + /// The caller must ensure: + /// - The `interface` pointer matches the exact pointer that was used during installation + /// - The handle and protocol combination is valid + /// - No other code is currently using the protocol interface being removed + unsafe fn uninstall_protocol_interface( + &self, + handle: Handle, + protocol: &'static BinaryGuid, + interface: *mut c_void, + ) -> Result<()>; + + /// Queries a handle to determine if it supports a specified protocol. + /// + /// Returns a pointer to the protocol interface if found. + /// + /// # Arguments + /// + /// * `handle` - Handle to query + /// * `protocol` - GUID of the protocol to find (must be a static constant) + /// + /// # Returns + /// + /// Pointer to the protocol interface if found. + /// + /// # Safety + /// + /// The caller must ensure: + /// - The returned pointer is cast to the correct protocol interface type matching the GUID + /// - The protocol interface is not used after the handle is destroyed or the protocol is uninstalled + /// - The protocol interface structure is accessed according to its defined memory layout + /// + /// # Notes + /// + /// For type-safe protocol access, prefer using the higher-level `BootServices` methods when available. + unsafe fn handle_protocol(&self, handle: Handle, protocol: &'static BinaryGuid) -> Result<*mut c_void>; + + /// Locates the first instance of a protocol. + /// + /// Finds the first protocol instance in the system that matches the GUID. + /// + /// # Arguments + /// + /// * `protocol` - GUID of the protocol to locate (must be a static constant) + /// * `registration` - Optional registration key from protocol notifications + /// + /// # Returns + /// + /// Pointer to the first matching protocol interface. + /// + /// # Safety + /// + /// The caller must ensure: + /// - The returned pointer is cast to the correct protocol interface type matching the GUID + /// - The protocol interface is not used after it has been uninstalled from all handles + /// - If `registration` is provided, it must be a valid registration key from a protocol notification + /// - The protocol interface structure is accessed according to its defined memory layout + unsafe fn locate_protocol( + &self, + protocol: &'static BinaryGuid, + registration: Option<*mut c_void>, + ) -> Result<*mut c_void>; + + /// Returns an array of handles that support the requested protocol. + /// + /// Finds all handles in the system that have the specified protocol installed. + /// + /// # Arguments + /// + /// * `search_type` - Type of search to perform (AllHandle, ByProtocol, etc.) + /// + /// # Returns + /// + /// Vector of handles that match the search criteria. + fn locate_handle_buffer(&self, search_type: HandleSearchType) -> Result>; +} diff --git a/components/patina_uefi_services/src/service/runtime.rs b/components/patina_uefi_services/src/service/runtime.rs new file mode 100644 index 000000000..9b9f11ab8 --- /dev/null +++ b/components/patina_uefi_services/src/service/runtime.rs @@ -0,0 +1,133 @@ +//! Runtime Services Abstraction +//! +//! While UEFI Runtime Services are, by definition, persistent after ExitBootServices(), a given implementation +//! of this trait may not be. Since this is only intended to be used by Patina components and Patina components are +//! not supported at runtime at this time, this should not be an issue for component writers. However, if runtime +//! components are supported, the runtime services component producing this service must persist through runtime. +//! +//! At this time, it is expected that Runtime Services will ultimately map to C drivers through an FFI, and these +//! services simply provide safe wrapper for Patina components directly to those services. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! + +use alloc::{string::String, vec::Vec}; +use patina::BinaryGuid; +use patina::error::Result; +use r_efi::efi; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// Variable storage and retrieval operations. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait RuntimeVariableServices { + /// Gets the value of a variable. + /// + /// # Arguments + /// + /// * `variable_name` - Name of the variable to retrieve + /// * `vendor_guid` - GUID identifying the variable namespace + /// + /// # Returns + /// + /// Variable data as a `Vec` + fn get_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid) -> Result>; + + /// Sets the value of a variable. + /// + /// # Arguments + /// + /// * `variable_name` - Name of the variable to set + /// * `vendor_guid` - GUID identifying the variable namespace + /// * `attributes` - Variable attributes (boot service, runtime, etc.) + /// * `data` - Data to store in the variable + fn set_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid, attributes: u32, data: &[u8]) -> Result<()>; + + /// Enumerates the current variable names. + /// + /// # Arguments + /// + /// * `variable_name` - Current variable name (modified on return) + /// * `vendor_guid` - Current vendor GUID (modified on return) + fn get_next_variable_name(&self, variable_name: &mut String, vendor_guid: &mut BinaryGuid) -> Result<()>; + + /// Returns information about the EFI variables. + /// + /// # Arguments + /// + /// * `attributes` - Variable attributes to query + /// + /// # Returns + /// + /// Tuple of (MaximumVariableStorageSize, RemainingVariableStorageSize, MaximumVariableSize) + fn query_variable_info(&self, attributes: u32) -> Result<(u64, u64, u64)>; +} + +/// Time and date management operations. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait RuntimeTimeServices { + /// Returns the current time and date information. + fn get_time(&self) -> Result; + + /// Sets the current local time and date information. + /// + /// # Arguments + /// + /// * `time` - Time structure to set + fn set_time(&self, time: &efi::Time) -> Result<()>; + + /// Returns the current wakeup alarm clock setting. + /// + /// # Returns + /// + /// Tuple of (Enabled, Pending, Time) + fn get_wakeup_time(&self) -> Result<(bool, bool, efi::Time)>; + + /// Sets the system wakeup alarm clock time. + /// + /// # Arguments + /// + /// * `enable` - Whether to enable the wakeup alarm + /// * `time` - Optional time to set for the alarm (efi::Time is Copy, so passed by value) + fn set_wakeup_time(&self, enable: bool, time: Option) -> Result<()>; +} + +/// System reset operations. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait RuntimeResetServices { + /// Resets the entire platform. + /// + /// Note: This function never returns on success (system resets). + /// In test/mock environments, returns Ok(()) to allow testing. + /// + /// # Arguments + /// + /// * `reset_type` - Type of reset to perform + /// * `reset_status` - Status code for the reset + /// * `data` - Optional additional reset data (Vec for mockall compatibility) + #[cfg(not(any(test, feature = "mockall")))] + fn reset_system(&self, reset_type: efi::ResetType, reset_status: efi::Status, data: Option<&[u8]>) -> !; + + /// Resets the entire platform (mock version for testing). + /// + /// This is the test/mock version that returns Result instead of never type + /// and uses Vec instead of &[u8] to avoid lifetime issues with mockall. + /// + /// # Arguments + /// + /// * `reset_type` - Type of reset to perform + /// * `reset_status` - Status code for the reset + /// * `data` - Optional additional reset data + #[cfg(any(test, feature = "mockall"))] + fn reset_system( + &self, + reset_type: efi::ResetType, + reset_status: efi::Status, + data: Option>, + ) -> Result<()>; +} diff --git a/components/patina_uefi_services/src/service/system_table.rs b/components/patina_uefi_services/src/service/system_table.rs new file mode 100644 index 000000000..c32c8ebb0 --- /dev/null +++ b/components/patina_uefi_services/src/service/system_table.rs @@ -0,0 +1,47 @@ +//! System Table Service Abstraction +//! +//! Provides access to UEFI System Table exposed functionality. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use patina::error::Result; +use r_efi::efi; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// System Table access service. +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait SystemTableService { + /// Gets the console input protocol. + /// + /// # Returns + /// * `Result<&'static mut efi::protocols::simple_text_input::Protocol>` - Console input protocol + /// + /// # Errors + /// * `EfiError::NotFound` - If console input protocol is not available + /// * `EfiError::NotReady` - If system table is not initialized + fn get_console_input(&self) -> Result<&'static mut efi::protocols::simple_text_input::Protocol>; + + /// Gets the console output protocol. + /// + /// # Returns + /// * `Result<&'static mut efi::protocols::simple_text_output::Protocol>` - Console output protocol + /// + /// # Errors + /// * `EfiError::NotFound` - If console output protocol is not available + /// * `EfiError::NotReady` - If system table is not initialized + fn get_console_output(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol>; + + /// Gets the standard error output protocol. + /// + /// # Returns + /// * `Result<&'static mut efi::protocols::simple_text_output::Protocol>` - Standard error output protocol + /// + /// # Errors + /// * `EfiError::NotFound` - If standard error protocol is not available + /// * `EfiError::NotReady` - If system table is not initialized + fn get_standard_error(&self) -> Result<&'static mut efi::protocols::simple_text_output::Protocol>; +} diff --git a/components/patina_uefi_services/src/service/variable.rs b/components/patina_uefi_services/src/service/variable.rs new file mode 100644 index 000000000..8cca123c3 --- /dev/null +++ b/components/patina_uefi_services/src/service/variable.rs @@ -0,0 +1,144 @@ +//! Variable Services Abstraction +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use alloc::{string::String, vec::Vec}; +use patina::BinaryGuid; +use patina::error::Result; + +#[cfg(any(test, feature = "mockall"))] +use mockall::automock; + +/// An interface for interacting with UEFI Variables. +/// +/// ## Example Usage +/// +/// ```rust +/// use patina::component::{IntoComponent, prelude::Service}; +/// use patina_uefi_services::service::variable::UefiSpecVariableServices; +/// use patina::error::Result; +/// use patina::BinaryGuid; +/// +/// #[derive(IntoComponent)] +/// struct MyComponent; +/// +/// impl MyComponent { +/// fn entry_point( +/// self, +/// variable_services: Service +/// ) -> Result<()> { +/// let guid = BinaryGuid::from_fields(0x8BE4DF61, 0x93CA, 0x11D2, 0xAA, 0x0D, &[0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C]); +/// let (data, attributes) = variable_services.get_variable("BootOrder", &guid)?; +/// Ok(()) +/// } +/// } +/// ``` +#[cfg_attr(any(test, feature = "mockall"), automock)] +pub trait UefiSpecVariableServices { + /// Gets the value of a UEFI variable. + /// + /// # Arguments + /// * `variable_name` - Name of the variable to retrieve + /// * `vendor_guid` - GUID namespace of the variable + /// + /// # Returns + /// * `Result<(Vec, u32)>` - Variable data and attributes on success + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::variable::UefiSpecVariableServices; + /// # use patina::error::Result; + /// # use patina::BinaryGuid; + /// # const GLOBAL_VARIABLE_GUID: BinaryGuid = BinaryGuid::from_fields(0x8BE4DF61, 0x93CA, 0x11D2, 0xAA, 0x0D, &[0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C]); + /// # fn example(variable_services: &dyn UefiSpecVariableServices) -> Result<()> { + /// let (data, attributes) = variable_services.get_variable("BootOrder", &GLOBAL_VARIABLE_GUID)?; + /// # Ok(()) + /// # } + /// ``` + fn get_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid) -> Result<(Vec, u32)>; + + /// Sets the value of a UEFI variable. + /// + /// # Arguments + /// * `variable_name` - Name of the variable to set + /// * `vendor_guid` - GUID namespace of the variable + /// * `attributes` - Variable attributes (runtime, boot service access, etc.) + /// * `data` - Data to store in the variable + /// + /// # Returns + /// * `Result<()>` - Success status + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::variable::UefiSpecVariableServices; + /// # use patina::error::Result; + /// # use patina::BinaryGuid; + /// # const EFI_VARIABLE_BOOTSERVICE_ACCESS: u32 = 0x00000002; + /// # fn example(variable_services: &dyn UefiSpecVariableServices) -> Result<()> { + /// # let my_guid = BinaryGuid::from_fields(0, 0, 0, 0, 0, &[0; 6]); + /// # let data = vec![1, 2, 3, 4]; + /// variable_services.set_variable( + /// "MyVar", + /// &my_guid, + /// EFI_VARIABLE_BOOTSERVICE_ACCESS, + /// &data + /// )?; + /// # Ok(()) + /// # } + /// ``` + fn set_variable(&self, variable_name: &str, vendor_guid: &BinaryGuid, attributes: u32, data: &[u8]) -> Result<()>; + + /// Enumerates the current variable names. + /// + /// This method provides a way to iterate through all variables by repeatedly + /// calling it with the results from the previous call. + /// + /// # Arguments + /// * `variable_name` - On input: previous variable name; on output: next variable name + /// * `vendor_guid` - On input: previous vendor GUID; on output: next vendor GUID + /// + /// # Returns + /// * `Result<()>` - Success status; returns NOT_FOUND when no more variables + /// + /// # Example + /// ```ignore + /// # use patina_uefi_services::service::variable::UefiSpecVariableServices; + /// # use patina::error::Result; + /// # use patina::OwnedGuid; + /// # use alloc::string::String; + /// # fn example(variable_services: &dyn UefiSpecVariableServices) -> Result<()> { + /// let mut name = String::new(); + /// let mut guid = OwnedGuid::from_fields(0, 0, 0, 0, 0, [0; 6]); + /// while variable_services.get_next_variable_name(&mut name, &mut guid).is_ok() { + /// println!("Variable: {} in namespace {:?}", name, guid); + /// } + /// # Ok(()) + /// # } + /// ``` + fn get_next_variable_name(&self, variable_name: &mut String, vendor_guid: &mut BinaryGuid) -> Result<()>; + + /// Returns information about the EFI variables. + /// + /// # Arguments + /// * `attributes` - Variable attributes to query information for + /// + /// # Returns + /// * `Result<(u64, u64, u64)>` - Tuple of (MaximumVariableStorageSize, RemainingVariableStorageSize, MaximumVariableSize) + /// + /// # Example + /// ```no_run + /// # use patina_uefi_services::service::variable::UefiSpecVariableServices; + /// # use patina::error::Result; + /// # const EFI_VARIABLE_BOOTSERVICE_ACCESS: u32 = 0x00000002; + /// # const EFI_VARIABLE_RUNTIME_ACCESS: u32 = 0x00000004; + /// # fn example(variable_services: &dyn UefiSpecVariableServices) -> Result<()> { + /// let (max_storage, remaining, max_size) = variable_services.query_variable_info( + /// EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS + /// )?; + /// # Ok(()) + /// # } + /// ``` + fn query_variable_info(&self, attributes: u32) -> Result<(u64, u64, u64)>; +} diff --git a/components/patina_uefi_services/src/types.rs b/components/patina_uefi_services/src/types.rs new file mode 100644 index 000000000..20584b6e3 --- /dev/null +++ b/components/patina_uefi_services/src/types.rs @@ -0,0 +1,326 @@ +//! Type wrappers for UEFI FFI types +//! +//! This module provides idiomatic Rust wrappers around raw UEFI FFI types from r_efi. +//! +//! These wrappers provide a cleaner interface for Pure Rust code while maintaining +//! zero-cost abstractions over the underlying FFI types. +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 + +use core::fmt; +use r_efi::efi; + +/// A handle to a UEFI device or protocol. +/// +/// This is a type-safe wrapper around the raw `r_efi::efi::Handle` type. +/// Handles are used throughout UEFI to identify devices, images, and protocol instances. +/// +/// # Examples +/// +/// ```rust +/// use patina_uefi_services::types::Handle; +/// +/// let handle = Handle::null(); +/// assert!(handle.is_null()); +/// ``` +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Handle(efi::Handle); + +impl Handle { + /// Creates a new Handle from a raw EFI handle. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Handle; + /// use core::ptr; + /// + /// let raw_handle = ptr::null_mut(); + /// let handle = Handle::new(raw_handle); + /// ``` + #[inline] + pub const fn new(handle: efi::Handle) -> Self { + Self(handle) + } + + /// Creates a null Handle. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Handle; + /// + /// let handle = Handle::null(); + /// assert!(handle.is_null()); + /// ``` + #[inline] + pub const fn null() -> Self { + Self(core::ptr::null_mut()) + } + + /// Returns true if this handle is null. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Handle; + /// + /// let handle = Handle::null(); + /// assert!(handle.is_null()); + /// ``` + #[inline] + pub const fn is_null(self) -> bool { + self.0.is_null() + } + + /// Returns the raw EFI handle. + /// + /// This is useful when you need to pass the handle to FFI functions. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Handle; + /// use core::ptr; + /// + /// let handle = Handle::null(); + /// assert_eq!(handle.as_raw(), ptr::null_mut()); + /// ``` + #[inline] + pub const fn as_raw(self) -> efi::Handle { + self.0 + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_null() { f.write_str("Handle(null)") } else { write!(f, "Handle({:p})", self.0) } + } +} + +impl fmt::Display for Handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_null() { f.write_str("null") } else { write!(f, "{:p}", self.0) } + } +} + +impl From for Handle { + #[inline] + fn from(handle: efi::Handle) -> Self { + Self::new(handle) + } +} + +impl From for efi::Handle { + #[inline] + fn from(handle: Handle) -> Self { + handle.as_raw() + } +} + +impl AsRef for Handle { + #[inline] + fn as_ref(&self) -> &efi::Handle { + &self.0 + } +} + +/// A handle to a UEFI event. +/// +/// This is a type-safe wrapper around the raw `r_efi::efi::Event` type. +/// Events are used for asynchronous notifications and timer operations in UEFI. +/// +/// # Examples +/// +/// ```rust +/// use patina_uefi_services::types::Event; +/// +/// let event = Event::null(); +/// assert!(event.is_null()); +/// ``` +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Event(efi::Event); + +impl Event { + /// Creates a new Event from a raw EFI event. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Event; + /// use core::ptr; + /// + /// let raw_event = ptr::null_mut(); + /// let event = Event::new(raw_event); + /// ``` + #[inline] + pub const fn new(event: efi::Event) -> Self { + Self(event) + } + + /// Creates a null Event. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Event; + /// + /// let event = Event::null(); + /// assert!(event.is_null()); + /// ``` + #[inline] + pub const fn null() -> Self { + Self(core::ptr::null_mut()) + } + + /// Returns true if this event is null. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Event; + /// + /// let event = Event::null(); + /// assert!(event.is_null()); + /// ``` + #[inline] + pub const fn is_null(self) -> bool { + self.0.is_null() + } + + /// Returns the raw EFI event. + /// + /// This is useful when you need to pass the event to FFI functions. + /// + /// # Examples + /// + /// ```rust + /// use patina_uefi_services::types::Event; + /// use core::ptr; + /// + /// let event = Event::null(); + /// assert_eq!(event.as_raw(), ptr::null_mut()); + /// ``` + #[inline] + pub const fn as_raw(self) -> efi::Event { + self.0 + } +} + +impl fmt::Debug for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_null() { f.write_str("Event(null)") } else { write!(f, "Event({:p})", self.0) } + } +} + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_null() { f.write_str("null") } else { write!(f, "{:p}", self.0) } + } +} + +impl From for Event { + #[inline] + fn from(event: efi::Event) -> Self { + Self::new(event) + } +} + +impl From for efi::Event { + #[inline] + fn from(event: Event) -> Self { + event.as_raw() + } +} + +impl AsRef for Event { + #[inline] + fn as_ref(&self) -> &efi::Event { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + use core::ptr; + + #[test] + fn test_handle_null() { + let handle = Handle::null(); + assert!(handle.is_null()); + assert_eq!(handle.as_raw(), ptr::null_mut()); + } + + #[test] + fn test_handle_non_null() { + let raw = 0x1234 as efi::Handle; + let handle = Handle::new(raw); + assert!(!handle.is_null()); + assert_eq!(handle.as_raw(), raw); + } + + #[test] + fn test_handle_conversion() { + let raw = 0x5678 as efi::Handle; + let handle = Handle::from(raw); + let converted_back: efi::Handle = handle.into(); + assert_eq!(raw, converted_back); + } + + #[test] + fn test_handle_clone() { + let handle1 = Handle::new(0x1234 as efi::Handle); + let handle2 = handle1; + assert_eq!(handle1, handle2); + } + + #[test] + fn test_handle_debug() { + let handle = Handle::null(); + let debug_str = format!("{:?}", handle); + assert_eq!(debug_str, "Handle(null)"); + } + + #[test] + fn test_event_null() { + let event = Event::null(); + assert!(event.is_null()); + assert_eq!(event.as_raw(), ptr::null_mut()); + } + + #[test] + fn test_event_non_null() { + let raw = 0xABCD as efi::Event; + let event = Event::new(raw); + assert!(!event.is_null()); + assert_eq!(event.as_raw(), raw); + } + + #[test] + fn test_event_conversion() { + let raw = 0xDEAD as efi::Event; + let event = Event::from(raw); + let converted_back: efi::Event = event.into(); + assert_eq!(raw, converted_back); + } + + #[test] + fn test_event_clone() { + let event1 = Event::new(0xBEEF as efi::Event); + let event2 = event1; + assert_eq!(event1, event2); + } + + #[test] + fn test_event_debug() { + let event = Event::null(); + let debug_str = format!("{:?}", event); + assert_eq!(debug_str, "Event(null)"); + } +} diff --git a/cspell.yml b/cspell.yml index 2a2c3ef31..33570eeb5 100644 --- a/cspell.yml +++ b/cspell.yml @@ -108,6 +108,7 @@ words: - mpidr - msdia - mtrrs + - nanos - nestedfv - nocapture - nologo