Interrupt Handling

Relevant source files

This page covers the interrupt handling capabilities of the ARM PL031 RTC driver, including the interrupt registers, match-based interrupt generation, status checking, and interrupt management functions. The interrupt system allows applications to receive notifications when the RTC reaches a specific timestamp.

For general driver architecture and register operations, see 3.1 and 3.3. For memory safety considerations related to interrupt handling, see 3.5.

Interrupt System Overview

The PL031 RTC interrupt system is based on a timestamp matching mechanism. When the current RTC time matches a pre-configured match value, an interrupt is generated if enabled. The driver provides safe abstractions over the hardware interrupt registers to manage this functionality.

flowchart TD
subgraph subGraph3["Hardware Logic"]
    COMPARE["Time Comparison Logic"]
    INT_GEN["Interrupt Generation"]
end
subgraph subGraph2["Hardware Registers"]
    MR["Match Register (mr)"]
    IMSC["Interrupt Mask (imsc)"]
    RIS["Raw Interrupt Status (ris)"]
    MIS["Masked Interrupt Status (mis)"]
    ICR["Interrupt Clear (icr)"]
    DR["Data Register (dr)"]
end
subgraph subGraph1["Driver API"]
    SET_MATCH["set_match_timestamp()"]
    ENABLE_INT["enable_interrupt()"]
    CHECK_PENDING["interrupt_pending()"]
    CHECK_MATCHED["matched()"]
    CLEAR_INT["clear_interrupt()"]
end
subgraph subGraph0["Application Layer"]
    APP["Application Code"]
    HANDLER["Interrupt Handler"]
end

APP --> ENABLE_INT
APP --> SET_MATCH
CHECK_MATCHED --> RIS
CHECK_PENDING --> MIS
CLEAR_INT --> ICR
COMPARE --> RIS
DR --> COMPARE
ENABLE_INT --> IMSC
HANDLER --> CHECK_PENDING
HANDLER --> CLEAR_INT
IMSC --> INT_GEN
INT_GEN --> MIS
MR --> COMPARE
RIS --> INT_GEN
SET_MATCH --> MR

Sources: src/lib.rs(L17 - L39)  src/lib.rs(L78 - L120) 

Interrupt Registers Layout

The PL031 interrupt system uses five dedicated registers within the Registers structure to manage interrupt functionality:

RegisterOffsetSizePurposeAccess
mr0x0432-bitMatch Register - stores target timestampRead/Write
imsc0x108-bitInterrupt Mask Set/Clear - enables/disables interruptsRead/Write
ris0x148-bitRaw Interrupt Status - shows match status regardless of maskRead
mis0x188-bitMasked Interrupt Status - shows actual interrupt stateRead
icr0x1C8-bitInterrupt Clear Register - clears pending interruptsWrite
flowchart TD
subgraph subGraph4["Driver Methods"]
    SET_MATCH_FUNC["set_match_timestamp()"]
    ENABLE_FUNC["enable_interrupt()"]
    MATCHED_FUNC["matched()"]
    PENDING_FUNC["interrupt_pending()"]
    CLEAR_FUNC["clear_interrupt()"]
end
subgraph subGraph3["Register Operations"]
    subgraph subGraph2["Interrupt Clearing"]
        ICR_OP["icr: write_volatile(0x01)"]
    end
    subgraph subGraph1["Status Reading"]
        RIS_OP["ris: read_volatile() & 0x01"]
        MIS_OP["mis: read_volatile() & 0x01"]
    end
    subgraph Configuration["Configuration"]
        MR_OP["mr: write_volatile(timestamp)"]
        IMSC_OP["imsc: write_volatile(0x01/0x00)"]
    end
end

CLEAR_FUNC --> ICR_OP
ENABLE_FUNC --> IMSC_OP
MATCHED_FUNC --> RIS_OP
PENDING_FUNC --> MIS_OP
SET_MATCH_FUNC --> MR_OP

Sources: src/lib.rs(L17 - L39)  src/lib.rs(L78 - L120) 

Interrupt Lifecycle and State Management

The interrupt system follows a well-defined lifecycle from configuration through interrupt generation to clearing:

stateDiagram-v2
[*] --> Idle : "RTC initialized"
Idle --> Configured : "set_match_timestamp()"
Configured --> Armed : "enable_interrupt(true)"
Armed --> Matched : "RTC time == match time"
Matched --> InterruptPending : "Hardware generates interrupt"
InterruptPending --> Cleared : "clear_interrupt()"
Cleared --> Armed : "Interrupt cleared, still armed"
Armed --> Disabled : "enable_interrupt(false)"
Disabled --> Idle : "Interrupt disabled"
note left of Matched : ['"matched() returns true"']
note left of InterruptPending : ['"interrupt_pending() returns true"']
note left of Cleared : ['"Hardware interrupt cleared"']

