blob: f2321889ad6b7da3ba40d9ab22579a04f94723fb [file] [log] [blame]
//! Provides support for the UEFI debugging protocol.
//!
//! This protocol is designed to allow debuggers to query the state of the firmware,
//! as well as set up callbacks for various events.
//!
//! It also defines a Debugport protocol for debugging over serial devices.
//!
//! An example UEFI debugger is Intel's [UDK Debugger Tool][udk].
//!
//! [udk]: https://firmware.intel.com/develop/intel-uefi-tools-and-utilities/intel-uefi-development-kit-debugger-tool
use core::ffi::c_void;
use crate::proto::unsafe_protocol;
use crate::{Result, Status, StatusExt};
// re-export for ease of use
pub use context::SystemContext;
pub use exception::ExceptionType;
mod context;
mod exception;
/// The debugging support protocol allows debuggers to connect to a UEFI machine.
/// It is expected that there will typically be two instances of the EFI Debug Support protocol in the system.
/// One associated with the native processor instruction set (IA-32, x64, ARM, RISC-V, or Itanium processor
/// family), and one for the EFI virtual machine that implements EFI byte code (EBC).
/// While multiple instances of the EFI Debug Support protocol are expected, there must never be more than
/// one for any given instruction set.
///
/// NOTE: OVMF only implements this protocol interface for the virtual EBC processor
#[derive(Debug)]
#[repr(C)]
#[unsafe_protocol("2755590c-6f3c-42fa-9ea4-a3ba543cda25")]
pub struct DebugSupport {
isa: ProcessorArch,
get_maximum_processor_index:
extern "efiapi" fn(this: &mut DebugSupport, max_processor_index: &mut usize) -> Status,
register_periodic_callback: unsafe extern "efiapi" fn(
this: &mut DebugSupport,
processor_index: usize,
periodic_callback: Option<unsafe extern "efiapi" fn(SystemContext)>,
) -> Status,
register_exception_callback: unsafe extern "efiapi" fn(
this: &mut DebugSupport,
processor_index: usize,
exception_callback: Option<unsafe extern "efiapi" fn(ExceptionType, SystemContext)>,
exception_type: ExceptionType,
) -> Status,
invalidate_instruction_cache: unsafe extern "efiapi" fn(
this: &mut DebugSupport,
processor_index: usize,
start: *mut c_void,
length: u64,
) -> Status,
}
impl DebugSupport {
/// Returns the processor architecture of the running CPU.
#[must_use]
pub const fn arch(&self) -> ProcessorArch {
self.isa
}
/// Returns the maximum value that may be used for the processor_index parameter in
/// `register_periodic_callback()` and `register_exception_callback()`.
///
/// Note: Applications built with EDK2 (such as OVMF) always return `0` as of 2021-09-15
pub fn get_maximum_processor_index(&mut self) -> usize {
// initially set to a canary value for testing purposes
let mut max_processor_index: usize = usize::MAX;
// per the UEFI spec, this call should only return EFI_SUCCESS
let _ = (self.get_maximum_processor_index)(self, &mut max_processor_index);
max_processor_index
}
/// Registers a function to be called back periodically in interrupt context.
/// Pass `None` for `callback` to deregister the currently registered function for
/// a specified `processor_index`. Will return `Status::INVALID_PARAMETER` if
/// `processor_index` exceeds the current maximum from `Self::get_maximum_processor_index`.
///
/// Note: Applications built with EDK2 (such as OVMF) ignore the `processor_index` parameter
///
/// # Safety
/// No portion of the debug agent that runs in interrupt context may make any
/// calls to EFI services or other protocol interfaces.
pub unsafe fn register_periodic_callback(
&mut self,
processor_index: usize,
callback: Option<unsafe extern "efiapi" fn(SystemContext)>,
) -> Result {
if processor_index > self.get_maximum_processor_index() {
return Err(Status::INVALID_PARAMETER.into());
}
// Safety: As we've validated the `processor_index`, this should always be safe
(self.register_periodic_callback)(self, processor_index, callback).to_result()
}
/// Registers a function to be called when a given processor exception occurs.
/// Pass `None` for `callback` to deregister the currently registered function for a
/// given `exception_type` and `processor_index`. Will return `Status::INVALID_PARAMETER`
/// if `processor_index` exceeds the current maximum from `Self::get_maximum_processor_index`.
///
/// Note: Applications built with EDK2 (such as OVMF) ignore the `processor_index` parameter
///
/// # Safety
/// No portion of the debug agent that runs in interrupt context may make any
/// calls to EFI services or other protocol interfaces.
pub unsafe fn register_exception_callback(
&mut self,
processor_index: usize,
callback: Option<unsafe extern "efiapi" fn(ExceptionType, SystemContext)>,
exception_type: ExceptionType,
) -> Result {
if processor_index > self.get_maximum_processor_index() {
return Err(Status::INVALID_PARAMETER.into());
}
// Safety: As we've validated the `processor_index`, this should always be safe
(self.register_exception_callback)(self, processor_index, callback, exception_type)
.to_result()
}
/// Invalidates processor instruction cache for a memory range for a given `processor_index`.
///
/// Note: Applications built with EDK2 (such as OVMF) ignore the `processor_index` parameter
///
/// # Safety
/// `start` must be a c_void ptr to a valid memory address
pub unsafe fn invalidate_instruction_cache(
&mut self,
processor_index: usize,
start: *mut c_void,
length: u64,
) -> Result {
if processor_index > self.get_maximum_processor_index() {
return Err(Status::INVALID_PARAMETER.into());
}
// per the UEFI spec, this call should only return EFI_SUCCESS
// Safety: As we've validated the `processor_index`, this should always be safe
(self.invalidate_instruction_cache)(self, processor_index, start, length).to_result()
}
}
newtype_enum! {
/// The instruction set architecture of the running processor.
///
/// UEFI can be and has been ported to new CPU architectures in the past,
/// therefore modeling this C enum as a Rust enum (where the compiler must know
/// about every variant in existence) would _not_ be safe.
pub enum ProcessorArch: u32 => {
/// 32-bit x86 PC
X86_32 = 0x014C,
/// 64-bit x86 PC
X86_64 = 0x8664,
/// Intel Itanium
ITANIUM = 0x200,
/// UEFI Interpreter bytecode
EBC = 0x0EBC,
/// ARM Thumb / Mixed
ARM = 0x01C2,
/// ARM 64-bit
AARCH_64 = 0xAA64,
/// RISC-V 32-bit
RISCV_32 = 0x5032,
/// RISC-V 64-bit
RISCV_64 = 0x5064,
/// RISC-V 128-bit
RISCV_128 = 0x5128,
}}
/// The debug port protocol abstracts the underlying debug port
/// hardware, whether it is a regular Serial port or something else.
#[derive(Debug)]
#[repr(C)]
#[unsafe_protocol("eba4e8d2-3858-41ec-a281-2647ba9660d0")]
pub struct DebugPort {
reset: extern "efiapi" fn(this: &DebugPort) -> Status,
write: extern "efiapi" fn(
this: &DebugPort,
timeout: u32,
buffer_size: &mut usize,
buffer: *const c_void,
) -> Status,
read: extern "efiapi" fn(
this: &DebugPort,
timeout: u32,
buffer_size: &mut usize,
buffer: *mut c_void,
) -> Status,
poll: extern "efiapi" fn(this: &DebugPort) -> Status,
}
impl DebugPort {
/// Resets the debugport device.
pub fn reset(&self) -> Result {
(self.reset)(self).to_result()
}
/// Write data to the debugport device.
///
/// Note: `timeout` is given in microseconds
pub fn write(&self, timeout: u32, data: &[u8]) -> Result<(), usize> {
let mut buffer_size = data.len();
(self.write)(
self,
timeout,
&mut buffer_size,
data.as_ptr().cast::<c_void>(),
)
.to_result_with(
|| debug_assert_eq!(buffer_size, data.len()),
|_| buffer_size,
)
}
/// Read data from the debugport device.
///
/// Note: `timeout` is given in microseconds
pub fn read(&self, timeout: u32, data: &mut [u8]) -> Result<(), usize> {
let mut buffer_size = data.len();
(self.read)(
self,
timeout,
&mut buffer_size,
data.as_mut_ptr().cast::<c_void>(),
)
.to_result_with(
|| debug_assert_eq!(buffer_size, data.len()),
|_| buffer_size,
)
}
/// Check to see if any data is available to be read from the debugport device.
pub fn poll(&self) -> Result {
(self.poll)(self).to_result()
}
}