#Rust

Finding the Time Part 2 - Rust Async and the Arm Generic Timer

Tech Essays Reporter
3 min read

A deep dive into implementing async timer functionality for ARM architectures, focusing on the Arm Generic Timer and its integration with Rust's Embassy framework.

In the evolving landscape of embedded systems, the confluence of Rust's async programming model and ARM's architecture presents both challenges and opportunities. This article explores the implementation of async timer functionality specifically for ARMv8-R architecture, examining how the Arm Generic Timer can serve as the foundation for sophisticated async runtimes like Embassy.

The journey begins with an understanding of timer peripherals across ARM architectures. The ubiquitous Arm SYSTICK, found in all Cortex-M processors, reveals its limitations through its 24-bit counter design. While sufficient for basic RTOS scheduling ticks, its maximum interval of approximately 35 milliseconds at 480 MHz proves inadequate for longer-duration events or power-sensitive applications. This constraint forces developers into a suboptimal pattern of frequent reprogramming, draining battery life and introducing unnecessary complexity.

A step up, the Arm CMSDK APB Timer offers a 32-bit counter, extending the maximum interval to nearly nine seconds. While more versatile for setting arbitrary future alarms, this peripheral still requires careful clock configuration to achieve practical ranges for many embedded applications.

The true breakthrough comes with the Arm Generic Timer, a 64-bit peripheral standard in Armv8-A and Armv8-R systems. With a maximum period exceeding 292 years even at a generous 2 GHz clock speed, this timer eliminates wrap-around concerns and enables true long-duration event scheduling. Its implementation as system registers rather than memory-mapped I/O provides more efficient access, a critical factor in real-time systems.

The article then delves into the practical implementation of an embassy-time driver for the Arm Generic Timer. The implementation reveals elegant solutions to several challenges:

  1. Timer Queue Management: A sorted queue of future events allows efficient scheduling and ensures the next alarm is always at the head of the queue.

  2. Atomic Operations: The driver leverages Rust's atomic operations to manage shared state safely across async contexts.

  3. Interrupt Handling: A carefully designed interrupt handler bridges the hardware timer with the embassy runtime, ensuring timely wakeups of pending tasks.

The code examples demonstrate a sophisticated approach to timer management, with a critical-section protected mutex guarding the timer queue state. The implementation showcases how to safely interact with the timer registers while maintaining the invariants required by the async runtime.

The practical example provided demonstrates a working Embassy runtime on ARMv8-R, capable of running multiple async tasks with precise timing. This implementation not only validates the approach but also provides a foundation for more complex embedded applications that require concurrent processing with precise timing guarantees.

Interestingly, the article highlights a distinction between Embassy and RTIC frameworks. While Embassy has been successfully adapted for ARMv8-R through the embassy-executor crate's platform-cortex-ar feature, RTIC traditionally limited itself to Microcontroller profile architectures. The author hints at ongoing work to extend RTIC support to more powerful ARM architectures, suggesting this implementation could serve as a model for that effort.

The technical details reveal important considerations for ARM-based async implementations:

  • The use of WFE (Wait For Event) and SEV (Set Event) instructions provides a more efficient alternative to traditional WFI (Wait For Interrupt) patterns, avoiding race conditions between interrupt flags and sleep states.
  • The distinction between Physical and Virtual timers becomes relevant in hypervisor environments, with the Virtual Timer accounting for time only when the guest OS is running.
  • The implementation must carefully manage the timer's interrupt mask to ensure power efficiency when no events are scheduled.

This work represents a significant step forward in enabling sophisticated async programming models on more powerful ARM processors beyond traditional microcontrollers. By leveraging the capabilities of the Arm Generic Timer, developers can build complex concurrent systems with precise timing guarantees, opening new possibilities for embedded applications in fields from automotive to industrial automation.

The practical implementation provided serves as both a working solution and an educational resource for developers seeking to understand the intersection of ARM architecture, Rust's async model, and embedded systems programming. As the embedded Rust ecosystem continues to mature, such foundational work will prove increasingly valuable in bridging the gap between high-level programming models and low-level hardware constraints.

Comments

Loading comments...