Skip to main content

Crate irql

Crate irql 

Source
Expand description

Compile-time IRQL (Interrupt Request Level) safety for Windows kernel drivers.

This crate provides compile-time verification of IRQL constraints using Rust’s type system. IRQL violations are caught by the compiler, preventing a common class of bugs in Windows kernel-mode code.

§Quick Start

use irql::{requires_irql, root_irql, Dispatch, Passive};

// Function requiring Dispatch IRQL or higher
#[requires_irql(Dispatch)]
fn acquire_spinlock() {
    // Spinlock operations require Dispatch level
}

// Entry point at Passive IRQL
#[root_irql(Passive)]
fn driver_init() {
    // Can call higher IRQL functions using call_irql!
    call_irql!(acquire_spinlock());
}

§IRQL Levels

The crate provides types for each Windows IRQL level:

  • Passive - Normal kernel execution (can access paged memory)
  • Apc - Asynchronous Procedure Call level
  • Dispatch - DPC level (most common for drivers)
  • Dirql - Device interrupt level
  • Profile, Clock, Ipi, Power, High - Higher interrupt levels

§The IRQL Rule

IRQL can only stay the same or be raised, never lowered.

This fundamental rule is enforced at compile time through the IrqlCanRaiseTo trait. Attempting to call a lower-IRQL function from a higher-IRQL context results in a compile error with a clear diagnostic message.

§Attributes and Macros

  • requires_irql - Mark functions requiring a minimum IRQL
  • root_irql - Mark entry points with their IRQL level
  • call_irql! - Call IRQL-constrained functions (available inside annotated functions)

§Example: Struct Methods

use irql::{requires_irql, root_irql, Dispatch};

struct Device {
    id: u32,
}

#[requires_irql(Dispatch)]
impl Device {
    fn new(id: u32) -> Self {
        Device { id }
    }
     
    fn process_interrupt(&self) {
        // All methods require Dispatch IRQL
    }
}

#[root_irql(Dispatch)]
fn interrupt_handler() {
    let device = call_irql!(Device::new(1));
    call_irql!(device.process_interrupt());
}

§Compile-Time Error Example

use irql::{requires_irql, Dispatch, Passive};

#[requires_irql(Passive)]
fn low_irql() { }

#[requires_irql(Dispatch)]
fn high_irql() {
    call_irql!(low_irql()); // ERROR: Cannot lower IRQL!
}

The compiler produces:

error: IRQL violation: cannot call function at `Passive` IRQL
       from current IRQL level `Dispatch`

§Safety

This crate provides compile-time guarantees only. You must ensure:

  • Entry points are annotated with their actual runtime IRQL
  • IRQL-raising operations (spinlocks, etc.) are properly tracked
  • Runtime IRQL matches compile-time annotations

For more details, see the repository.

Macros§

call_irql_inner

Structs§

Apc
IRQL level: Apc
Clock
IRQL level: Clock
Dirql
IRQL level: Dirql
Dispatch
IRQL level: Dispatch
High
IRQL level: High
Ipi
IRQL level: Ipi
Passive
IRQL level: Passive
Power
IRQL level: Power
Profile
IRQL level: Profile

Traits§

IrqlCanRaiseTo
Trait indicating that an IRQL level can be raised to a target level.
IrqlLevel
Marker trait for IRQL level types.

Attribute Macros§

requires_irql
Marks a function or impl block as requiring a minimum IRQL level.
root_irql
Marks an entry point function with a specific IRQL context.