Interrupt Configuration

The set_match_timestamp() method configures when an interrupt should be generated:

#![allow(unused)]
fn main() {
pub fn set_match_timestamp(&mut self, match_timestamp: u32)
}

This function writes the target timestamp to the match register (mr). When the RTC's data register (dr) equals this value, the hardware sets the raw interrupt status bit.

Interrupt Enabling and Masking

The enable_interrupt() method controls whether interrupts are actually generated:

#![allow(unused)]
fn main() {
pub fn enable_interrupt(&mut self, mask: bool)
}

When mask is true, the function writes 0x01 to the interrupt mask register (imsc), enabling interrupts. When false, it writes 0x00, disabling them.

Sources: src/lib.rs(L78 - L82)  src/lib.rs(L108 - L113) 

Status Checking and Interrupt Detection

The driver provides two distinct methods for checking interrupt status, each serving different purposes:

Raw Match Status (matched())

The matched() method checks the raw interrupt status register (ris) to determine if the match condition has occurred, regardless of whether interrupts are enabled:

#![allow(unused)]
fn main() {
pub fn matched(&self) -> bool
}

This method reads the ris register and returns true if bit 0 is set, indicating that the RTC time matches the match register value.

Masked Interrupt Status (interrupt_pending())

The interrupt_pending() method checks the masked interrupt status register (mis) to determine if there is an actual pending interrupt:

#![allow(unused)]
fn main() {
pub fn interrupt_pending(&self) -> bool
}

This method returns true only when both the match condition is met AND interrupts are enabled. This is the method typically used in interrupt handlers.

flowchart TD
subgraph subGraph0["Status Check Flow"]
    TIME_CHECK["RTC Time == Match Time?"]
    RIS_SET["Set ris bit 0"]
    MASK_CHECK["Interrupt enabled (imsc)?"]
    MIS_SET["Set mis bit 0"]
    MATCHED_CALL["matched() called"]
    READ_RIS["Read ris register"]
    MATCHED_RESULT["Return (ris & 0x01) != 0"]
    PENDING_CALL["interrupt_pending() called"]
    READ_MIS["Read mis register"]
    PENDING_RESULT["Return (mis & 0x01) != 0"]
end

MASK_CHECK --> MIS_SET
MATCHED_CALL --> READ_RIS
PENDING_CALL --> READ_MIS
READ_MIS --> PENDING_RESULT
READ_RIS --> MATCHED_RESULT
RIS_SET --> MASK_CHECK
TIME_CHECK --> RIS_SET

Sources: src/lib.rs(L86 - L91)  src/lib.rs(L97 - L102) 

Interrupt Clearing

The clear_interrupt() method resets the interrupt state by writing to the interrupt clear register (icr):

#![allow(unused)]
fn main() {
pub fn clear_interrupt(&mut self)
}

This function writes 0x01 to the icr register, which clears both the raw interrupt status (ris) and masked interrupt status (mis) bits. After clearing, the interrupt system can generate new interrupts when the next match condition occurs.

Usage Patterns and Best Practices

Basic Interrupt Setup

// Configure interrupt for specific timestamp
rtc.set_match_timestamp(target_time);
rtc.enable_interrupt(true);

Interrupt Handler Pattern

// In interrupt handler
if rtc.interrupt_pending() {
    // Handle the interrupt
    handle_rtc_interrupt();
    
    // Clear the interrupt
    rtc.clear_interrupt();
    
    // Optionally set new match time
    rtc.set_match_timestamp(next_target_time);
}

Polling Pattern (Alternative to Interrupts)

// Poll for match without using interrupts
rtc.enable_interrupt(false);
loop {
    if rtc.matched() {
        handle_rtc_event();
        // Set new match time or break
        rtc.set_match_timestamp(next_target_time);
    }
    // Other work...
}

Sources: src/lib.rs(L78 - L120) 

Thread Safety and Concurrency

The interrupt-related methods maintain the same thread safety guarantees as the rest of the driver. Reading methods like matched() and interrupt_pending() are safe to call concurrently, while writing methods like set_match_timestamp(), enable_interrupt(), and clear_interrupt() require mutable access to ensure atomicity.

The driver implements Send and Sync traits, allowing the RTC instance to be shared across threads with appropriate synchronization mechanisms.

Sources: src/lib.rs(L123 - L128)