diff --git a/src/asm/asm.s b/src/asm/asm.s index 4014ab5b1..39d577976 100644 --- a/src/asm/asm.s +++ b/src/asm/asm.s @@ -13,6 +13,13 @@ _x86_64_asm_interrupt_disable: cli retq +.global _x86_64_asm_interrupt_enable_and_hlt +.p2align 4 +_x86_64_asm_interrupt_enable_and_hlt: + sti + hlt + retq + .global _x86_64_asm_int3 .p2align 4 _x86_64_asm_int3: diff --git a/src/asm/mod.rs b/src/asm/mod.rs index b5604ec1d..72140dc26 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -12,6 +12,12 @@ extern "C" { )] pub(crate) fn x86_64_asm_interrupt_disable(); + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_interrupt_enable_and_hlt" + )] + pub(crate) fn x86_64_asm_interrupt_enable_and_hlt(); + #[cfg_attr( any(target_env = "gnu", target_env = "musl"), link_name = "_x86_64_asm_int3" diff --git a/src/instructions/interrupts.rs b/src/instructions/interrupts.rs index d1f643888..3658dcb64 100644 --- a/src/instructions/interrupts.rs +++ b/src/instructions/interrupts.rs @@ -84,6 +84,61 @@ where ret } +/// Atomically enable interrupts and put the CPU to sleep +/// +/// Executes the `sti; hlt` instruction sequence. Since the `sti` instruction +/// keeps interrupts disabled until after the immediately following +/// instruction (called "interrupt shadow"), no interrupt can occur between the +/// two instructions. (One exception to this are non-maskable interrupts; this +/// is explained below.) +/// +/// This function is useful to put the CPU to sleep without missing interrupts +/// that occur immediately before the `hlt` instruction: +/// +/// ```ignore +/// // there is a race between the check and the `hlt` instruction here: +/// +/// if nothing_to_do() { +/// // <- race when the interrupt occurs here +/// x86_64::instructions::hlt(); // wait for the next interrupt +/// } +/// +/// // avoid this race by using `enable_interrupts_and_hlt`: +/// +/// x86_64::instructions::interrupts::disable(); +/// if nothing_to_do() { +/// // <- no interrupts can occur here (interrupts are disabled) +/// x86_64::instructions::interrupts::enable_interrupts_and_hlt(); +/// } +/// +/// ``` +/// +/// ## Non-maskable Interrupts +/// +/// On some processors, the interrupt shadow of `sti` does not apply to +/// non-maskable interrupts (NMIs). This means that an NMI can occur between +/// the `sti` and `hlt` instruction, with the result that the CPU is put to +/// sleep even though a new interrupt occured. +/// +/// To work around this, it is recommended to check in the NMI handler if +/// the interrupt occured between `sti` and `hlt` instructions. If this is the +/// case, the handler should increase the instruction pointer stored in the +/// interrupt stack frame so that the `hlt` instruction is skipped. +/// +/// See for more +/// information. +#[inline] +pub fn enable_interrupts_and_hlt() { + #[cfg(feature = "inline_asm")] + unsafe { + asm!("sti; hlt" :::: "volatile"); + } + #[cfg(not(feature = "inline_asm"))] + unsafe { + crate::asm::x86_64_asm_interrupt_enable_and_hlt(); + } +} + /// Cause a breakpoint exception by invoking the `int3` instruction. #[inline] pub fn int3() {