Overview

Relevant source files

Purpose and Scope

This document provides an introduction to the cap_access library, a capability-based access control system designed for ArceOS and embedded environments. This overview covers the library's fundamental purpose, core components, and architectural design. For detailed implementation specifics of the capability system, see Capability System. For practical usage examples and integration patterns, see Usage Guide. For information about ArceOS-specific integration details, see ArceOS Integration.

What is cap_access

The cap_access library implements a capability-based access control mechanism that provides unforgeable access tokens for protected objects. It serves as a foundational security primitive within the ArceOS operating system ecosystem, enabling fine-grained permission management in resource-constrained environments.

The library centers around two primary components defined in src/lib.rs(L4 - L15) :

  • Cap bitflags representing access permissions (READ, WRITE, EXECUTE)
  • WithCap<T> wrapper struct that associates objects with their required capabilities

Unlike traditional access control lists or ownership models, capability-based security provides direct, unforgeable references to objects along with the permissions needed to access them. This approach eliminates ambient authority and reduces the attack surface in system security.

Sources: Cargo.toml(L1 - L16)  src/lib.rs(L1 - L21)  README.md(L7 - L12) 

Core Components Overview

The cap_access library consists of three fundamental layers that work together to provide secure object access:

ComponentLocationPurpose
Capbitflagssrc/lib.rs4-15Define access permissions using efficient bit operations
WithCapwrappersrc/lib.rs17-21Associate any object type with capability requirements
Access control methodssrc/lib.rs46-99Provide safe and unsafe access patterns with capability validation

The system leverages the bitflags crate v2.6 for efficient permission operations, enabling combinations like Cap::READ | Cap::WRITE while maintaining no_std compatibility for embedded environments.

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L17 - L21)  Cargo.toml(L14 - L15) 

System Architecture

flowchart TD
subgraph ExternalDeps["External Dependencies"]
    BitflagsLib["bitflags crate v2.6Efficient flag operations"]
end
subgraph CreationAPI["Object Creation"]
    NewMethod["WithCap::new(inner: T, cap: Cap)→ WithCap<T>"]
    CapGetter["cap() → Cap"]
end
subgraph AccessMethods["Access Control Methods"]
    CanAccess["can_access(cap: Cap)→ bool"]
    Access["access(cap: Cap)→ Option<&T>"]
    AccessOrErr["access_or_err(cap: Cap, err: E)→ Result<&T, E>"]
    AccessUnchecked["access_unchecked()→ &T (unsafe)"]
end
subgraph CoreTypes["Core Type System"]
    CapBitflags["Cap bitflagsREAD | WRITE | EXECUTE"]
    WithCapStruct["WithCap<T>inner: T, cap: Cap"]
end

CanAccess --> Access
CanAccess --> AccessOrErr
CapBitflags --> BitflagsLib
CapBitflags --> WithCapStruct
CapGetter --> CapBitflags
NewMethod --> WithCapStruct
WithCapStruct --> CanAccess

This architecture diagram shows the relationship between the core types and the methods that operate on them. The Cap bitflags provide the foundation for permission representation, while WithCap<T> wraps arbitrary objects with capability requirements. Access control methods then enforce these requirements through various safe and unsafe interfaces.

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L17 - L27)  src/lib.rs(L46 - L99)  Cargo.toml(L14 - L15) 

Access Control Flow


This sequence diagram illustrates the different access patterns available in the cap_access system. The can_access() method serves as the core validation function used by all safe access methods, while access_unchecked() bypasses capability validation entirely for performance-critical scenarios.

Sources: src/lib.rs(L46 - L48)  src/lib.rs(L72 - L78)  src/lib.rs(L93 - L99)  src/lib.rs(L55 - L57) 

Target Environments and Use Cases

The cap_access library is specifically designed for system-level programming environments where security and resource constraints are primary concerns:

no_std Compatibility

The library operates in no_std environments as specified in src/lib.rs(L1)  making it suitable for:

  • Embedded systems with limited memory
  • Kernel modules without standard library access
  • Bare-metal applications on microcontrollers

ArceOS Integration

As indicated by the package metadata in Cargo.toml(L8 - L11)  cap_access serves as a foundational component within the ArceOS operating system, providing:

  • Memory region access control
  • File system permission enforcement
  • Device driver capability management
  • Process isolation mechanisms

Multi-Architecture Support

The library supports multiple target architectures through its build system, including x86_64, RISC-V, and ARM64 platforms in both hosted and bare-metal configurations.

Sources: src/lib.rs(L1)  Cargo.toml(L8 - L12)  README.md(L7 - L8) 

Key Design Principles

The cap_access library embodies several important design principles that distinguish it from traditional access control mechanisms:

  1. Unforgeable Capabilities: The Cap bitflags cannot be arbitrarily created or modified, ensuring that only authorized code can grant permissions
  2. Zero-Cost Abstractions: Capability checks compile to efficient bitwise operations with minimal runtime overhead
  3. Type Safety: The WithCap<T> wrapper preserves the original type while adding capability enforcement
  4. Flexible Access Patterns: Multiple access methods (access(), access_or_err(), access_unchecked()) accommodate different error handling strategies and performance requirements

This capability-based approach provides stronger security guarantees than discretionary access control while maintaining the performance characteristics required for systems programming.

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L46 - L99)  README.md(L9 - L12) 

Core Architecture

Relevant source files

Purpose and Scope

This document provides a comprehensive overview of the fundamental architecture underlying the cap_access capability-based access control system. It covers the core components that enable secure object protection through unforgeable capability tokens and controlled access patterns.

For specific implementation details of the capability flags system, see Capability System. For in-depth coverage of the object wrapper mechanics, see Object Protection with WithCap. For detailed access method implementations, see Access Control Methods.

System Overview

The cap_access library implements a capability-based access control model built around two primary components: capability tokens (Cap) and protected object wrappers (WithCap<T>). The architecture enforces access control at compile time and runtime through a combination of type safety and capability checking.

Core Architecture Diagram

flowchart TD
subgraph subGraph3["Access Methods"]
    CanAccess["can_access()"]
    Access["access()"]
    AccessOrErr["access_or_err()"]
    AccessUnchecked["access_unchecked()"]
end
subgraph subGraph2["WithCap Fields"]
    Inner["inner: T"]
    CapField["cap: Cap"]
end
subgraph subGraph1["Cap Implementation Details"]
    READ["Cap::READ (1 << 0)"]
    WRITE["Cap::WRITE (1 << 1)"]
    EXECUTE["Cap::EXECUTE (1 << 2)"]
    Contains["Cap::contains()"]
end
subgraph subGraph0["cap_access Core Components"]
    Cap["Cap bitflags struct"]
    WithCap["WithCap<T> wrapper struct"]
    Methods["Access control methods"]
end

CanAccess --> Contains
Cap --> Contains
Cap --> EXECUTE
Cap --> READ
Cap --> WRITE
CapField --> Contains
Methods --> Access
Methods --> AccessOrErr
Methods --> AccessUnchecked
Methods --> CanAccess
WithCap --> CapField
WithCap --> Inner
WithCap --> Methods

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L17 - L21)  src/lib.rs(L23 - L100) 

Core Components

Capability Token System

The Cap struct serves as the foundational security primitive, implemented using the bitflags crate to provide efficient bitwise operations on capability flags. The three basic capabilities can be combined using bitwise OR operations to create compound permissions.

CapabilityValuePurpose
Cap::READ1 << 0Allows reading access to protected data
Cap::WRITE1 << 1Enables modification of protected data
Cap::EXECUTE1 << 2Permits execution of protected code

Protected Object Wrapper

The WithCap<T> generic struct encapsulates any type T along with its associated capabilities. This wrapper ensures that access to the inner object is mediated through capability checks, preventing unauthorized operations.

flowchart TD
subgraph subGraph2["Capability Query"]
    GetCap["cap() -> Cap"]
    CanAccess["can_access(cap: Cap) -> bool"]
end
subgraph Constructor["Constructor"]
    New["WithCap::new(inner: T, cap: Cap)"]
end
subgraph subGraph0["WithCap<T> Structure"]
    InnerField["inner: T"]
    CapacityField["cap: Cap"]
end

CapacityField --> CanAccess
CapacityField --> GetCap
New --> CapacityField
New --> InnerField

Sources: src/lib.rs(L17 - L21)  src/lib.rs(L24 - L27)  src/lib.rs(L29 - L32)  src/lib.rs(L34 - L48) 

Access Control Implementation

Capability Checking Logic

The core access control mechanism centers on the can_access method, which uses the Cap::contains operation to verify that the wrapper's capability set includes all bits required by the requested capability.

flowchart TD
Request["Access Request with Cap"]
Check["can_access(requested_cap)"]
Contains["self.cap.contains(requested_cap)"]
Allow["Grant Access"]
Deny["Deny Access"]

Check --> Contains
Contains --> Allow
Contains --> Deny
Request --> Check

Sources: src/lib.rs(L46 - L48) 

Access Pattern Variants

The architecture provides three distinct access patterns, each with different safety and error handling characteristics:

MethodReturn TypeSafetyUse Case
access(cap)Option<&T>SafeOptional access with None on failure
access_or_err(cap, err)Result<&T, E>SafeError propagation with custom error types
access_unchecked()&TUnsafePerformance-critical code with guaranteed valid capabilities

Access Method Flow

flowchart TD
AccessCall["Access Method Called"]
SafeCheck["Safe or Unsafe?"]
CapCheck["Capability Check"]
OptionReturn["Return Option<&T>"]
ResultReturn["Return Result<&T, E>"]
UnsafeReturn["Return &T directly"]

AccessCall --> SafeCheck
CapCheck --> OptionReturn
CapCheck --> ResultReturn
SafeCheck --> CapCheck
SafeCheck --> UnsafeReturn

Sources: src/lib.rs(L50 - L57)  src/lib.rs(L59 - L78)  src/lib.rs(L80 - L99) 

Type Safety and Memory Safety

The architecture leverages Rust's type system to enforce capability-based access control at compile time while providing runtime capability validation. The WithCap<T> wrapper prevents direct access to the inner object, forcing all access through the controlled interface methods.

The unsafe access_unchecked method provides an escape hatch for performance-critical scenarios where the caller can guarantee capability validity, while maintaining clear documentation of the safety requirements.

Sources: src/lib.rs(L1 - L101) 

Capability System

Relevant source files

Purpose and Scope

The Capability System forms the foundation of access control in the cap_access library. This document covers the Cap bitflags structure, the three fundamental capability types (READ, WRITE, EXECUTE), and the mechanisms for combining and checking capabilities. For information about how capabilities are used with protected objects, see Object Protection with WithCap. For details on access control methods that utilize these capabilities, see Access Control Methods.

Cap Bitflags Structure

The capability system is implemented using the bitflags crate to provide efficient bit-level operations for permission management. The Cap structure represents unforgeable access tokens that can be combined using bitwise operations.

flowchart TD
subgraph subGraph2["Bitwise Operations"]
    UNION["Union (|)"]
    INTERSECTION["Intersection (&)"]
    CONTAINS["contains()"]
end
subgraph subGraph1["Bitflags Traits"]
    DEFAULT["Default"]
    DEBUG["Debug"]
    CLONE["Clone"]
    COPY["Copy"]
end
subgraph subGraph0["Cap Structure [lines 4-15]"]
    CAP["Cap: u32"]
    READ["READ = 1 << 0"]
    WRITE["WRITE = 1 << 1"]
    EXECUTE["EXECUTE = 1 << 2"]
end

CAP --> CLONE
CAP --> COPY
CAP --> DEBUG
CAP --> DEFAULT
CAP --> EXECUTE
CAP --> READ
CAP --> WRITE
EXECUTE --> UNION
INTERSECTION --> CONTAINS
READ --> UNION
UNION --> CONTAINS
WRITE --> UNION

Cap Bitflags Architecture

The Cap struct is defined as a bitflags structure that wraps a u32 value, providing type-safe capability manipulation with automatic trait derivations for common operations.

Sources: src/lib.rs(L4 - L15) 

Basic Capability Types

The capability system defines three fundamental access rights that correspond to common operating system permissions:

CapabilityBit PositionValuePurpose
READ01 << 0(1)Grants readable access to protected data
WRITE11 << 1(2)Grants writable access to protected data
EXECUTE21 << 2(4)Grants executable access to protected data
flowchart TD
subgraph subGraph1["Capability Constants"]
    READ_CONST["Cap::READ = 0x01"]
    WRITE_CONST["Cap::WRITE = 0x02"]
    EXECUTE_CONST["Cap::EXECUTE = 0x04"]
end
subgraph subGraph0["Bit Layout [u32]"]
    BIT2["Bit 2: EXECUTE"]
    BIT1["Bit 1: WRITE"]
    BIT0["Bit 0: READ"]
    UNUSED["Bits 3-31: Reserved"]
end

BIT0 --> READ_CONST
BIT1 --> WRITE_CONST
BIT2 --> EXECUTE_CONST

Capability Bit Layout

Each capability type occupies a specific bit position, allowing for efficient combination and checking operations using bitwise arithmetic.

Sources: src/lib.rs(L8 - L14) 

Capability Combinations

Capabilities can be combined using bitwise OR operations to create composite permissions. The bitflags implementation provides natural syntax for capability composition:

flowchart TD
subgraph subGraph2["Use Cases"]
    READONLY["Read-only files"]
    READWRITE["Mutable data"]
    EXECUTABLE["Program files"]
    FULLACCESS["Administrative access"]
end
subgraph subGraph1["Common Combinations"]
    RW["Cap::READ | Cap::WRITE"]
    RX["Cap::READ | Cap::EXECUTE"]
    WX["Cap::WRITE | Cap::EXECUTE"]
    RWX["Cap::READ | Cap::WRITE | Cap::EXECUTE"]
end
subgraph subGraph0["Single Capabilities"]
    R["Cap::READ"]
    W["Cap::WRITE"]
    X["Cap::EXECUTE"]
end

R --> READONLY
R --> RW
R --> RWX
R --> RX
RW --> READWRITE
RWX --> FULLACCESS
RX --> EXECUTABLE
W --> RW
W --> RWX
W --> WX
X --> RWX
X --> RX
X --> WX

Capability Combination Patterns

The bitflags design enables natural combination of capabilities to express complex permission requirements.

Sources: src/lib.rs(L4 - L15) 

Capability Checking Logic

The capability system provides the contains method for checking whether a set of capabilities includes required permissions. This forms the foundation for all access control decisions in the system.

flowchart TD
subgraph subGraph0["contains() Logic"]
    BITWISE["(self.bits & other.bits) == other.bits"]
    SUBSET["Check if 'other' is subset of 'self'"]
end
subgraph subGraph1["Example Scenarios"]
    SCENARIO1["Object has READ|WRITERequest: READ → true"]
    SCENARIO2["Object has READRequest: WRITE → false"]
    SCENARIO3["Object has READ|WRITERequest: READ|WRITE → true"]
end
START["can_access(cap: Cap)"]
CHECK["self.cap.contains(cap)"]
TRUE["Return true"]
FALSE["Return false"]

BITWISE --> SUBSET
CHECK --> BITWISE
START --> CHECK
SUBSET --> FALSE
SUBSET --> TRUE

Capability Checking Flow

The can_access method uses bitwise operations to determine if the requested capabilities are a subset of the available capabilities.

Sources: src/lib.rs(L46 - L48) 

Implementation Details

The Cap structure leverages several key design patterns for efficient and safe capability management:

Bitflags Integration

The implementation uses the bitflags! macro to generate a complete capability management API, including:

  • Automatic implementation of bitwise operations (|, &, ^, !)
  • Type-safe flag manipulation methods
  • Built-in contains() method for subset checking
  • Standard trait implementations (Debug, Clone, Copy, Default)

Memory Efficiency

The Cap structure occupies only 4 bytes (u32) regardless of capability combinations, making it suitable for embedded environments where memory usage is critical.

Constant Evaluation

The can_access method is marked as const fn, enabling compile-time capability checking when capability values are known at compile time.

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L46 - L48) 

Object Protection with WithCap

Relevant source files

Purpose and Scope

This document covers the WithCap<T> wrapper struct, which serves as the core object protection mechanism in the cap_access library. The WithCap<T> struct associates any object of type T with a specific capability (Cap), providing controlled access to the wrapped object through capability checking.

For details about the underlying capability system and Cap bitflags, see Capability System. For comprehensive coverage of the access control methods provided by WithCap<T>, see Access Control Methods.

WithCap Structure Overview

The WithCap<T> struct provides a simple but powerful abstraction for capability-based object protection. It wraps any object with an associated capability, ensuring that access to the object is mediated through capability checks.

Core Architecture

flowchart TD
subgraph AccessMethods["Access Methods"]
    new["new(inner, cap)"]
    can_access["can_access(cap)"]
    access["access(cap)"]
    access_or_err["access_or_err(cap, err)"]
    access_unchecked["access_unchecked()"]
    cap_getter["cap()"]
end
subgraph CapSystem["Capability System"]
    READ["Cap::READ"]
    WRITE["Cap::WRITE"]
    EXECUTE["Cap::EXECUTE"]
    COMBINED["Combined Capabilities"]
end
subgraph WithCap["WithCap<T> Structure"]
    inner["inner: T"]
    cap["cap: Cap"]
end

COMBINED --> cap
EXECUTE --> cap
READ --> cap
WRITE --> cap

Sources: src/lib.rs(L18 - L21)  src/lib.rs(L23 - L100) 

Struct Definition

The WithCap<T> struct contains exactly two fields that work together to provide controlled access:

FieldTypePurpose
innerTThe protected object of arbitrary type
capCapThe capability bitflags defining allowed access

The struct is generic over type T, allowing any object to be protected with capabilities.

Sources: src/lib.rs(L18 - L21) 

Object Wrapping Process

Creating Protected Objects

The WithCap::new() constructor associates an object with specific capabilities, creating an immutable binding between the object and its access permissions.

flowchart TD
subgraph Examples["Example Objects"]
    FileData["File Data"]
    MemoryRegion["Memory Region"]
    DeviceHandle["Device Handle"]
end
subgraph Creation["Object Protection Process"]
    UnprotectedObj["Unprotected Object"]
    Capability["Cap Permissions"]
    Constructor["WithCap::new()"]
    ProtectedObj["WithCap<T>"]
end

Capability --> Constructor
Constructor --> ProtectedObj
DeviceHandle --> UnprotectedObj
FileData --> UnprotectedObj
MemoryRegion --> UnprotectedObj
UnprotectedObj --> Constructor

Sources: src/lib.rs(L24 - L27) 

Capability Association

The new() method permanently associates the provided capability with the object. Once created, the capability cannot be modified, ensuring the integrity of the access control policy.

The constructor signature demonstrates this immutable binding:

  • pub fn new(inner: T, cap: Cap) -> Self

Sources: src/lib.rs(L24 - L27) 

Access Control Implementation

Capability Checking Logic

The core access control mechanism relies on the can_access() method, which performs bitwise capability checking using the underlying Cap::contains() operation.

flowchart TD
subgraph AccessMethods["Access Method Types"]
    SafeAccess["access() → Option<&T>"]
    ErrorAccess["access_or_err() → Result<&T, E>"]
    UnsafeAccess["access_unchecked() → &T"]
end
subgraph AccessFlow["Access Control Flow"]
    Request["Access Request with Cap"]
    Check["can_access(cap)"]
    Contains["cap.contains(requested_cap)"]
    Allow["Grant Access"]
    Deny["Deny Access"]
end

Allow --> ErrorAccess
Allow --> SafeAccess
Check --> Contains
Contains --> Allow
Contains --> Deny
Deny --> ErrorAccess
Deny --> SafeAccess
Request --> Check
UnsafeAccess --> Check

Sources: src/lib.rs(L46 - L48)  src/lib.rs(L72 - L78)  src/lib.rs(L93 - L99)  src/lib.rs(L55 - L57) 

Method Categories

The WithCap<T> struct provides three categories of access methods:

Method CategorySafetyReturn TypeUse Case
Checked AccessSafeOption<&T>General-purpose access
Error-based AccessSafeResult<&T, E>When custom error handling needed
Unchecked AccessUnsafe&TPerformance-critical code paths

Sources: src/lib.rs(L72 - L78)  src/lib.rs(L93 - L99)  src/lib.rs(L55 - L57) 

Internal Architecture Details

Capability Storage

The WithCap<T> struct stores capabilities as Cap bitflags, enabling efficient bitwise operations for permission checking. The cap() getter method provides read-only access to the stored capability.

Memory Layout Considerations

The struct maintains a simple memory layout with the protected object and capability stored adjacently. This design minimizes overhead while maintaining clear separation between the object and its access permissions.

flowchart TD
subgraph CapabilityBits["Cap Bitfield Structure"]
    Bit0["Bit 0: READ"]
    Bit1["Bit 1: WRITE"]
    Bit2["Bit 2: EXECUTE"]
    Reserved["Bits 3-31: Reserved"]
end
subgraph MemoryLayout["WithCap<T> Memory Layout"]
    InnerData["inner: T(Protected Object)"]
    CapData["cap: Cap(u32 bitflags)"]
end
WithCap["WithCap"]

CapData --> Bit0
CapData --> Bit1
CapData --> Bit2
CapData --> Reserved
CapData --> WithCap
InnerData --> WithCap

Sources: src/lib.rs(L18 - L21)  src/lib.rs(L29 - L32) 

Const Methods

Several methods are declared as const fn, enabling compile-time evaluation when possible:

  • cap() - capability getter
  • can_access() - capability checking
  • access() - checked access

This design supports zero-cost abstractions in performance-critical embedded environments.

Sources: src/lib.rs(L30)  src/lib.rs(L46)  src/lib.rs(L72) 

Access Control Methods

Relevant source files

Purpose and Scope

This document covers the access control methods provided by the WithCap<T> wrapper for accessing protected objects. It explains the different access patterns available, their safety guarantees, and appropriate usage scenarios. For information about the capability system and permission flags, see Capability System. For details about the WithCap<T> wrapper structure itself, see Object Protection with WithCap.

The access control methods implement a capability-based security model where objects can only be accessed when the caller presents the required capabilities. The system provides multiple access patterns to accommodate different error handling strategies and performance requirements.

Access Control Flow

The access control system follows a consistent pattern where capability validation determines whether access is granted to the protected object.

flowchart TD
CALLER["Caller Code"]
METHOD["Access Method Call"]
CHECK["can_access(cap)"]
VALIDATE["CapabilityValidation"]
GRANT["Access Granted"]
DENY["Access Denied"]
RETURN_REF["Return &T"]
ERROR_HANDLING["Error Handling"]
OPTION["Option::None"]
RESULT["Result::Err(E)"]
UNSAFE["Undefined Behavior"]
SAFE_ACCESS["access(cap)"]
RESULT_ACCESS["access_or_err(cap, err)"]
UNSAFE_ACCESS["access_unchecked()"]

CALLER --> METHOD
CHECK --> VALIDATE
DENY --> ERROR_HANDLING
ERROR_HANDLING --> OPTION
ERROR_HANDLING --> RESULT
ERROR_HANDLING --> UNSAFE
GRANT --> RETURN_REF
METHOD --> CHECK
METHOD --> RESULT_ACCESS
METHOD --> SAFE_ACCESS
METHOD --> UNSAFE_ACCESS
RESULT_ACCESS --> CHECK
SAFE_ACCESS --> CHECK
UNSAFE_ACCESS --> RETURN_REF
VALIDATE --> DENY
VALIDATE --> GRANT

Sources: src/lib.rs(L46 - L99) 

Capability Validation

All safe access methods rely on the can_access method for capability validation. This method performs a bitwise containment check to determine if the required capabilities are present.

can_accessMethod

The can_access method provides the fundamental capability checking logic used by all safe access patterns.

Method SignatureReturn TypePurpose
can_access(&self, cap: Cap)boolCheck if capabilities allow access

Implementation Details:

  • Uses self.cap.contains(cap) for bitwise capability checking
  • Declared as const fn for compile-time evaluation
  • Returns true if all requested capability bits are present
  • Returns false if any requested capability bits are missing

Sources: src/lib.rs(L46 - L48) 

Safe Access Methods

The library provides two safe access methods that perform capability validation before granting access to the protected object.

Option-Based Access

The access method provides safe access with Option<&T> return semantics for cases where capability failure should be handled gracefully.

flowchart TD
INPUT["access(cap: Cap)"]
CHECK["can_access(cap)"]
SOME["Some(&self.inner)"]
NONE["None"]
CLIENT_OK["Client handles success"]
CLIENT_ERR["Client handles None"]

CHECK --> NONE
CHECK --> SOME
INPUT --> CHECK
NONE --> CLIENT_ERR
SOME --> CLIENT_OK

Method Characteristics:

  • Signature: access(&self, cap: Cap) -> Option<&T>
  • Safety: Memory safe with compile-time guarantees
  • Performance: Minimal overhead with const fn optimization
  • Error Handling: Returns None on capability mismatch

Usage Pattern:

if let Some(data) = protected_obj.access(Cap::READ) {
    // Use data safely
} else {
    // Handle access denial
}

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

Result-Based Access

The access_or_err method provides safe access with Result<&T, E> return semantics, allowing custom error types and error propagation patterns.

flowchart TD
INPUT["access_or_err(cap: Cap, err: E)"]
CHECK["can_access(cap)"]
OK["Ok(&self.inner)"]
ERR["Err(err)"]
CLIENT_OK["Client handles success"]
CLIENT_ERR["Client handles custom error"]
PROPAGATE["Error propagation with ?"]

CHECK --> ERR
CHECK --> OK
ERR --> CLIENT_ERR
ERR --> PROPAGATE
INPUT --> CHECK
OK --> CLIENT_OK

Method Characteristics:

  • Signature: access_or_err<E>(&self, cap: Cap, err: E) -> Result<&T, E>
  • Safety: Memory safe with compile-time guarantees
  • Error Handling: Returns custom error type on capability mismatch
  • Integration: Compatible with ? operator for error propagation

Usage Pattern:

let data = protected_obj.access_or_err(Cap::WRITE, "Write access denied")?;
// Use data safely, or propagate error

Sources: src/lib.rs(L93 - L99) 

Unsafe Access Methods

The library provides an unsafe access method for performance-critical scenarios where capability checking overhead must be eliminated.

Unchecked Access

The access_unchecked method bypasses all capability validation and directly returns a reference to the protected object.

flowchart TD
INPUT["access_unchecked()"]
DIRECT["&self.inner"]
WARNING["⚠️ UNSAFE:No capability validation"]
CALLER["Caller Responsibility"]
BYPASS["Bypasses can_access()"]
PERFORMANCE["Zero-overhead access"]

BYPASS --> PERFORMANCE
DIRECT --> WARNING
INPUT --> BYPASS
INPUT --> DIRECT
WARNING --> CALLER

Method Characteristics:

  • Signature: unsafe fn access_unchecked(&self) -> &T
  • Safety: UNSAFE - No capability validation performed
  • Performance: Zero overhead - direct memory access
  • Responsibility: Caller must ensure capability compliance

Safety Requirements: The caller must guarantee that they have the appropriate capabilities for the intended use of the object. Violating this contract may lead to:

  • Security vulnerabilities
  • Privilege escalation
  • Undefined behavior in security-critical contexts

Sources: src/lib.rs(L55 - L57) 

Access Method Comparison

MethodSafetyReturn TypeOverheadUse Case
accessSafeOption<&T>MinimalGeneral purpose with option handling
access_or_errSafeResult<&T, E>MinimalError propagation and custom errors
access_uncheckedUnsafe&TZeroPerformance-critical trusted code

Implementation Architecture

The access control methods are implemented as part of the WithCap<T> struct and follow a layered architecture:

flowchart TD
subgraph subGraph2["Memory Access"]
    INNER_REF["&self.inner"]
end
subgraph subGraph1["Core Validation"]
    CONTAINS["self.cap.contains(cap)"]
    BITFLAGS["bitflags operations"]
end
subgraph subGraph0["WithCap Access Methods"]
    CAN_ACCESS["can_access(cap: Cap)→ bool"]
    ACCESS["access(cap: Cap)→ Option<&T>"]
    ACCESS_ERR["access_or_err(cap: Cap, err: E)→ Result<&T, E>"]
    ACCESS_UNSAFE["access_unchecked()→ &T"]
end

ACCESS --> CAN_ACCESS
ACCESS --> INNER_REF
ACCESS_ERR --> CAN_ACCESS
ACCESS_ERR --> INNER_REF
ACCESS_UNSAFE --> INNER_REF
CAN_ACCESS --> CONTAINS
CONTAINS --> BITFLAGS

Architecture Characteristics:

  • Layered Design: Safe methods build upon capability validation
  • Shared Validation: All safe methods use can_access for consistency
  • Zero-Cost Abstractions: const fn declarations enable compile-time optimization
  • Type Safety: Generic design works with any wrapped type T

Sources: src/lib.rs(L23 - L100) 

Usage Guide

Relevant source files

This guide provides practical examples and patterns for using the cap_access library in real applications. It covers the essential usage patterns, access methods, and best practices for implementing capability-based access control in your code.

For architectural details about the underlying capability system, see Core Architecture. For information about integrating with ArceOS, see ArceOS Integration.

Basic Usage Patterns

The fundamental workflow with cap_access involves creating protected objects with WithCap::new() and accessing them through capability-checked methods.

Creating Protected Objects

flowchart TD
subgraph subGraph0["Cap Constants"]
    READ["Cap::READ"]
    WRITE["Cap::WRITE"]
    EXECUTE["Cap::EXECUTE"]
end
Object["Raw Object(T)"]
WithCap_new["WithCap::new()"]
Capability["Cap Permission(READ | WRITE | EXECUTE)"]
Protected["WithCap<T>Protected Object"]

Capability --> WithCap_new
EXECUTE --> Capability
Object --> WithCap_new
READ --> Capability
WRITE --> Capability
WithCap_new --> Protected

The most common pattern is to wrap objects at creation time with appropriate capabilities:

Object TypeTypical CapabilitiesUse Case
Configuration DataCap::READRead-only system settings
User FilesCap::READ | Cap::WRITEEditable user content
Executable CodeCap::READ | Cap::EXECUTEProgram binaries
System ResourcesCap::READ | Cap::WRITE | Cap::EXECUTEFull access resources

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L23 - L27) 

Capability Composition

Capabilities can be combined using bitwise operations to create complex permission sets:

flowchart TD
subgraph subGraph1["Combined Capabilities"]
    RW["Cap::READ | Cap::WRITERead-Write Access"]
    RX["Cap::READ | Cap::EXECUTERead-Execute Access"]
    RWX["Cap::READ | Cap::WRITE | Cap::EXECUTEFull Access"]
end
subgraph subGraph0["Individual Capabilities"]
    READ_CAP["Cap::READ(1 << 0)"]
    WRITE_CAP["Cap::WRITE(1 << 1)"]
    EXEC_CAP["Cap::EXECUTE(1 << 2)"]
end

EXEC_CAP --> RWX
EXEC_CAP --> RX
READ_CAP --> RW
READ_CAP --> RWX
READ_CAP --> RX
WRITE_CAP --> RW
WRITE_CAP --> RWX

Sources: src/lib.rs(L4 - L15)  README.md(L16 - L29) 

Access Method Comparison

The WithCap<T> struct provides three distinct access patterns, each with different safety and error handling characteristics:

flowchart TD
subgraph subGraph1["Return Types"]
    Bool["Boolean CheckValidation only"]
    Option["Option PatternNone on failure"]
    Result["Result PatternCustom error on failure"]
    Direct["Direct ReferenceNo safety checks"]
end
subgraph subGraph0["Access Methods"]
    can_access["can_access(cap)→ bool"]
    access["access(cap)→ Option<&T>"]
    access_or_err["access_or_err(cap, err)→ Result<&T, E>"]
    access_unchecked["access_unchecked()→ &T (UNSAFE)"]
end
WithCap["WithCap<T>Protected Object"]

WithCap --> access
WithCap --> access_or_err
WithCap --> access_unchecked
WithCap --> can_access
access --> Option
access_or_err --> Result
access_unchecked --> Direct
can_access --> Bool

Method Selection Guidelines

MethodWhen to UseSafety LevelPerformance
can_access()Pre-flight validation checksSafeFastest
access()Optional access patternsSafeFast
access_or_err()Error handling with contextSafeFast
access_unchecked()Performance-critical pathsUnsafeFastest

Sources: src/lib.rs(L34 - L48)  src/lib.rs(L59 - L78)  src/lib.rs(L80 - L99)  src/lib.rs(L50 - L57) 

Practical Access Patterns

Safe Access with Option Handling

The access() method returns Option<&T> and is ideal for scenarios where access failure is expected and should be handled gracefully:

sequenceDiagram
    participant Client as Client
    participant WithCapaccess as "WithCap::access()"
    participant WithCapcan_access as "WithCap::can_access()"
    participant innerT as "inner: T"

    Client ->> WithCapaccess: access(requested_cap)
    WithCapaccess ->> WithCapcan_access: self.cap.contains(requested_cap)
    alt Capability Match
        WithCapcan_access -->> WithCapaccess: true
        WithCapaccess ->> innerT: &self.inner
        innerT -->> WithCapaccess: &T
        WithCapaccess -->> Client: Some(&T)
    else Capability Mismatch
        WithCapcan_access -->> WithCapaccess: false
        WithCapaccess -->> Client: None
    end

This pattern is demonstrated in the README examples where checking for Cap::EXECUTE on a read-write object returns None.

Sources: src/lib.rs(L72 - L78)  README.md(L22 - L28) 

Error-Based Access with Context

The access_or_err() method provides custom error messages for better debugging and user feedback:

flowchart TD
subgraph Output["Output"]
    Result_Ok["Result::Ok(&T)"]
    Result_Err["Result::Err(E)"]
end
subgraph subGraph1["access_or_err Process"]
    Check["can_access(cap)"]
    Success["Ok(&inner)"]
    Failure["Err(custom_error)"]
end
subgraph Input["Input"]
    Cap_Request["Requested Capability"]
    Error_Value["Custom Error Value"]
end

Cap_Request --> Check
Check --> Failure
Check --> Success
Error_Value --> Check
Failure --> Result_Err
Success --> Result_Ok

This method is particularly useful in system programming contexts where specific error codes or messages are required for debugging.

Sources: src/lib.rs(L93 - L99) 

Unsafe High-Performance Access

The access_unchecked() method bypasses capability validation for performance-critical code paths:

Safety Requirements:

  • Caller must manually verify capability compliance
  • Should only be used in trusted, performance-critical contexts
  • Requires unsafe block, making the safety contract explicit

Sources: src/lib.rs(L55 - L57) 

Best Practices

Capability Design Patterns

  1. Principle of Least Privilege: Grant minimal necessary capabilities
  2. Capability Composition: Use bitwise OR to combine permissions
  3. Validation at Boundaries: Check capabilities at system boundaries
  4. Consistent Error Handling: Choose one access method per subsystem

Integration Patterns

flowchart TD
subgraph subGraph2["Protected Resources"]
    FileSystem["File System Objects"]
    Memory["Memory Regions"]
    DeviceDrivers["Device Drivers"]
end
subgraph subGraph1["cap_access Integration"]
    WithCap_Factory["WithCap CreationWithCap::new(obj, cap)"]
    Access_Layer["Access Controlaccess() / access_or_err()"]
    Validation["Capability Validationcan_access()"]
end
subgraph subGraph0["Application Layer"]
    UserCode["User Application Code"]
    ServiceLayer["Service Layer"]
end

Access_Layer --> Validation
ServiceLayer --> Access_Layer
ServiceLayer --> WithCap_Factory
UserCode --> Access_Layer
UserCode --> WithCap_Factory
WithCap_Factory --> DeviceDrivers
WithCap_Factory --> FileSystem
WithCap_Factory --> Memory

Common Usage Scenarios

ScenarioRecommended PatternExample
File Access ControlWithCap::new(file, Cap::READ | Cap::WRITE)User file permissions
Memory ProtectionWithCap::new(region, Cap::READ | Cap::EXECUTE)Code segment protection
Device Driver AccessWithCap::new(device, Cap::WRITE)Hardware write-only access
Configuration DataWithCap::new(config, Cap::READ)Read-only system settings

Sources: src/lib.rs(L23 - L27)  README.md(L19 - L24) 

Advanced Usage Patterns

Dynamic Capability Checking

The can_access() method enables sophisticated access control logic:


This pattern is essential for security-sensitive applications that need comprehensive access logging and audit trails.

Sources: src/lib.rs(L46 - L48) 

Multi-Level Access Control

Complex systems often require nested capability checks across multiple abstraction layers:

flowchart TD
subgraph subGraph1["WithCap Instances"]
    AppData["WithCap<UserData>"]
    KernelData["WithCap<SystemCall>"]
    HWData["WithCap<Device>"]
end
subgraph subGraph0["System Architecture"]
    App["Application LayerCap::READ | Cap::WRITE"]
    Kernel["Kernel LayerCap::EXECUTE"]
    Hardware["Hardware LayerCap::READ | Cap::WRITE | Cap::EXECUTE"]
end
UserAccess["User Read Access"]
SyscallAccess["System Call Execution"]
DirectHW["Direct Hardware Access"]

App --> AppData
AppData --> UserAccess
HWData --> DirectHW
Hardware --> HWData
Kernel --> KernelData
KernelData --> SyscallAccess

This layered approach ensures that capability validation occurs at appropriate system boundaries while maintaining performance where needed.

Sources: src/lib.rs(L1 - L101) 

ArceOS Integration

Relevant source files

Purpose and Scope

This document explains how the cap_access library integrates into the ArceOS operating system ecosystem, serving as a foundational security primitive for capability-based access control. It covers the library's role in kernel security, no_std compatibility requirements, and integration patterns across ArceOS components.

For detailed information about the core capability system and access control methods, see Core Architecture. For practical usage examples, see Usage Guide.

ArceOS Ecosystem Integration

The cap_access library serves as a core security infrastructure component within the ArceOS operating system, providing capability-based access control primitives that are used throughout the kernel and user-space applications.

Integration Architecture

flowchart TD
subgraph subGraph3["User Applications"]
    SystemServices["System Services"]
    UserApps["User Applications"]
    RuntimeLibs["Runtime Libraries"]
end
subgraph subGraph2["Protected Resources"]
    MemoryRegions["Memory Regions"]
    FileObjects["File Objects"]
    DeviceResources["Device Resources"]
    ProcessData["Process Data"]
end
subgraph subGraph1["cap_access Security Layer"]
    CapStruct["Cap bitflags"]
    WithCapStruct["WithCap"]
    AccessMethods["Access Methods"]
end
subgraph subGraph0["ArceOS Kernel Layer"]
    KernelCore["Kernel Core"]
    MemoryMgr["Memory Manager"]
    FileSystem["File System"]
    DeviceDrivers["Device Drivers"]
end

AccessMethods --> CapStruct
AccessMethods --> WithCapStruct
DeviceDrivers --> WithCapStruct
FileSystem --> WithCapStruct
KernelCore --> CapStruct
MemoryMgr --> WithCapStruct
RuntimeLibs --> AccessMethods
SystemServices --> AccessMethods
UserApps --> AccessMethods
WithCapStruct --> DeviceResources
WithCapStruct --> FileObjects
WithCapStruct --> MemoryRegions
WithCapStruct --> ProcessData

Sources: Cargo.toml(L8 - L12)  src/lib.rs(L17 - L21) 

Package Configuration for ArceOS

The library is specifically configured for integration with ArceOS through its package metadata:

ConfigurationValuePurpose
Homepagehttps://github.com/arceos-org/arceosLinks to ArceOS project
Keywords["arceos", "capabilities", "permission", "access-control"]ArceOS-specific tagging
Categories["os", "no-std"]Operating system and embedded focus

Sources: Cargo.toml(L8 - L12) 

Kernel Security Layer

The cap_access library provides unforgeable security tokens through its capability-based access control system, enabling fine-grained permission management within ArceOS kernel components.

Security Primitive Integration

flowchart TD
subgraph subGraph3["Kernel Usage"]
    KernelCheck["Kernel Permission Check"]
    ResourceAccess["Resource Access"]
    ErrorHandling["Error Handling"]
end
subgraph subGraph2["Access Control"]
    CanAccess["can_access()"]
    Access["access()"]
    AccessOrErr["access_or_err()"]
    AccessUnchecked["access_unchecked()"]
end
subgraph subGraph1["Object Protection"]
    WithCapNew["WithCap::new()"]
    WithCapStruct["WithCap"]
    InnerData["inner: T"]
    CapField["cap: Cap"]
end
subgraph subGraph0["Capability Definition"]
    CapRead["Cap::READ"]
    CapWrite["Cap::WRITE"]
    CapExecute["Cap::EXECUTE"]
end

Access --> CanAccess
Access --> KernelCheck
AccessOrErr --> CanAccess
AccessOrErr --> ErrorHandling
AccessUnchecked --> ResourceAccess
CanAccess --> CapField
CapExecute --> WithCapNew
CapRead --> WithCapNew
CapWrite --> WithCapNew
KernelCheck --> ResourceAccess
WithCapNew --> WithCapStruct
WithCapStruct --> CapField
WithCapStruct --> InnerData

Sources: src/lib.rs(L4 - L15)  src/lib.rs(L46 - L48)  src/lib.rs(L72 - L78) 

Security Guarantees

The capability system provides several security guarantees essential for kernel operation:

  • Unforgeable Tokens: Cap bitflags cannot be arbitrarily created, only through controlled initialization
  • Compile-time Safety: The type system prevents capability escalation through WithCap<T> wrapper
  • Runtime Validation: Methods like can_access() and access() provide runtime permission checking
  • Controlled Unsafe Access: access_unchecked() provides escape hatch for performance-critical kernel code

Sources: src/lib.rs(L46 - L48)  src/lib.rs(L72 - L78)  src/lib.rs(L55 - L57) 

no_std Compatibility

The cap_access library is designed for no_std environments, making it suitable for embedded systems and kernel contexts where the standard library is not available.

no_std Configuration

The library uses conditional compilation to support both std and no_std environments:

#![cfg_attr(not(test), no_std)]

This configuration enables:

  • Kernel Integration: Direct usage within ArceOS kernel without standard library dependencies
  • Embedded Support: Deployment on resource-constrained embedded systems
  • Bare Metal: Operation on bare metal platforms without operating system support

Sources: src/lib.rs(L1) 

Dependencies

The library maintains minimal dependencies to support no_std compatibility:

DependencyVersionPurposeno_std Compatible
bitflags2.6Efficient bitflag operations forCapYes

Sources: Cargo.toml(L14 - L15) 

Multi-Platform Support

ArceOS targets multiple hardware architectures, and cap_access provides consistent capability-based access control across all supported platforms.

Supported Architectures

Based on the CI configuration and ArceOS requirements, cap_access supports:

  • x86_64: Standard desktop and server platforms (x86_64-unknown-linux-gnu, x86_64-unknown-none)
  • RISC-V: Embedded and specialized systems (riscv64gc-unknown-none-elf)
  • ARM64: Mobile and embedded platforms (aarch64-unknown-none-softfloat)

Platform-Agnostic Design

The capability system is designed to be platform-agnostic through:

  • Pure Rust Implementation: No platform-specific assembly or system calls
  • Bitflag Operations: Efficient bitwise operations supported on all target architectures
  • Const Functions: Compile-time evaluation where possible for performance

Sources: src/lib.rs(L30 - L32)  src/lib.rs(L46 - L48)  src/lib.rs(L72 - L78) 

Integration Patterns

Typical ArceOS Usage Patterns

Within ArceOS, cap_access follows several common integration patterns:

  1. Kernel Resource Protection: Wrapping kernel data structures with WithCap<T> to control access
  2. System Call Interface: Using capability checking in system call implementations
  3. Driver Security: Protecting device driver resources with capability-based access
  4. Memory Management: Controlling access to memory regions through capability tokens

Example Integration Flow

sequenceDiagram
    participant Application as "Application"
    participant ArceOSKernel as "ArceOS Kernel"
    participant cap_access as "cap_access"
    participant ProtectedResource as "Protected Resource"

    Application ->> ArceOSKernel: "System call with capability"
    ArceOSKernel ->> cap_access: "WithCap::new(resource, cap)"
    cap_access ->> ProtectedResource: "Wrap with capability"
    ProtectedResource -->> cap_access: "WithCap<Resource>"
    cap_access -->> ArceOSKernel: "Protected object"
    Application ->> ArceOSKernel: "Access request"
    ArceOSKernel ->> cap_access: "access(required_cap)"
    cap_access ->> cap_access: "can_access() check"
    alt "Capability sufficient"
        cap_access -->> ArceOSKernel: "Some(&resource)"
        ArceOSKernel -->> Application: "Access granted"
    else "Capability insufficient"
        cap_access -->> ArceOSKernel: "None"
        ArceOSKernel -->> Application: "Access denied"
    end

Sources: src/lib.rs(L24 - L27)  src/lib.rs(L72 - L78)  src/lib.rs(L46 - L48) 

This integration pattern ensures that ArceOS maintains strong security guarantees while providing the performance characteristics required for operating system operations across diverse hardware platforms.

Development Guide

Relevant source files

This document provides guidance for developers contributing to the cap_access library. It covers the project structure, development environment setup, testing procedures, and contribution workflow. For detailed information about the build system and CI pipeline, see Build System and CI. For platform-specific development considerations, see Multi-Platform Support.

Project Structure

The cap_access project follows a standard Rust library structure with emphasis on embedded and no_std compatibility. The codebase is designed as a foundational security primitive for the ArceOS operating system ecosystem.

Repository Organization

flowchart TD
subgraph subGraph2["Generated Artifacts"]
    TARGET["target/Build outputs"]
    DOCS["target/doc/Generated documentation"]
    LOCK["Cargo.lockDependency lock file"]
end
subgraph subGraph1["GitHub Workflows"]
    CI[".github/workflows/ci.ymlContinuous integration"]
end
subgraph subGraph0["Repository Root"]
    CARGO["Cargo.tomlPackage metadata"]
    GITIGNORE[".gitignoreVCS exclusions"]
    SRC["src/Source code"]
end

CARGO --> TARGET
CI --> DOCS
CI --> TARGET
GITIGNORE --> LOCK
GITIGNORE --> TARGET
SRC --> TARGET

Sources: Cargo.toml(L1 - L16)  .gitignore(L1 - L5)  .github/workflows/ci.yml(L1 - L56) 

Package Configuration

The project is configured as a multi-licensed Rust library with specific focus on operating systems and embedded development:

PropertyValue
Namecap_access
Version0.1.0
Edition2021
LicenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
Categoriesos,no-std
Keywordsarceos,capabilities,permission,access-control

Sources: Cargo.toml(L1 - L12) 

Development Environment

Required Tools

The development environment requires the Rust nightly toolchain with specific components for cross-compilation and code quality:

flowchart TD
subgraph subGraph1["Target Platforms"]
    LINUX["x86_64-unknown-linux-gnuStandard Linux"]
    BAREMETAL["x86_64-unknown-noneBare metal x86_64"]
    RISCV["riscv64gc-unknown-none-elfRISC-V embedded"]
    ARM["aarch64-unknown-none-softfloatARM64 embedded"]
end
subgraph subGraph0["Rust Toolchain"]
    NIGHTLY["rustc nightlyCompiler toolchain"]
    RUSTFMT["rustfmtCode formatter"]
    CLIPPY["clippyLinter"]
    RUSTSRC["rust-srcSource code component"]
end

CLIPPY --> NIGHTLY
NIGHTLY --> ARM
NIGHTLY --> BAREMETAL
NIGHTLY --> LINUX
NIGHTLY --> RISCV
RUSTFMT --> NIGHTLY
RUSTSRC --> NIGHTLY

Sources: .github/workflows/ci.yml(L15 - L19)  .github/workflows/ci.yml(L11 - L12) 

Dependencies

The project maintains minimal dependencies to ensure compatibility with embedded environments:

  • bitflags 2.6: Provides efficient bit flag operations for the Cap permission system

Sources: Cargo.toml(L14 - L15) 

Quality Assurance Pipeline

Automated Checks

The CI pipeline enforces code quality through multiple automated checks:

flowchart TD
subgraph subGraph1["Target Matrix"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Code Quality Gates"]
    FORMAT["cargo fmt --all --checkCode formatting validation"]
    LINT["cargo clippy --target TARGET --all-featuresLinting analysis"]
    BUILD["cargo build --target TARGET --all-featuresCompilation verification"]
    TEST["cargo test --target x86_64-unknown-linux-gnuUnit test execution"]
end

BUILD --> T1
BUILD --> T2
BUILD --> T3
BUILD --> T4
BUILD --> TEST
FORMAT --> LINT
LINT --> BUILD
LINT --> T1
LINT --> T2
LINT --> T3
LINT --> T4
TEST --> T1

Sources: .github/workflows/ci.yml(L22 - L30) 

Clippy Configuration

The linting process uses Clippy with specific allowances for the project's design patterns:

  • Allows clippy::new_without_default due to the WithCap::new design requiring explicit capability parameters

Sources: .github/workflows/ci.yml(L25) 

Documentation System

Documentation Generation

The project uses a dual documentation system for both development and public consumption:

flowchart TD
subgraph subGraph2["Quality Controls"]
    BROKEN["-D rustdoc::broken_intra_doc_linksLink validation"]
    MISSING["-D missing-docsCoverage enforcement"]
end
subgraph subGraph1["Documentation Outputs"]
    LOCALDIR["target/doc/Local documentation"]
    GHPAGES["gh-pages branchPublished documentation"]
    DOCSRS["docs.rsPublic registry docs"]
end
subgraph subGraph0["Documentation Pipeline"]
    RUSTDOC["cargo doc --no-deps --all-featuresAPI documentation generation"]
    INDEX["printf redirect to index.htmlEntry point creation"]
    DEPLOY["JamesIves/github-pages-deploy-actionGitHub Pages deployment"]
end

BROKEN --> RUSTDOC
DEPLOY --> GHPAGES
GHPAGES --> DOCSRS
INDEX --> LOCALDIR
LOCALDIR --> DEPLOY
MISSING --> RUSTDOC
RUSTDOC --> LOCALDIR

Sources: .github/workflows/ci.yml(L32 - L55)  .github/workflows/ci.yml(L40) 

Documentation Standards

The documentation system enforces strict quality standards:

  • Broken Link Detection: All intra-documentation links must be valid
  • Coverage Requirement: All public APIs must be documented
  • Automatic Deployment: Documentation updates are automatically published on the default branch

Sources: .github/workflows/ci.yml(L40) 

Contributing Workflow

Pre-Commit Requirements

Before submitting changes, ensure all quality gates pass:

  1. Format Check: cargo fmt --all -- --check
  2. Lint Check: cargo clippy --all-features
  3. Build Verification: Test on all supported targets
  4. Unit Tests: cargo test on x86_64-unknown-linux-gnu

Supported Platforms

Development must consider compatibility across multiple target architectures:

TargetEnvironmentPurpose
x86_64-unknown-linux-gnuStandard LinuxDevelopment and testing
x86_64-unknown-noneBare metal x86_64Embedded systems
riscv64gc-unknown-none-elfRISC-V embeddedIoT and embedded
aarch64-unknown-none-softfloatARM64 embeddedMobile and embedded

Sources: .github/workflows/ci.yml(L12) 

The development process is designed to maintain the library's core principles of security, performance, and broad platform compatibility while ensuring code quality through automated verification.

Build System and CI

Relevant source files

This document covers the build system configuration, continuous integration pipeline, and multi-target support for the cap_access library. It explains how the project maintains code quality across multiple embedded and systems programming targets, automates testing and documentation generation, and ensures compatibility with no_std environments.

For information about the specific target architectures and their embedded considerations, see Multi-Platform Support.

Package Configuration

The cap_access library is configured as a Rust crate with specific metadata optimized for embedded and systems programming environments. The package configuration emphasizes no_std compatibility and integration with the ArceOS ecosystem.

Core Package Metadata

The project is defined in Cargo.toml(L1 - L12)  with the following key characteristics:

PropertyValuePurpose
namecap_accessPrimary crate identifier
version0.1.0Initial release version
edition2021Rust 2021 edition features
categories["os", "no-std"]Operating systems and no_std environments
keywords["arceos", "capabilities", "permission", "access-control"]Discovery and classification

The triple licensing scheme specified in Cargo.toml(L7)  (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) provides maximum compatibility with different project requirements, particularly important for kernel and embedded development where licensing constraints vary.

Dependencies

The crate maintains minimal dependencies to support no_std environments:

[dependencies]
bitflags = "2.6"

The single dependency on bitflags version 2.6 Cargo.toml(L14 - L15)  provides efficient flag operations for the capability system while maintaining no_std compatibility.

Sources: Cargo.toml(L1 - L16) 

CI Pipeline Architecture

The continuous integration system implements a comprehensive multi-target testing strategy designed for embedded and systems programming environments. The pipeline validates code quality, builds across multiple architectures, and automates documentation deployment.

Pipeline Structure

flowchart TD
subgraph subGraph2["Quality Checks"]
    FMT["cargo fmt --all -- --check"]
    CLIPPY["cargo clippy --target TARGET --all-features"]
    BUILD["cargo build --target TARGET --all-features"]
    TEST["cargo test --target TARGET"]
end
subgraph subGraph1["CI Job Matrix"]
    RUST_VERSION["nightly toolchain"]
    TARGET_X86_LINUX["x86_64-unknown-linux-gnu"]
    TARGET_X86_BARE["x86_64-unknown-none"]
    TARGET_RISCV["riscv64gc-unknown-none-elf"]
    TARGET_ARM["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["CI Triggers"]
    PUSH["push events"]
    PR["pull_request events"]
end
subgraph subGraph3["Documentation Job"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
    INDEX_GEN["printf redirect to index.html"]
    PAGES_DEPLOY["JamesIves/github-pages-deploy-action@v4"]
end
CI_JOB["CI_JOB"]

CI_JOB --> RUST_VERSION
DOC_BUILD --> INDEX_GEN
INDEX_GEN --> PAGES_DEPLOY
PR --> CI_JOB
PUSH --> CI_JOB
RUST_VERSION --> TARGET_ARM
RUST_VERSION --> TARGET_RISCV
RUST_VERSION --> TARGET_X86_BARE
RUST_VERSION --> TARGET_X86_LINUX
TARGET_ARM --> BUILD
TARGET_ARM --> CLIPPY
TARGET_RISCV --> BUILD
TARGET_RISCV --> CLIPPY
TARGET_X86_BARE --> BUILD
TARGET_X86_BARE --> CLIPPY
TARGET_X86_LINUX --> BUILD
TARGET_X86_LINUX --> CLIPPY
TARGET_X86_LINUX --> FMT
TARGET_X86_LINUX --> TEST

Sources: .github/workflows/ci.yml(L1 - L56) 

Job Configuration Matrix

The CI pipeline uses a matrix strategy to test across multiple targets simultaneously. The configuration in .github/workflows/ci.yml(L8 - L12)  defines:

strategy:
  fail-fast: false
  matrix:
    rust-toolchain: [nightly]
    targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]

The fail-fast: false setting ensures that failures on one target don't prevent testing of other targets, crucial for multi-architecture development.

Sources: .github/workflows/ci.yml(L8 - L12) 

Quality Assurance Pipeline

The quality assurance process implements multiple validation stages to ensure code correctness and consistency across all supported targets.

Toolchain Setup

Each CI run establishes a complete Rust development environment using .github/workflows/ci.yml(L15 - L19) :

- uses: dtolnay/rust-toolchain@nightly
  with:
    toolchain: ${{ matrix.rust-toolchain }}
    components: rust-src, clippy, rustfmt
    targets: ${{ matrix.targets }}

The rust-src component enables building for bare-metal targets that require custom standard libraries.

Code Quality Checks

flowchart TD
subgraph subGraph1["Quality Enforcement"]
    CLIPPY_CONFIG["-A clippy::new_without_default"]
    FORMAT_STRICT["--check flag enforces formatting"]
    TEST_OUTPUT["--nocapture for debugging"]
    ALL_FEATURES["--all-features enables complete validation"]
end
subgraph subGraph0["Quality Gates"]
    VERSION_CHECK["rustc --version --verbose"]
    FORMAT_CHECK["cargo fmt --all -- --check"]
    LINT_CHECK["cargo clippy --target TARGET"]
    BUILD_CHECK["cargo build --target TARGET"]
    UNIT_TEST["cargo test --target TARGET"]
end

BUILD_CHECK --> ALL_FEATURES
BUILD_CHECK --> UNIT_TEST
FORMAT_CHECK --> FORMAT_STRICT
FORMAT_CHECK --> LINT_CHECK
LINT_CHECK --> BUILD_CHECK
LINT_CHECK --> CLIPPY_CONFIG
UNIT_TEST --> TEST_OUTPUT
VERSION_CHECK --> FORMAT_CHECK

The pipeline enforces strict code formatting through .github/workflows/ci.yml(L22 - L23)  and comprehensive linting via .github/workflows/ci.yml(L24 - L25)  The clippy configuration includes -A clippy::new_without_default to suppress warnings about missing Default implementations, which may not be appropriate for capability-wrapped types.

Testing Strategy

Unit tests execute only on the x86_64-unknown-linux-gnu target .github/workflows/ci.yml(L28 - L30)  as bare-metal targets cannot run standard test frameworks. The test command includes --nocapture to display all output for debugging purposes.

Sources: .github/workflows/ci.yml(L20 - L30) 

Multi-Target Build Matrix

The build system supports diverse execution environments ranging from standard Linux development to bare-metal embedded systems.

Target Architecture Mapping

Target TripleArchitectureEnvironmentUse Case
x86_64-unknown-linux-gnux86_64Standard LinuxDevelopment and testing
x86_64-unknown-nonex86_64Bare metalKernel development
riscv64gc-unknown-none-elfRISC-V 64-bitEmbedded ELFIoT and embedded systems
aarch64-unknown-none-softfloatARM64Embedded soft-floatResource-constrained ARM systems

Build Process Flow

flowchart TD
subgraph subGraph2["Validation Per Target"]
    CLIPPY_LINUX["clippy x86_64-unknown-linux-gnu"]
    CLIPPY_X86_BARE["clippy x86_64-unknown-none"]
    CLIPPY_RISCV["clippy riscv64gc-unknown-none-elf"]
    CLIPPY_ARM["clippy aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Target-Specific Builds"]
    BUILD_LINUX["cargo build --target x86_64-unknown-linux-gnu"]
    BUILD_X86_BARE["cargo build --target x86_64-unknown-none"]
    BUILD_RISCV["cargo build --target riscv64gc-unknown-none-elf"]
    BUILD_ARM["cargo build --target aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Source Validation"]
    SOURCE_CODE["src/lib.rs"]
    CARGO_CONFIG["Cargo.toml"]
end

BUILD_ARM --> CLIPPY_ARM
BUILD_LINUX --> CLIPPY_LINUX
BUILD_RISCV --> CLIPPY_RISCV
BUILD_X86_BARE --> CLIPPY_X86_BARE
CARGO_CONFIG --> BUILD_ARM
CARGO_CONFIG --> BUILD_LINUX
CARGO_CONFIG --> BUILD_RISCV
CARGO_CONFIG --> BUILD_X86_BARE
SOURCE_CODE --> BUILD_ARM
SOURCE_CODE --> BUILD_LINUX
SOURCE_CODE --> BUILD_RISCV
SOURCE_CODE --> BUILD_X86_BARE

Each target undergoes identical validation through .github/workflows/ci.yml(L24 - L27)  ensuring consistent behavior across all supported platforms.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L24 - L27) 

Documentation Generation and Deployment

The documentation system automatically generates and deploys API documentation to GitHub Pages, providing accessible reference material for library users.

Documentation Build Process

The documentation job runs independently of the main CI pipeline .github/workflows/ci.yml(L32 - L56)  and includes sophisticated error handling for different branch contexts:

- name: Build docs
  continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
  run: |
    cargo doc --no-deps --all-features
    printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

The continue-on-error condition in .github/workflows/ci.yml(L45)  allows documentation failures on non-default branches while ensuring they block merges to the main branch.

Automated Index Generation

The documentation build process includes automatic index page creation .github/workflows/ci.yml(L46 - L48)  that redirects to the main crate documentation, improving user experience when accessing the documentation site.

GitHub Pages Deployment

Documentation deployment uses the JamesIves/github-pages-deploy-action@v4 action .github/workflows/ci.yml(L49 - L55)  with single-commit deployment to the gh-pages branch, ensuring clean deployment history.

The deployment only triggers on pushes to the default branch .github/workflows/ci.yml(L50)  preventing unnecessary deployments from feature branches.

Sources: .github/workflows/ci.yml(L32 - L56) 

Build Environment Configuration

Environment Variables and Flags

The documentation build process configures strict linting through the RUSTDOCFLAGS environment variable .github/workflows/ci.yml(L40) :

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

This configuration treats broken documentation links and missing documentation as errors, ensuring comprehensive and correct documentation.

Permission Configuration

The documentation job requires special permissions .github/workflows/ci.yml(L36 - L37)  to write to the repository for GitHub Pages deployment:

permissions:
  contents: write

Sources: .github/workflows/ci.yml(L36 - L40) 

Multi-Platform Support

Relevant source files

This document covers the multi-platform architecture support implemented in cap_access, including the supported target architectures, build matrix configuration, and platform-specific considerations for embedded and bare-metal environments.

For information about the overall build system and continuous integration setup, see Build System and CI. For details about how cap_access integrates with ArceOS across different platforms, see ArceOS Integration.

Supported Target Architectures

The cap_access library is designed to run across multiple hardware architectures and execution environments, with a particular focus on embedded and systems programming contexts. The library supports both hosted and bare-metal execution environments.

Target Architecture Matrix

flowchart TD
subgraph subGraph2["cap_access Library"]
    core_lib["Core Libraryno_std compatible"]
    bitflags_dep["bitflags dependencyVersion 2.6"]
end
subgraph subGraph1["Bare Metal Environments"]
    x86_64_bare["x86_64-unknown-noneBare metal x86_64"]
    riscv64["riscv64gc-unknown-none-elfRISC-V 64-bit with extensions"]
    aarch64["aarch64-unknown-none-softfloatARM64 without hardware floating point"]
end
subgraph subGraph0["Linux Hosted Environment"]
    x86_64_linux["x86_64-unknown-linux-gnuStandard Linux target"]
end

bitflags_dep --> core_lib
core_lib --> aarch64
core_lib --> riscv64
core_lib --> x86_64_bare
core_lib --> x86_64_linux

Target Architecture Details

TargetEnvironmentUse CaseFloating Point
x86_64-unknown-linux-gnuHosted LinuxDevelopment, testingHardware
x86_64-unknown-noneBare metalEmbedded x86_64 systemsSoftware
riscv64gc-unknown-none-elfBare metalRISC-V embedded boardsSoftware
aarch64-unknown-none-softfloatBare metalARM64 embedded devicesSoftware

Sources: .github/workflows/ci.yml(L12)  Cargo.toml(L12) 

Continuous Integration Build Matrix

The multi-platform support is validated through a comprehensive CI pipeline that builds and tests the library across all supported architectures.

CI Pipeline Architecture

flowchart TD
subgraph subGraph3["Quality Checks"]
    fmt_check["cargo fmt --all --check"]
    clippy_check["cargo clippy --target TARGET"]
    build_check["cargo build --target TARGET"]
    unit_test["cargo test --target TARGET"]
end
subgraph subGraph2["Target Matrix Execution"]
    x86_linux_job["x86_64-unknown-linux-gnuBuild + Test + Clippy"]
    x86_bare_job["x86_64-unknown-noneBuild + Clippy"]
    riscv_job["riscv64gc-unknown-none-elfBuild + Clippy"]
    arm_job["aarch64-unknown-none-softfloatBuild + Clippy"]
end
subgraph subGraph1["Build Matrix Strategy"]
    rust_nightly["Rust Nightly Toolchain"]
    fail_fast_false["fail-fast: false"]
end
subgraph subGraph0["GitHub Actions Workflow"]
    trigger["Push/Pull Request Triggers"]
    ubuntu_runner["ubuntu-latest runner"]
end

arm_job --> build_check
arm_job --> clippy_check
fail_fast_false --> arm_job
fail_fast_false --> riscv_job
fail_fast_false --> x86_bare_job
fail_fast_false --> x86_linux_job
riscv_job --> build_check
riscv_job --> clippy_check
rust_nightly --> fail_fast_false
trigger --> ubuntu_runner
ubuntu_runner --> rust_nightly
x86_bare_job --> build_check
x86_bare_job --> clippy_check
x86_linux_job --> build_check
x86_linux_job --> clippy_check
x86_linux_job --> fmt_check
x86_linux_job --> unit_test

CI Pipeline Configuration Details

The CI pipeline implements a matrix strategy with the following characteristics:

  • Rust Toolchain: Nightly with rust-src, clippy, and rustfmt components
  • Failure Strategy: fail-fast: false allows all targets to complete even if one fails
  • Testing Scope: Unit tests only run on x86_64-unknown-linux-gnu due to hosted environment requirements
  • Quality Checks: All targets undergo formatting, linting, and build verification

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L15 - L19)  .github/workflows/ci.yml(L28 - L30) 

Platform-Specific Considerations

No Standard Library Compatibility

The cap_access library is designed with no_std compatibility as a primary requirement, enabling deployment in resource-constrained embedded environments.

Dependency Management


The library maintains a minimal dependency footprint with only the bitflags crate as an external dependency, ensuring compatibility with embedded environments that cannot support the full Rust standard library.

Sources: Cargo.toml(L14 - L15)  Cargo.toml(L12) 

Architecture-Specific Build Considerations

Each target architecture has specific build requirements and constraints:

x86_64 Targets

  • x86_64-unknown-linux-gnu: Full hosted environment with standard library support for development and testing
  • x86_64-unknown-none: Bare metal target requiring #![no_std] and custom runtime support

RISC-V Target

  • riscv64gc-unknown-none-elf: Supports RISC-V 64-bit with general extensions (G) and compressed instructions (C)
  • Requires software floating-point operations
  • Common target for embedded RISC-V development boards

ARM64 Target

  • aarch64-unknown-none-softfloat: ARM64 architecture without hardware floating-point unit
  • Optimized for embedded ARM64 devices with software floating-point implementation
  • Suitable for resource-constrained ARM-based systems

Sources: .github/workflows/ci.yml(L12) 

Testing and Validation Strategy

The multi-platform support validation employs a targeted testing strategy that accounts for the constraints of different execution environments.

Platform Testing Matrix

flowchart TD
subgraph subGraph1["Target Platform Coverage"]
    x86_linux["x86_64-unknown-linux-gnu✓ Format ✓ Lint ✓ Build ✓ Test"]
    x86_bare["x86_64-unknown-none✓ Format ✓ Lint ✓ Build ✗ Test"]
    riscv["riscv64gc-unknown-none-elf✓ Format ✓ Lint ✓ Build ✗ Test"]
    arm["aarch64-unknown-none-softfloat✓ Format ✓ Lint ✓ Build ✗ Test"]
end
subgraph subGraph0["Test Categories"]
    format_test["Code Formattingcargo fmt --all --check"]
    lint_test["Static Analysiscargo clippy"]
    build_test["Compilationcargo build"]
    unit_test["Runtime Testingcargo test"]
end

build_test --> arm
build_test --> riscv
build_test --> x86_bare
build_test --> x86_linux
format_test --> arm
format_test --> riscv
format_test --> x86_bare
format_test --> x86_linux
lint_test --> arm
lint_test --> riscv
lint_test --> x86_bare
lint_test --> x86_linux
unit_test --> x86_linux

Testing Constraints by Platform

  • Hosted Platforms: Full test suite including unit tests can execute in the hosted Linux environment
  • Bare Metal Platforms: Limited to static analysis and compilation verification due to lack of test runtime
  • Cross-Compilation: All bare metal targets are cross-compiled from the x86_64 Linux host environment

The testing strategy ensures that while runtime testing is limited to hosted environments, all platforms receive comprehensive static analysis and successful compilation verification.

Sources: .github/workflows/ci.yml(L22 - L30)  .github/workflows/ci.yml(L25)  .github/workflows/ci.yml(L29) 

Overview

Relevant source files

The kspin crate provides kernel-space spinlock implementations with configurable protection levels for the ArceOS operating system ecosystem. This library offers three distinct spinlock types that control interrupt and preemption states during critical sections, enabling safe concurrent access to shared data in kernel environments.

This document covers the fundamental architecture, protection mechanisms, and usage patterns of the kspin crate. For detailed implementation specifics of the core spinlock logic, see Core Implementation Architecture. For usage guidelines and safety considerations, see Usage Guidelines and Safety.

System Architecture

The kspin crate implements a layered architecture where specialized spinlock types are built upon a generic BaseSpinLock foundation. The system integrates with the kernel_guard crate to provide different levels of protection through compile-time type safety.

Component Architecture

flowchart TD
subgraph subGraph3["Feature Flags"]
    SMPFeature["smp feature"]
end
subgraph subGraph2["External Dependencies"]
    NoOp["kernel_guard::NoOp"]
    NoPreempt["kernel_guard::NoPreempt"]
    NoPreemptIrqSave["kernel_guard::NoPreemptIrqSave"]
    CfgIf["cfg-if"]
end
subgraph subGraph1["Core Implementation"]
    BaseSpinLock["BaseSpinLock"]
    BaseSpinLockGuard["BaseSpinLockGuard"]
end
subgraph subGraph0["Public API Layer"]
    SpinRaw["SpinRaw"]
    SpinNoPreempt["SpinNoPreempt"]
    SpinNoIrq["SpinNoIrq"]
    SpinRawGuard["SpinRawGuard"]
    SpinNoPreemptGuard["SpinNoPreemptGuard"]
    SpinNoIrqGuard["SpinNoIrqGuard"]
end

BaseSpinLock --> CfgIf
BaseSpinLock --> NoOp
BaseSpinLock --> NoPreempt
BaseSpinLock --> NoPreemptIrqSave
BaseSpinLock --> SMPFeature
SpinNoIrq --> BaseSpinLock
SpinNoIrqGuard --> BaseSpinLockGuard
SpinNoPreempt --> BaseSpinLock
SpinNoPreemptGuard --> BaseSpinLockGuard
SpinRaw --> BaseSpinLock
SpinRawGuard --> BaseSpinLockGuard

Sources: src/lib.rs(L1 - L37)  Cargo.toml(L19 - L21)  Cargo.toml(L14 - L17) 

Protection Level Hierarchy

The kspin crate provides three protection levels, each corresponding to different kernel execution contexts and safety requirements. The protection levels are implemented through type aliases that parameterize the generic BaseSpinLock with specific guard types.

Protection Levels and Guard Mapping

flowchart TD
subgraph subGraph3["Usage Context"]
    IRQDisabledContext["IRQ-disabled context"]
    GeneralKernelContext["General kernel context"]
    InterruptHandler["Interrupt handler"]
end
subgraph subGraph2["Protection Behavior"]
    NoBehavior["No protectionManual control required"]
    PreemptionDisabled["Disables preemptionIRQ context required"]
    FullProtection["Disables preemption + IRQsAny context safe"]
end
subgraph subGraph1["Guard Types"]
    NoOp["NoOp"]
    NoPreempt["NoPreempt"]
    NoPreemptIrqSave["NoPreemptIrqSave"]
end
subgraph subGraph0["Spinlock Types"]
    SpinRaw["SpinRaw"]
    SpinNoPreempt["SpinNoPreempt"]
    SpinNoIrq["SpinNoIrq"]
end

FullProtection --> GeneralKernelContext
FullProtection --> InterruptHandler
NoBehavior --> IRQDisabledContext
NoOp --> NoBehavior
NoPreempt --> PreemptionDisabled
NoPreemptIrqSave --> FullProtection
PreemptionDisabled --> IRQDisabledContext
SpinNoIrq --> NoPreemptIrqSave
SpinNoPreempt --> NoPreempt
SpinRaw --> NoOp

Sources: src/lib.rs(L10 - L36)  README.md(L16 - L33) 

Key Design Principles

PrincipleImplementationBenefit
Type SafetyGuard types encode protection requirementsCompile-time prevention of misuse
RAII PatternBaseSpinLockGuardensures cleanupAutomatic lock release on scope exit
Zero-Cost Abstractionsmpfeature eliminates overheadSingle-core optimization
Flexible ProtectionThree distinct protection levelsContext-appropriate safety

The crate follows a compile-time optimization strategy where the smp feature flag controls whether actual atomic operations are generated. In single-core environments, the lock state is optimized away entirely, reducing the spinlock to a simple guard management system.

Sources: Cargo.toml(L14 - L17)  README.md(L12) 

Integration with ArceOS Ecosystem

The kspin crate serves as a foundational synchronization primitive within the ArceOS operating system project. It provides the kernel-level locking mechanisms required for:

  • Memory management subsystems requiring atomic access to page tables and allocation structures
  • Device driver frameworks needing interrupt-safe critical sections
  • Scheduler components protecting task queues and scheduling state
  • File system implementations ensuring metadata consistency

The library's design emphasizes kernel-space usage through its dependency on kernel_guard, which provides the underlying preemption and interrupt control mechanisms. The three-tier protection model allows different kernel subsystems to choose the appropriate level of protection based on their execution context requirements.

Sources: Cargo.toml(L6)  Cargo.toml(L8)  src/lib.rs(L6) 

Compilation Model

The kspin crate uses conditional compilation to adapt its behavior based on target environment characteristics:

flowchart TD
subgraph subGraph2["Multi-Core Build"]
    AtomicState["AtomicBool lock state"]
    CompareExchange["compare_exchange operations"]
    SpinBehavior["Actual spinning behavior"]
end
subgraph subGraph1["Single-Core Build"]
    NoLockState["No lock state field"]
    NoAtomics["No atomic operations"]
    AlwaysSucceed["Lock always succeeds"]
end
subgraph subGraph0["Feature Detection"]
    SMPCheck["smp feature enabled?"]
end

SMPCheck --> AlwaysSucceed
SMPCheck --> AtomicState
SMPCheck --> CompareExchange
SMPCheck --> NoAtomics
SMPCheck --> NoLockState
SMPCheck --> SpinBehavior

This compilation strategy ensures that embedded and single-core kernel environments receive fully optimized code with no synchronization overhead, while multi-core systems get the full atomic operation implementation required for correct concurrent behavior.

Sources: README.md(L12)  Cargo.toml(L15 - L16) 

Project Structure and Dependencies

Relevant source files

This document describes the organizational structure of the kspin crate, including its file layout, external dependencies, and build configuration. It covers the project's dependency management, feature flag system, and how the crate is organized to support different compilation targets.

For detailed information about the specific spinlock types and their usage, see Spinlock Types and Public API. For implementation details of the core architecture, see Core Implementation Architecture.

File Structure Overview

The kspin crate follows a minimal but well-organized structure typical of focused Rust libraries. The project consists of core source files, build configuration, and development tooling.

Project File Organization

flowchart TD
subgraph subGraph2["Development Tools"]
    VSCodeDir[".vscode/IDE configuration"]
    TargetDir["target/Build artifacts (ignored)"]
end
subgraph subGraph1["Source Code (src/)"]
    LibRs["lib.rsPublic API & type aliases"]
    BaseRs["base.rsCore BaseSpinLock implementation"]
end
subgraph subGraph0["Root Directory"]
    CargoToml["Cargo.tomlProject metadata & dependencies"]
    CargoLock["Cargo.lockDependency lock file"]
    GitIgnore[".gitignoreVersion control exclusions"]
end
subgraph CI/CD["CI/CD"]
    GithubActions[".github/workflows/Automated testing & docs"]
end

CargoLock --> CargoToml
CargoToml --> BaseRs
CargoToml --> LibRs
GitIgnore --> TargetDir
GitIgnore --> VSCodeDir
LibRs --> BaseRs

Sources: Cargo.toml(L1 - L22)  .gitignore(L1 - L4)  Cargo.lock(L1 - L74) 

Source File Responsibilities

FilePurposeKey Components
src/lib.rsPublic API surfaceSpinRaw,SpinNoPreempt,SpinNoIrqtype aliases
src/base.rsCore implementationBaseSpinLock,BaseSpinLockGuard,BaseGuardtrait

The crate deliberately maintains a minimal file structure with only two source files, emphasizing simplicity and focused functionality.

Sources: Cargo.toml(L1 - L22) 

External Dependencies

The kspin crate has a carefully curated set of external dependencies that provide essential functionality for kernel-space synchronization and conditional compilation.

Direct Dependencies

flowchart TD
subgraph subGraph2["Transitive Dependencies"]
    CrateInterface["crate_interfacev0.1.4Interface definitions"]
    ProcMacro2["proc-macro2v1.0.93Procedural macros"]
    Quote["quotev1.0.38Code generation"]
    Syn["synv2.0.96Syntax parsing"]
    UnicodeIdent["unicode-identv1.0.14Unicode support"]
end
subgraph subGraph1["Direct Dependencies"]
    CfgIf["cfg-ifv1.0.0Conditional compilation"]
    KernelGuard["kernel_guardv0.1.2Protection mechanisms"]
end
subgraph subGraph0["kspin Crate"]
    KSpin["kspinv0.1.0"]
end

CrateInterface --> ProcMacro2
CrateInterface --> Quote
CrateInterface --> Syn
KSpin --> CfgIf
KSpin --> KernelGuard
KernelGuard --> CfgIf
KernelGuard --> CrateInterface
ProcMacro2 --> UnicodeIdent
Quote --> ProcMacro2
Syn --> ProcMacro2
Syn --> Quote
Syn --> UnicodeIdent

Sources: Cargo.toml(L19 - L21)  Cargo.lock(L5 - L74) 

Dependency Roles

CrateVersionPurposeUsage in kspin
cfg-if1.0.0Conditional compilation utilitiesEnables SMP vs single-core optimizations
kernel_guard0.1.2Kernel protection mechanismsProvidesNoOp,NoPreempt,NoPreemptIrqSaveguards

The kernel_guard crate is the primary external dependency, providing the guard types that implement different protection levels. The cfg-if crate enables clean conditional compilation based on feature flags.

Sources: Cargo.toml(L20 - L21)  Cargo.lock(L23 - L30) 

Feature Configuration

The kspin crate uses Cargo feature flags to enable compile-time optimization for different target environments, particularly distinguishing between single-core and multi-core systems.

Feature Flag System

flowchart TD
subgraph subGraph2["Generated Code Characteristics"]
    SMPCode["SMP Implementation• AtomicBool for lock state• compare_exchange operations• Actual spinning behavior"]
    SingleCoreCode["Single-core Implementation• No lock state field• Lock always succeeds• No atomic operations"]
end
subgraph subGraph1["Compilation Modes"]
    SMPMode["SMP ModeMulti-core systems"]
    SingleCoreMode["Single-core ModeEmbedded/simple systems"]
end
subgraph subGraph0["Feature Flags"]
    SMPFeature["smpMulti-core support"]
    DefaultFeature["defaultEmpty set"]
end

DefaultFeature --> SingleCoreMode
SMPFeature --> SMPMode
SMPMode --> SMPCode
SingleCoreMode --> SingleCoreCode

Sources: Cargo.toml(L14 - L17) 

Feature Flag Details

FeatureDefaultDescriptionImpact
smpNoEnable multi-core supportAdds atomic operations and actual lock state
defaultYesDefault feature set (empty)Optimized for single-core environments

The feature system allows the same codebase to generate dramatically different implementations:

  • Without smp: Lock operations are compile-time no-ops, eliminating all atomic overhead
  • With smp: Full atomic spinlock implementation with proper memory ordering

Sources: Cargo.toml(L14 - L17) 

Build System Organization

The project follows standard Rust crate conventions with specific configurations for kernel-space development and multi-platform support.

Package Metadata Configuration

flowchart TD
subgraph Licensing["Licensing"]
    License["licenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
end
subgraph subGraph2["Repository Links"]
    Homepage["homepageArceOS GitHub"]
    Repository["repositorykspin GitHub"]
    Documentation["documentationdocs.rs"]
end
subgraph subGraph1["Package Classification"]
    Keywords["keywords['arceos', 'synchronization', 'spinlock', 'no-irq']"]
    Categories["categories['os', 'no-std']"]
    Description["description'Spinlocks for kernel space...'"]
end
subgraph subGraph0["Package Identity"]
    Name["name = 'kspin'"]
    Version["version = '0.1.0'"]
    Edition["edition = '2021'"]
    Authors["authors = ['Yuekai Jia']"]
end

Description --> Repository
Homepage --> License
Name --> Keywords
Repository --> Documentation
Version --> Categories

Sources: Cargo.toml(L1 - L12) 

Development Environment Setup

The project includes configuration for common development tools:

ToolConfigurationPurpose
Git.gitignoreexcludes/target,/.vscode,.DS_StoreClean repository state
VS Code.vscode/directory (ignored)IDE-specific settings
CargoCargo.lockcommittedReproducible builds

The .gitignore configuration ensures that build artifacts and IDE-specific files don't pollute the repository, while the committed Cargo.lock file ensures reproducible builds across different environments.

Sources: .gitignore(L1 - L4)  Cargo.lock(L1 - L4) 

Spinlock Types and Public API

Relevant source files

This document covers the three main spinlock types exposed by the kspin crate and their public interface. These types provide different levels of protection suitable for various kernel contexts. For detailed implementation internals, see Core Implementation Architecture. For comprehensive usage guidelines and safety requirements, see Usage Guidelines and Safety.

Spinlock Type Overview

The kspin crate provides three specialized spinlock types, each offering a different balance between performance and protection. All types are implemented as type aliases of the generic BaseSpinLock with different guard types from the kernel_guard crate.

Spinlock TypeGuard TypeProtection LevelUse Context
SpinRawNoOpNo protectionPreemption and IRQ-disabled contexts
SpinNoPreemptNoPreemptDisables preemptionIRQ-disabled contexts
SpinNoIrqNoPreemptIrqSaveDisables preemption + IRQsAny context

Sources: src/lib.rs(L10 - L36) 

Type Hierarchy and Guard Relationships

flowchart TD
subgraph subGraph2["Core Implementation (src/base.rs)"]
    BaseSpinLock["BaseSpinLock<G, T>Generic spinlock"]
    BaseSpinLockGuard["BaseSpinLockGuard<'a, G, T>RAII guard"]
end
subgraph subGraph1["kernel_guard Crate Guards"]
    NoOp["NoOpDoes nothing"]
    NoPreempt["NoPreemptDisables preemption"]
    NoPreemptIrqSave["NoPreemptIrqSaveDisables preemption + IRQs"]
end
subgraph subGraph0["Public API Types (src/lib.rs)"]
    SpinRaw["SpinRaw<T>BaseSpinLock<NoOp, T>"]
    SpinNoPreempt["SpinNoPreempt<T>BaseSpinLock<NoPreempt, T>"]
    SpinNoIrq["SpinNoIrq<T>BaseSpinLock<NoPreemptIrqSave, T>"]
    SpinRawGuard["SpinRawGuard<'a, T>BaseSpinLockGuard<'a, NoOp, T>"]
    SpinNoPreemptGuard["SpinNoPreemptGuard<'a, T>BaseSpinLockGuard<'a, NoPreempt, T>"]
    SpinNoIrqGuard["SpinNoIrqGuard<'a, T>BaseSpinLockGuard<'a, NoPreemptIrqSave, T>"]
end

SpinNoIrq --> BaseSpinLock
SpinNoIrq --> NoPreemptIrqSave
SpinNoIrq --> SpinNoIrqGuard
SpinNoIrqGuard --> BaseSpinLockGuard
SpinNoPreempt --> BaseSpinLock
SpinNoPreempt --> NoPreempt
SpinNoPreempt --> SpinNoPreemptGuard
SpinNoPreemptGuard --> BaseSpinLockGuard
SpinRaw --> BaseSpinLock
SpinRaw --> NoOp
SpinRaw --> SpinRawGuard
SpinRawGuard --> BaseSpinLockGuard

Sources: src/lib.rs(L6 - L36) 

Public API Structure

flowchart TD
subgraph subGraph2["RAII Lifecycle"]
    acquire["Guard AcquisitionProtection enabled"]
    critical["Critical SectionData access"]
    release["Guard DropProtection disabled"]
end
subgraph subGraph1["Guard Operations"]
    deref["Deref::deref()→ &T"]
    deref_mut["DerefMut::deref_mut()→ &mut T"]
    drop_guard["Drop::drop()→ ()"]
end
subgraph subGraph0["Spinlock Operations"]
    new["new(data: T)→ SpinLock<T>"]
    lock["lock()→ Guard<'_, T>"]
    try_lock["try_lock()→ Option<Guard<'_, T>>"]
    is_locked["is_locked()→ bool"]
end

acquire --> deref
acquire --> deref_mut
critical --> drop_guard
deref --> critical
deref_mut --> critical
drop_guard --> release
lock --> acquire
new --> lock
new --> try_lock
try_lock --> acquire

Sources: src/lib.rs(L8)  README.md(L19 - L32) 

Type Definitions and Documentation

SpinRaw

SpinRaw<T> provides the fastest spinlock implementation with no built-in protection mechanisms. It is defined as BaseSpinLock<NoOp, T> where the NoOp guard performs no protection operations.

Key Characteristics:

  • Zero protection overhead
  • Requires manual IRQ and preemption management
  • Must be used in preemption-disabled and IRQ-disabled contexts
  • Cannot be used in interrupt handlers

Sources: src/lib.rs(L29 - L33) 

SpinNoPreempt

SpinNoPreempt<T> disables kernel preemption during lock acquisition and critical sections. It is defined as BaseSpinLock<NoPreempt, T>.

Key Characteristics:

  • Automatically disables preemption when acquiring the lock
  • Safe from task switching but not from interrupts
  • Must be used in IRQ-disabled contexts
  • Cannot be used in interrupt handlers

Sources: src/lib.rs(L10 - L15) 

SpinNoIrq

SpinNoIrq<T> provides the highest level of protection by disabling both kernel preemption and local IRQs. It is defined as BaseSpinLock<NoPreemptIrqSave, T>.

Key Characteristics:

  • Disables both preemption and IRQs
  • Safe to use in any context including IRQ-enabled environments
  • Highest protection overhead but maximum safety
  • Can be used in interrupt handlers

Sources: src/lib.rs(L20 - L24) 

Associated Guard Types

Each spinlock type has a corresponding guard type that implements the RAII pattern:

  • SpinRawGuard<'a, T> for SpinRaw<T>
  • SpinNoPreemptGuard<'a, T> for SpinNoPreempt<T>
  • SpinNoIrqGuard<'a, T> for SpinNoIrq<T>

These guards provide mutable access to the protected data through Deref and DerefMut implementations, and automatically release the lock when dropped.

Sources: src/lib.rs(L17 - L18)  src/lib.rs(L26 - L27)  src/lib.rs(L35 - L36) 

Usage Example from Public Documentation

The crate provides standard usage patterns as demonstrated in the README:

// Raw spinlock - fastest, requires manual protection
let data = SpinRaw::new(());
let mut guard = data.lock();
/* critical section, does nothing while trying to lock. */
drop(guard);

// Preemption-disabled spinlock
let data = SpinNoPreempt::new(());
let mut guard = data.lock();
/* critical section, preemption are disabled. */
drop(guard);

// Full protection spinlock
let data = SpinNoIrq::new(());
let mut guard = data.lock();
/* critical section, both preemption and IRQs are disabled. */
drop(guard);

Sources: README.md(L16 - L33) 

SpinRaw

Relevant source files

Purpose and Scope

This page documents SpinRaw<T>, the raw spinlock implementation in the kspin crate that provides no built-in protection mechanisms. SpinRaw offers the fastest performance among the three spinlock types but requires manual management of preemption and interrupt state by the caller.

For information about spinlocks with built-in preemption protection, see SpinNoPreempt. For full protection including IRQ disabling, see SpinNoIrq. For detailed implementation internals, see BaseSpinLock and BaseSpinLockGuard. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.

Type Definition and Basic Interface

SpinRaw<T> is implemented as a type alias that specializes the generic BaseSpinLock with the NoOp guard type from the kernel_guard crate.

Core Type Definitions

flowchart TD
subgraph subGraph0["SpinRaw Type System"]
    SpinRaw["SpinRaw<T>Type alias"]
    BaseSpinLock["BaseSpinLock<NoOp, T>Generic implementation"]
    SpinRawGuard["SpinRawGuard<'a, T>RAII guard"]
    BaseSpinLockGuard["BaseSpinLockGuard<'a, NoOp, T>Generic guard"]
    NoOp["NoOpkernel_guard guard type"]
end

BaseSpinLock --> BaseSpinLockGuard
BaseSpinLock --> NoOp
BaseSpinLockGuard --> NoOp
SpinRaw --> BaseSpinLock
SpinRawGuard --> BaseSpinLockGuard

Sources: src/lib.rs(L33 - L36) 

The SpinRaw<T> type provides the standard spinlock interface methods inherited from BaseSpinLock:

MethodReturn TypeDescription
new(data: T)SpinRawCreates a new spinlock containing the given data
lock()SpinRawGuard<'_, T>Acquires the lock, spinning until successful
try_lock()Option<SpinRawGuard<'_, T>>Attempts to acquire the lock without spinning

Protection Behavior and NoOp Guard

The defining characteristic of SpinRaw is its use of the NoOp guard type, which performs no protection actions during lock acquisition or release.

Protection Flow Diagram

flowchart TD
subgraph subGraph1["NoOp Guard Behavior"]
    NoOpAcquire["NoOp::acquire()Does nothing"]
    NoOpRelease["NoOp::release()Does nothing"]
end
subgraph subGraph0["SpinRaw Lock Acquisition Flow"]
    Start["lock() called"]
    TryAcquire["Attempt atomic acquisition"]
    IsLocked["Lock alreadyheld?"]
    Spin["Spin wait(busy loop)"]
    Acquired["Lock acquired"]
    GuardCreated["SpinRawGuard createdwith NoOp::acquire()"]
end
note1["Note: No preemption disablingNo IRQ disablingNo protection barriers"]

Acquired --> GuardCreated
GuardCreated --> NoOpAcquire
GuardCreated --> NoOpRelease
IsLocked --> Acquired
IsLocked --> Spin
NoOpAcquire --> note1
NoOpRelease --> note1
Spin --> TryAcquire
Start --> TryAcquire
TryAcquire --> IsLocked

Sources: src/lib.rs(L29 - L36)  src/base.rs

Unlike SpinNoPreempt and SpinNoIrq, the NoOp guard performs no system-level protection operations:

  • No preemption disabling/enabling
  • No interrupt disabling/enabling
  • No memory barriers beyond those required for the atomic lock operations

Safety Requirements and Usage Contexts

SpinRaw places the burden of ensuring safe usage entirely on the caller. The lock itself provides only the basic mutual exclusion mechanism without any system-level protections.

Required Caller Responsibilities

flowchart TD
subgraph subGraph1["Consequences of Violation"]
    Deadlock["Potential deadlockif preempted while holding lock"]
    RaceCondition["Race conditionswith interrupt handlers"]
    DataCorruption["Data corruptionfrom concurrent access"]
end
subgraph subGraph0["Caller Must Ensure"]
    PreemptDisabled["Preemption already disabledBefore calling lock()"]
    IRQDisabled["Local IRQs already disabledBefore calling lock()"]
    NoInterruptUse["Never used ininterrupt handlers"]
    ProperContext["Operating in appropriatekernel context"]
end
subgraph subGraph2["Safe Usage Pattern"]
    DisablePreempt["disable_preemption()"]
    DisableIRQ["disable_local_irq()"]
    UseLock["SpinRaw::lock()"]
    CriticalSection["/* critical section */"]
    ReleaseLock["drop(guard)"]
    EnableIRQ["enable_local_irq()"]
    EnablePreempt["enable_preemption()"]
end

CriticalSection --> ReleaseLock
DisableIRQ --> UseLock
DisablePreempt --> DisableIRQ
EnableIRQ --> EnablePreempt
IRQDisabled --> RaceCondition
NoInterruptUse --> RaceCondition
PreemptDisabled --> Deadlock
ProperContext --> DataCorruption
ReleaseLock --> EnableIRQ
UseLock --> CriticalSection

Sources: src/lib.rs(L31 - L32)  README.md(L19 - L22) 

Performance Characteristics

SpinRaw offers the highest performance among the three spinlock types due to its minimal overhead approach.

Performance Comparison

Spinlock TypeAcquisition OverheadRelease OverheadContext Switches
SpinRawAtomic operation onlyAtomic operation onlyManual control required
SpinNoPreemptAtomic + preemption disableAtomic + preemption enablePrevented during lock
SpinNoIrqAtomic + preemption + IRQ disableAtomic + preemption + IRQ enableFully prevented

SMP vs Single-Core Behavior

flowchart TD
subgraph subGraph2["Feature Flag Impact on SpinRaw"]
    SMPEnabled["smp featureenabled?"]
    subgraph subGraph1["Single-Core Path (no smp)"]
        NoLockField["No lock fieldoptimized out"]
        NoAtomics["No atomic operationslock always succeeds"]
        NoSpinning["No spinning behaviorimmediate success"]
        NoOverhead["Zero overheadcompile-time optimization"]
    end
    subgraph subGraph0["Multi-Core Path (smp)"]
        AtomicField["AtomicBool lock fieldin BaseSpinLock"]
        CompareExchange["compare_exchange operationsfor acquisition"]
        RealSpin["Actual spinning behavioron contention"]
        MemoryOrder["Memory orderingconstraints"]
    end
end

AtomicField --> CompareExchange
CompareExchange --> RealSpin
NoAtomics --> NoSpinning
NoLockField --> NoAtomics
NoSpinning --> NoOverhead
RealSpin --> MemoryOrder
SMPEnabled --> AtomicField
SMPEnabled --> NoLockField

Sources: README.md(L12)  src/base.rs

In single-core environments (without the smp feature), SpinRaw becomes a zero-cost abstraction since no actual locking is necessary when preemption and interrupts are properly controlled.

Integration with BaseSpinLock Architecture

SpinRaw leverages the generic BaseSpinLock implementation while providing no additional protection through its NoOp guard parameter.

Architectural Mapping

classDiagram
class SpinRaw {
    <<type alias>>
    
    +new(data: T) SpinRaw~T~
    +lock() SpinRawGuard~T~
    +try_lock() Option~SpinRawGuard~T~~
}

class BaseSpinLock~NoOp_T~ {
    -lock_state: AtomicBool
    +new(data: T) Self
    +lock() BaseSpinLockGuard~NoOp_T~
    +try_lock() Option~BaseSpinLockGuard~NoOp_T~~
    -try_lock_inner() bool
}

class SpinRawGuard {
    <<type alias>>
    
    +deref() &T
    +deref_mut() &mut T
}

class BaseSpinLockGuard~NoOp_T~ {
    -lock: BaseSpinLock~NoOp_T~
    -guard_state: NoOp
    +new() Self
    +deref() &T
    +deref_mut() &mut T
}

class NoOp {
    
    +acquire() Self
    +release()
}

SpinRaw  --|>  BaseSpinLock : type alias
SpinRawGuard  --|>  BaseSpinLockGuard : type alias
BaseSpinLock  -->  BaseSpinLockGuard : creates
BaseSpinLockGuard  -->  NoOp : uses

Sources: src/lib.rs(L33 - L36)  src/base.rs

The SpinRaw type inherits all functionality from BaseSpinLock while the NoOp guard ensures minimal overhead by performing no protection operations during the guard's lifetime.

SpinNoPreempt

Relevant source files

This document describes the SpinNoPreempt spinlock type, which provides kernel-space synchronization by disabling preemption during critical sections. This spinlock offers a balanced approach between performance and safety, suitable for IRQ-disabled contexts where preemption control is necessary but interrupt masking is already handled externally.

For information about the raw spinlock without protection mechanisms, see SpinRaw. For the full-protection spinlock that also disables IRQs, see SpinNoIrq. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.

Type Definition and Core Components

SpinNoPreempt<T> is implemented as a type alias that combines the generic BaseSpinLock with the NoPreempt guard type from the kernel_guard crate. This composition provides preemption-disabled synchronization while maintaining type safety and RAII semantics.

Type Composition Diagram

flowchart TD
SpinNoPreempt["SpinNoPreempt<T>"]
BaseSpinLock["BaseSpinLock<NoPreempt, T>"]
NoPreempt["NoPreempt"]
SpinNoPreemptGuard["SpinNoPreemptGuard<'a, T>"]
BaseSpinLockGuard["BaseSpinLockGuard<'a, NoPreempt, T>"]

BaseSpinLock --> NoPreempt
BaseSpinLock --> SpinNoPreemptGuard
BaseSpinLockGuard --> NoPreempt
SpinNoPreempt --> BaseSpinLock
SpinNoPreemptGuard --> BaseSpinLockGuard

Sources: src/lib.rs(L15 - L18) 

The key components are:

ComponentDefinitionPurpose
SpinNoPreemptBaseSpinLock<NoPreempt, T>Main spinlock type that holds data of typeT
SpinNoPreemptGuard<'a, T>BaseSpinLockGuard<'a, NoPreempt, T>RAII guard providing mutable access to protected data
NoPreemptGuard type fromkernel_guardcrateImplements preemption disable/enable behavior

Sources: src/lib.rs(L15 - L18)  src/lib.rs(L6) 

Safety Requirements and Usage Context

SpinNoPreempt has specific safety requirements that must be understood before use. The spinlock is designed for contexts where IRQ handling is already managed externally, but preemption control is needed for the critical section.

Safety Context Requirements

flowchart TD
subgraph subGraph2["SpinNoPreempt Behavior"]
    AcquireLock["lock() called"]
    DisablePreemption["NoPreempt::acquire()"]
    CriticalExecution["Protected code execution"]
    EnablePreemption["NoPreempt::release()"]
    ReleaseLock["Guard dropped"]
end
subgraph subGraph1["Unsafe Usage Contexts"]
    IRQEnabled["IRQ-Enabled Context"]
    InterruptHandler["Interrupt Handler"]
    UserSpace["User Space"]
end
subgraph subGraph0["Safe Usage Contexts"]
    IRQDisabled["IRQ-Disabled Context"]
    KernelCode["Kernel Code with IRQ Control"]
    CriticalSection["Existing Critical Section"]
end

AcquireLock --> DisablePreemption
CriticalExecution --> EnablePreemption
CriticalSection --> AcquireLock
DisablePreemption --> CriticalExecution
EnablePreemption --> ReleaseLock
IRQDisabled --> AcquireLock
IRQEnabled --> AcquireLock
InterruptHandler --> AcquireLock
KernelCode --> AcquireLock

Sources: src/lib.rs(L10 - L14) 

Key Safety Rules

  1. IRQ Context Requirement: Must be used in local IRQ-disabled context
  2. Interrupt Handler Prohibition: Never use in interrupt handlers
  3. Preemption Assumption: Assumes external IRQ management is already in place

The documentation explicitly states: "It must be used in the local IRQ-disabled context, or never be used in interrupt handlers."

Sources: src/lib.rs(L13 - L14) 

Implementation Architecture

SpinNoPreempt leverages the modular architecture of the kspin crate, where the BaseSpinLock provides the core spinning logic and the NoPreempt guard type defines the protection behavior.

Lock Acquisition Flow


Sources: src/lib.rs(L15)  src/base.rs (referenced via BaseSpinLock usage)

Comparison with Other Spinlock Types

SpinNoPreempt occupies the middle ground in the protection spectrum provided by the kspin crate, offering more protection than SpinRaw but less overhead than SpinNoIrq.

Protection Level Comparison

Spinlock TypePreemption ControlIRQ ControlUsage ContextPerformance
SpinRawNoneNoneIRQ-disabled + preemption-disabledFastest
SpinNoPreemptDisabled during lockMust be externally disabledIRQ-disabled contextsBalanced
SpinNoIrqDisabled during lockDisabled during lockAny contextSafest but slower

Sources: src/lib.rs(L15)  src/lib.rs(L24)  src/lib.rs(L33) 

Guard Type Mapping

flowchart TD
subgraph subGraph2["Protection Behavior"]
    NoneProtection["No protection"]
    PreemptProtection["Preemption disabled"]
    FullProtection["Preemption + IRQ disabled"]
end
subgraph subGraph1["Guard Types (kernel_guard)"]
    NoOp["NoOp"]
    NoPreempt["NoPreempt"]
    NoPreemptIrqSave["NoPreemptIrqSave"]
end
subgraph subGraph0["Spinlock Types"]
    SpinRaw["SpinRaw<T>"]
    SpinNoPreempt["SpinNoPreempt<T>"]
    SpinNoIrq["SpinNoIrq<T>"]
end

NoOp --> NoneProtection
NoPreempt --> PreemptProtection
NoPreemptIrqSave --> FullProtection
SpinNoIrq --> NoPreemptIrqSave
SpinNoPreempt --> NoPreempt
SpinRaw --> NoOp

Sources: src/lib.rs(L6)  src/lib.rs(L15)  src/lib.rs(L24)  src/lib.rs(L33) 

Usage Patterns and Examples

The typical usage pattern for SpinNoPreempt follows the RAII principle, where the guard automatically manages preemption state during its lifetime.

Basic Usage Pattern

From the repository examples, the standard pattern is:

let data = SpinNoPreempt::new(());
let mut guard = data.lock();
/* critical section, preemption are disabled. */
drop(guard);

Sources: README.md(L24 - L27) 

Method Interface

SpinNoPreempt<T> inherits all methods from BaseSpinLock<NoPreempt, T>, providing the standard spinlock interface:

MethodReturn TypePurpose
new(data: T)SelfCreate new spinlock with initial data
lock()SpinNoPreemptGuard<'_, T>Acquire lock, blocking until available
try_lock()Option<SpinNoPreemptGuard<'_, T>>Attempt to acquire lock without blocking

The guard provides:

  • Automatic preemption re-enabling on drop
  • Mutable access to protected data via Deref and DerefMut
  • Lock release when guard goes out of scope

Sources: src/lib.rs(L15 - L18)  src/base.rs (inherited interface)

SpinNoIrq

Relevant source files

SpinNoIrq is the full-protection spinlock implementation in the kspin crate that provides maximum safety by disabling both kernel preemption and local interrupts during lock acquisition and critical sections. This is the safest but most performance-intensive spinlock variant, designed for use in IRQ-enabled contexts where complete isolation is required.

For information about lighter-weight spinlock variants, see SpinRaw and SpinNoPreempt. For comprehensive usage guidelines across all spinlock types, see Usage Guidelines and Safety.

Type Definition and Core Components

SpinNoIrq is implemented as a type alias that specializes the generic BaseSpinLock with the NoPreemptIrqSave guard type from the kernel_guard crate.

flowchart TD
subgraph subGraph0["SpinNoIrq Type System"]
    SpinNoIrq["SpinNoIrq<T>Type alias"]
    BaseSpinLock["BaseSpinLock<NoPreemptIrqSave, T>Generic implementation"]
    SpinNoIrqGuard["SpinNoIrqGuard<'a, T>RAII guard"]
    BaseSpinLockGuard["BaseSpinLockGuard<'a, NoPreemptIrqSave, T>Generic guard"]
    NoPreemptIrqSave["NoPreemptIrqSavekernel_guard protection"]
end

BaseSpinLock --> NoPreemptIrqSave
BaseSpinLockGuard --> NoPreemptIrqSave
SpinNoIrq --> BaseSpinLock
SpinNoIrqGuard --> BaseSpinLockGuard

The core type definitions establish the relationship between the public API and the underlying implementation:

ComponentDefinitionPurpose
SpinNoIrqBaseSpinLock<NoPreemptIrqSave, T>Main spinlock type for data of typeT
SpinNoIrqGuard<'a, T>BaseSpinLockGuard<'a, NoPreemptIrqSave, T>RAII guard providing mutable access to protected data

Sources: src/lib.rs(L24 - L27) 

Protection Mechanism

SpinNoIrq implements a dual-protection mechanism that addresses both concurrency sources in kernel environments: task preemption and interrupt handling.

sequenceDiagram
    participant CallingThread as "Calling Thread"
    participant SpinNoIrq as "SpinNoIrq"
    participant NoPreemptIrqSave as "NoPreemptIrqSave"
    participant KernelScheduler as "Kernel Scheduler"
    participant InterruptController as "Interrupt Controller"

    CallingThread ->> SpinNoIrq: lock()
    SpinNoIrq ->> NoPreemptIrqSave: acquire()
    NoPreemptIrqSave ->> KernelScheduler: disable_preemption()
    NoPreemptIrqSave ->> InterruptController: save_and_disable_irqs()
    Note over CallingThread,InterruptController: Critical Section<br>- No task switches possible<br>- No interrupts delivered<br>- Complete isolation
    SpinNoIrq ->> CallingThread: SpinNoIrqGuard
    CallingThread ->> CallingThread: /* access protected data */
    CallingThread ->> SpinNoIrq: drop(guard)
    SpinNoIrq ->> NoPreemptIrqSave: release()
    NoPreemptIrqSave ->> InterruptController: restore_irqs()
    NoPreemptIrqSave ->> KernelScheduler: enable_preemption()

The protection mechanism operates through the NoPreemptIrqSave guard which:

  1. Disables Preemption: Prevents the kernel scheduler from switching to other tasks
  2. Saves and Disables IRQs: Stores current interrupt state and disables local interrupts
  3. Provides Restoration: Automatically restores the previous state when the guard is dropped

Sources: src/lib.rs(L20 - L23) 

Usage Patterns

SpinNoIrq is designed for scenarios requiring complete protection and can be safely used in any kernel context.

Safe Contexts

flowchart TD
subgraph subGraph1["SpinNoIrq Usage"]
    SpinNoIrqLock["SpinNoIrq::lock()"]
    CriticalSection["Protected Critical Section"]
    AutoRestore["Automatic State Restoration"]
end
subgraph subGraph0["Kernel Contexts Where SpinNoIrq is Safe"]
    IRQEnabled["IRQ-EnabledKernel Code"]
    IRQDisabled["IRQ-DisabledKernel Code"]
    SyscallHandler["System CallHandlers"]
    TimerContext["Timer/SoftirqContext"]
    InitCode["InitializationCode"]
end

CriticalSection --> AutoRestore
IRQDisabled --> SpinNoIrqLock
IRQEnabled --> SpinNoIrqLock
InitCode --> SpinNoIrqLock
SpinNoIrqLock --> CriticalSection
SyscallHandler --> SpinNoIrqLock
TimerContext --> SpinNoIrqLock

Typical Usage Pattern

The standard usage follows the RAII pattern where the guard automatically manages protection state:

OperationEffectAutomatic Behavior
SpinNoIrq::new(data)Creates protected spinlockNone
lock.lock()Acquires lockDisables preemption + IRQs
Guard in scopeAccess to protected dataMaintains protection
drop(guard)Releases lockRestores preemption + IRQ state

Sources: README.md(L29 - L33) 

Safety Guarantees

SpinNoIrq provides the strongest safety guarantees among all spinlock variants in the kspin crate.

Protection Matrix

Threat VectorSpinNoIrq ProtectionMechanism
Task Preemption✅ Complete ProtectionNoPreemptcomponent disables scheduler
Hardware Interrupts✅ Complete ProtectionIrqSavecomponent disables IRQs
Nested Lock Acquisition✅ Deadlock PreventionIRQ disabling prevents interrupt-based nesting
Data Race Conditions✅ Mutual ExclusionAtomic lock state + protection barriers

Context Safety Analysis

flowchart TD
subgraph Reasoning["Reasoning"]
    IRQProtection["IRQ disabling providescomplete isolation"]
    PreemptProtection["Preemption disablingprevents task switches"]
    IRQHandlerIssue["Using in IRQ handleris redundant sinceIRQs already disabled"]
end
subgraph subGraph0["Context Safety for SpinNoIrq"]
    AnyContext["Any Kernel Context"]
    IRQHandler["Interrupt Handler"]
    TaskContext["Task Context"]
    SystemCall["System Call"]
    SafeResult["✅ Safe to Use"]
    UnsafeResult["❌ Not Recommended"]
end

AnyContext --> SafeResult
IRQHandler --> UnsafeResult
SafeResult --> IRQProtection
SafeResult --> PreemptProtection
SystemCall --> SafeResult
TaskContext --> SafeResult
UnsafeResult --> IRQHandlerIssue

Sources: src/lib.rs(L20 - L23) 

Performance Considerations

SpinNoIrq trades performance for safety, making it the most expensive but safest option.

Performance Impact Analysis

AspectImpact LevelDetails
Lock Acquisition OverheadHighIRQ save/restore + preemption control
Critical Section LatencyHighestNo interrupts can be serviced
System ResponsivenessSignificantDelayed interrupt handling
Throughput ImpactModerateDepends on critical section duration

Performance Comparison

flowchart TD
subgraph subGraph1["Trade-off Characteristics"]
    Performance["Performance"]
    Safety["Safety"]
end
subgraph subGraph0["Relative Performance (Lower is Faster)"]
    SpinRaw["SpinRawOverhead: Minimal"]
    SpinNoPreempt["SpinNoPreemptOverhead: Low"]
    SpinNoIrq["SpinNoIrqOverhead: High"]
end

Performance --> SpinNoIrq
Safety --> SpinNoIrq
SpinNoPreempt --> SpinNoIrq
SpinRaw --> SpinNoPreempt

Sources: src/lib.rs(L20 - L27) 

Relationship to Other Spinlock Types

SpinNoIrq sits at the top of the protection hierarchy, providing comprehensive safety at the cost of performance.

Spinlock Hierarchy

flowchart TD
subgraph subGraph1["Usage Context Requirements"]
    ManualControl["Manual IRQ/preemption control required"]
    IRQDisabledContext["Must be in IRQ-disabled context"]
    AnyKernelContext["Can be used in any kernel context"]
end
subgraph subGraph0["kspin Protection Hierarchy"]
    SpinRaw["SpinRaw<T>BaseSpinLock<NoOp, T>No protection"]
    SpinNoPreempt["SpinNoPreempt<T>BaseSpinLock<NoPreempt, T>Preemption disabled"]
    SpinNoIrq["SpinNoIrq<T>BaseSpinLock<NoPreemptIrqSave, T>Preemption + IRQs disabled"]
end

SpinNoIrq --> AnyKernelContext
SpinNoPreempt --> IRQDisabledContext
SpinNoPreempt --> SpinNoIrq
SpinRaw --> ManualControl
SpinRaw --> SpinNoPreempt

Selection Criteria

ScenarioRecommended TypeRationale
General kernel codeSpinNoIrqMaximum safety, acceptable overhead
Performance-critical paths with manual controlSpinRawMinimal overhead when protection already handled
IRQ-disabled contextsSpinNoPreemptBalanced protection without redundant IRQ control
Uncertain context or shared between contextsSpinNoIrqSafe default choice

Sources: src/lib.rs(L10 - L36)  README.md(L16 - L33) 

Usage Guidelines and Safety

Relevant source files

This document provides comprehensive guidelines for safely selecting and using the appropriate spinlock type from the kspin crate. It covers the safety requirements, context restrictions, and performance considerations for each spinlock variant.

For detailed information about the individual spinlock types and their APIs, see SpinRaw, SpinNoPreempt, and SpinNoIrq. For implementation details, see Core Implementation Architecture.

Safety Model Overview

The kspin crate implements a safety model based on execution context and protection levels. Each spinlock type provides different guarantees and has specific usage requirements that must be followed to ensure system correctness.

Protection Level Hierarchy

flowchart TD
A["SpinRaw"]
B["NoOp guard"]
C["SpinNoPreempt"]
D["NoPreempt guard"]
E["SpinNoIrq"]
F["NoPreemptIrqSave guard"]
G["No automatic protection"]
H["Disables preemption"]
I["Disables preemption + IRQs"]
J["Fastest performance"]
K["Balanced performance"]
L["Maximum safety"]
M["Manual context control required"]
N["IRQ-disabled context required"]
O["Any context safe"]

A --> B
B --> G
C --> D
D --> H
E --> F
F --> I
G --> J
G --> M
H --> K
H --> N
I --> L
I --> O

Sources: src/lib.rs(L6)  src/lib.rs(L10 - L36) 

Context Classification

The kernel execution environment is divided into distinct contexts, each with different safety requirements:

Context TypeIRQ StatePreemption StateAllowed Spinlocks
IRQ-enabled contextEnabledEnabledSpinNoIrqonly
IRQ-disabled contextDisabledMay be enabledSpinNoIrq,SpinNoPreempt,SpinRaw
Preemption-disabled contextDisabledDisabledSpinNoIrq,SpinRaw
Interrupt handlerDisabledDisabledSpinNoIrqonly

Sources: src/lib.rs(L10 - L36) 

Spinlock Selection Decision Tree

flowchart TD
Start["Need spinlock protection?"]
Context["What execution context?"]
IRQEnabled["IRQ-enabled context(general kernel code)"]
IRQDisabled["IRQ-disabled context(critical sections)"]
IntHandler["Interrupt handler"]
PreemptDisabled["Preemption-disabled context"]
UseNoIrq["Use SpinNoIrq"]
UseNoIrq2["Use SpinNoIrq(only safe option)"]
Performance["Performance critical?"]
UseRaw["Use SpinRaw(if preemption also disabled)"]
UseNoPreempt["Use SpinNoPreempt"]
UseNoIrq3["Use SpinNoIrq"]
PerfCritical["Need maximum performance?"]
UseRaw2["Use SpinRaw"]
UseNoIrq4["Use SpinNoIrq"]

Context --> IRQDisabled
Context --> IRQEnabled
Context --> IntHandler
Context --> PreemptDisabled
IRQDisabled --> Performance
IRQEnabled --> UseNoIrq
IntHandler --> UseNoIrq2
PerfCritical --> UseNoIrq4
PerfCritical --> UseRaw2
Performance --> UseNoIrq3
Performance --> UseNoPreempt
Performance --> UseRaw
PreemptDisabled --> PerfCritical
Start --> Context

Sources: src/lib.rs(L10 - L36)  README.md(L16 - L33) 

Context-Specific Usage Rules

IRQ-Enabled Context Usage

In IRQ-enabled contexts (typical kernel code), only SpinNoIrq<T> should be used:

// Safe: SpinNoIrq automatically handles IRQ and preemption state
let data = SpinNoIrq::new(shared_resource);
let guard = data.lock(); // IRQs and preemption disabled automatically
// ... critical section ...
drop(guard); // IRQs and preemption restored

Prohibited patterns:

  • Using SpinNoPreempt<T> or SpinRaw<T> in IRQ-enabled contexts
  • Assuming manual IRQ control is sufficient

Sources: src/lib.rs(L20 - L24)  README.md(L29 - L32) 

IRQ-Disabled Context Usage

When IRQs are already disabled, you have multiple options based on performance requirements:

// Option 1: Maximum safety (recommended)
let data = SpinNoIrq::new(resource);

// Option 2: Performance optimization when preemption control is needed
let data = SpinNoPreempt::new(resource);

// Option 3: Maximum performance when preemption is already disabled
let data = SpinRaw::new(resource);

Sources: src/lib.rs(L10 - L18)  src/lib.rs(L29 - L36) 

Interrupt Handler Restrictions

Interrupt handlers have strict requirements due to their execution context:

flowchart TD
IntHandler["Interrupt Handler"]
Requirement1["IRQs already disabled"]
Requirement2["Preemption already disabled"]
Requirement3["Cannot use SpinNoPreempt"]
Requirement4["Cannot use SpinRaw"]
Reason1["Would attempt to disablealready-disabled preemption"]
Reason2["No protection againstnested interrupts"]
Solution["Use SpinNoIrq only"]

IntHandler --> Requirement1
IntHandler --> Requirement2
IntHandler --> Requirement3
IntHandler --> Requirement4
IntHandler --> Solution
Requirement3 --> Reason1
Requirement4 --> Reason2

Sources: src/lib.rs(L13 - L15)  src/lib.rs(L31 - L33) 

Common Safety Violations and Pitfalls

Deadlock Scenarios

Understanding potential deadlock situations is critical for safe spinlock usage:

ScenarioRisk LevelSpinlock Types AffectedMitigation
Nested locking (same lock)HighAll typesUsetry_lock()or redesign
Lock ordering violationHighAll typesEstablish consistent lock hierarchy
IRQ handler accessing same lockCriticalSpinRaw,SpinNoPreemptUseSpinNoIrqexclusively
Long critical sectionsMediumAll typesMinimize critical section duration

Context Mismatches

Common mistakes when choosing the wrong spinlock type:

// WRONG: SpinRaw in IRQ-enabled context
let data = SpinRaw::new(resource);
let guard = data.lock(); // No protection - race condition possible

// WRONG: SpinNoPreempt in interrupt handler  
let data = SpinNoPreempt::new(resource);
let guard = data.lock(); // May not provide sufficient protection

// CORRECT: SpinNoIrq for maximum compatibility
let data = SpinNoIrq::new(resource);
let guard = data.lock(); // Safe in any context

Sources: src/lib.rs(L10 - L36) 

Performance Considerations

Overhead Comparison

The protection mechanisms impose different performance costs:

flowchart TD
SpinRaw["SpinRaw"]
Cost1["Minimal overhead• Atomic operations only• No guard state changes"]
SpinNoPreempt["SpinNoPreempt"]
Cost2["Moderate overhead• Atomic operations• Preemption control"]
SpinNoIrq["SpinNoIrq"]
Cost3["Higher overhead• Atomic operations• Preemption + IRQ control• State save/restore"]
Perf1["Fastest"]
Perf2["Balanced"]
Perf3["Safest"]

Cost1 --> Perf1
Cost2 --> Perf2
Cost3 --> Perf3
SpinNoIrq --> Cost3
SpinNoPreempt --> Cost2
SpinRaw --> Cost1

SMP vs Single-Core Optimization

The smp feature flag dramatically affects performance characteristics:

ConfigurationLock StateAtomic OperationsSpinning Behavior
Single-core (smpdisabled)Optimized outNoneAlways succeeds
Multi-core (smpenabled)AtomicBoolcompare_exchangeActual spinning

Sources: README.md(L12) 

Best Practices Summary

Selection Guidelines

  1. Default choice: Use SpinNoIrq<T> unless performance profiling indicates a bottleneck
  2. Performance-critical paths: Consider SpinNoPreempt<T> in IRQ-disabled contexts
  3. Maximum performance: Use SpinRaw<T> only in preemption-disabled, IRQ-disabled contexts
  4. Interrupt handlers: Always use SpinNoIrq<T>

Implementation Patterns

// Pattern 1: Safe default for shared kernel data
static SHARED_DATA: SpinNoIrq<SharedResource> = SpinNoIrq::new(SharedResource::new());

// Pattern 2: Performance-optimized for known IRQ-disabled context
fn irq_disabled_function() {
    static LOCAL_DATA: SpinNoPreempt<LocalResource> = SpinNoPreempt::new(LocalResource::new());
    let guard = LOCAL_DATA.lock();
    // ... critical section ...
}

// Pattern 3: Maximum performance in controlled environment
fn preempt_and_irq_disabled_function() {
    static FAST_DATA: SpinRaw<FastResource> = SpinRaw::new(FastResource::new());
    let guard = FAST_DATA.lock();
    // ... minimal critical section ...
}

Verification Checklist

  • Spinlock type matches execution context requirements
  • No nested acquisition of the same lock
  • Consistent lock ordering across all code paths
  • Critical sections are minimal in duration
  • Interrupt handlers only use SpinNoIrq<T>
  • Performance requirements justify any use of SpinRaw<T> or SpinNoPreempt<T>

Sources: src/lib.rs(L10 - L36)  README.md(L16 - L33) 

Core Implementation Architecture

Relevant source files

This page provides a detailed analysis of the core implementation that underlies all spinlock types in the kspin crate. It covers the generic BaseSpinLock architecture, the RAII guard system, and the feature-conditional compilation strategy that enables optimization for different target environments.

For information about the specific spinlock types that users interact with, see Spinlock Types and Public API. For detailed implementation analysis of individual components, see BaseSpinLock and BaseSpinLockGuard, BaseGuard Trait System, SMP vs Single-Core Implementation, and Memory Ordering and Atomic Operations.

Architectural Overview

The kspin crate implements a layered architecture where all public spinlock types (SpinRaw, SpinNoPreempt, SpinNoIrq) are type aliases that wrap a single generic implementation: BaseSpinLock<G, T>. This design provides compile-time specialization through the type system while maintaining a unified codebase.

Core Components Diagram

flowchart TD
subgraph subGraph4["External Dependencies"]
    KernelGuard["kernel_guard crate"]
    CoreHint["core::hint::spin_loop()"]
    CoreSync["core::sync::atomic"]
end
subgraph subGraph3["Data Storage"]
    UnsafeCell["UnsafeCell<T>"]
    AtomicBool["AtomicBool (SMP only)"]
end
subgraph subGraph2["Type System Integration"]
    BaseGuard["BaseGuard trait"]
    PhantomData["PhantomData<G>"]
end
subgraph subGraph1["Generic Implementation Layer"]
    BaseSpinLock["BaseSpinLock<G, T>"]
    BaseSpinLockGuard["BaseSpinLockGuard<'a, G, T>"]
end
subgraph subGraph0["Public API Layer"]
    SpinRaw["SpinRaw<T>"]
    SpinNoPreempt["SpinNoPreempt<T>"]
    SpinNoIrq["SpinNoIrq<T>"]
end

AtomicBool --> CoreSync
BaseGuard --> KernelGuard
BaseSpinLock --> AtomicBool
BaseSpinLock --> BaseSpinLockGuard
BaseSpinLock --> CoreHint
BaseSpinLock --> PhantomData
BaseSpinLock --> UnsafeCell
PhantomData --> BaseGuard
SpinNoIrq --> BaseSpinLock
SpinNoPreempt --> BaseSpinLock
SpinRaw --> BaseSpinLock

Sources: src/base.rs(L27 - L32)  src/base.rs(L37 - L43) 

Generic Type Parameterization

The BaseSpinLock<G, T> struct uses two generic parameters that enable compile-time specialization:

ParameterPurposeConstraints
GGuard behavior typeMust implementBaseGuardtrait
TProtected data typeSupports?Sizedfor unsized types

The guard type G determines the protection behavior through the BaseGuard trait, which provides acquire() and release() methods that are called when entering and exiting the critical section.

Type Parameterization Flow

flowchart TD
subgraph subGraph2["Public Type Aliases"]
    SpinRawAlias["SpinRaw<T>"]
    SpinNoPreemptAlias["SpinNoPreempt<T>"]
    SpinNoIrqAlias["SpinNoIrq<T>"]
end
subgraph subGraph1["BaseSpinLock Instantiations"]
    RawLock["BaseSpinLock<NoOp, T>"]
    PreemptLock["BaseSpinLock<NoPreempt, T>"]
    IrqLock["BaseSpinLock<NoPreemptIrqSave, T>"]
end
subgraph subGraph0["Guard Types (kernel_guard)"]
    NoOp["NoOp"]
    NoPreempt["NoPreempt"]
    NoPreemptIrqSave["NoPreemptIrqSave"]
end

IrqLock --> SpinNoIrqAlias
NoOp --> RawLock
NoPreempt --> PreemptLock
NoPreemptIrqSave --> IrqLock
PreemptLock --> SpinNoPreemptAlias
RawLock --> SpinRawAlias

Sources: src/base.rs(L27 - L32)  src/lib.rs

Feature-Conditional Architecture

The implementation uses the smp feature flag to provide dramatically different code paths for single-core versus multi-core environments:

SMP Feature Compilation Strategy


Sources: src/base.rs(L13 - L14)  src/base.rs(L29 - L30)  src/base.rs(L41 - L42)  src/base.rs(L79 - L93)  src/base.rs(L111 - L118)  src/base.rs(L125 - L136) 

Lock Acquisition and Guard Lifecycle

The core operation flow demonstrates how the RAII pattern ensures correct lock management:

Lock Lifecycle Flow

sequenceDiagram
    participant Client as Client
    participant BaseSpinLock as BaseSpinLock
    participant BaseGuard as BaseGuard
    participant AtomicBool as AtomicBool
    participant BaseSpinLockGuard as BaseSpinLockGuard

    Client ->> BaseSpinLock: lock()
    BaseSpinLock ->> BaseGuard: G::acquire()
    Note over BaseGuard: Disable IRQs/preemption
    BaseGuard -->> BaseSpinLock: irq_state
    alt SMP enabled
        BaseSpinLock ->> AtomicBool: compare_exchange_weak(false, true)
    loop While locked
        AtomicBool -->> BaseSpinLock: Err (already locked)
        BaseSpinLock ->> AtomicBool: load(Ordering::Relaxed)
        BaseSpinLock ->> BaseSpinLock: core::hint::spin_loop()
    end
    AtomicBool -->> BaseSpinLock: Ok (acquired)
    else SMP disabled
        Note over BaseSpinLock: Lock always succeeds
    end
    BaseSpinLock -->> Client: BaseSpinLockGuard
    Note over Client: Use protected data via Deref/DerefMut
    Client ->> BaseSpinLockGuard: drop()
    alt SMP enabled
        BaseSpinLockGuard ->> AtomicBool: store(false, Ordering::Release)
    end
    BaseSpinLockGuard ->> BaseGuard: G::release(irq_state)
    Note over BaseGuard: Restore IRQs/preemption

Sources: src/base.rs(L77 - L101)  src/base.rs(L218 - L227) 

Memory Safety and Synchronization

The implementation provides memory safety through several mechanisms:

MechanismImplementationPurpose
UnsafeCellsrc/base.rs31Interior mutability for protected data
RAII Guardsrc/base.rs37-43Automatic lock release on scope exit
Raw pointer in guardsrc/base.rs40Direct data access while lock is held
Atomic operationssrc/base.rs83-85Multi-core synchronization
Memory orderingOrdering::Acquire/ReleaseProper memory barriers

Data Access Safety Model

flowchart TD
subgraph subGraph2["Safety Guarantees"]
    ExclusiveAccess["Exclusive data access"]
    AutoRelease["Automatic lock release"]
    MemoryOrdering["Proper memory ordering"]
end
subgraph subGraph1["BaseSpinLockGuard Structure"]
    DataPtr["data: *mut T"]
    LockRef["lock: &'a AtomicBool"]
    IrqState["irq_state: G::State"]
    PhantomRef["_phantom: &'a PhantomData<G>"]
end
subgraph subGraph0["BaseSpinLock Structure"]
    UnsafeCellField["data: UnsafeCell<T>"]
    AtomicLock["lock: AtomicBool"]
    PhantomG["_phantom: PhantomData<G>"]
end

AtomicLock --> LockRef
AtomicLock --> MemoryOrdering
DataPtr --> ExclusiveAccess
LockRef --> AtomicLock
LockRef --> AutoRelease
PhantomG --> PhantomRef
PhantomRef --> UnsafeCellField
UnsafeCellField --> DataPtr

Sources: src/base.rs(L27 - L32)  src/base.rs(L37 - L43)  src/base.rs(L195 - L210)  src/base.rs(L218 - L227) 

BaseSpinLock and BaseSpinLockGuard

Relevant source files

Purpose and Scope

This document provides detailed technical analysis of the core spinlock implementation in the kspin crate. The BaseSpinLock<G, T> struct and its companion BaseSpinLockGuard<G, T> form the foundational implementation that underlies all public spinlock types in the crate. This generic implementation enables different protection behaviors through type parameterization while providing efficient RAII-based lock management.

For information about the public spinlock APIs that users interact with, see Spinlock Types and Public API. For details about the trait system that enables different protection behaviors, see BaseGuard Trait System.

Architecture Overview

The core implementation consists of two primary components that work together to provide safe, efficient spinlock functionality:

Core Implementation Structure

flowchart TD
subgraph subGraph3["BaseSpinLock Architecture"]
    BaseSpinLock["BaseSpinLock<G, T>Generic spinlock container"]
    BaseSpinLockGuard["BaseSpinLockGuard<G, T>RAII access guard"]
    subgraph subGraph2["Core Operations"]
        AcquireLock["G::acquire() + atomic CAS"]
        ReleaseLock["atomic store + G::release()"]
        DataAccess["Deref/DerefMut traits"]
    end
    subgraph subGraph1["BaseSpinLockGuard Fields"]
        PhantomRef["_phantom: &PhantomData<G>Lifetime-bound guard marker"]
        IrqState["irq_state: G::StateSaved protection state"]
        DataPtr["data: *mut TDirect data access pointer"]
        LockRef["lock: &AtomicBool(SMP only)"]
    end
    subgraph subGraph0["BaseSpinLock Fields"]
        PhantomG["_phantom: PhantomData<G>Zero-cost guard type marker"]
        LockState["lock: AtomicBool(SMP only)"]
        Data["data: UnsafeCell<T>Protected data storage"]
    end
end

AcquireLock --> BaseSpinLockGuard
BaseSpinLock --> AcquireLock
BaseSpinLock --> Data
BaseSpinLock --> LockState
BaseSpinLock --> PhantomG
BaseSpinLockGuard --> DataAccess
BaseSpinLockGuard --> DataPtr
BaseSpinLockGuard --> IrqState
BaseSpinLockGuard --> LockRef
BaseSpinLockGuard --> PhantomRef
BaseSpinLockGuard --> ReleaseLock

Sources: src/base.rs(L27 - L43)  src/base.rs(L49 - L101)  src/base.rs(L218 - L227) 

Method Implementation Map

flowchart TD
subgraph subGraph2["SMP-Conditional Logic"]
    CompareExchange["compare_exchange_weakline 85"]
    SpinLoop["spin_loop()line 90"]
    AtomicStore["store(false)line 224"]
end
subgraph subGraph1["BaseSpinLockGuard Traits"]
    Deref["Deref::deref()line 198"]
    DerefMut["DerefMut::deref_mut()line 206"]
    Drop["Drop::drop()line 222"]
    Debug["Debug::fmt()line 213"]
end
subgraph subGraph0["BaseSpinLock Methods"]
    New["new(data: T)line 52"]
    Lock["lock()line 77"]
    TryLock["try_lock()line 122"]
    IsLocked["is_locked()line 110"]
    ForceUnlock["force_unlock()line 159"]
    GetMut["get_mut()line 170"]
    IntoInner["into_inner()line 63"]
end
BaseSpinLockGuard["BaseSpinLockGuard"]

BaseSpinLockGuard --> Deref
BaseSpinLockGuard --> DerefMut
BaseSpinLockGuard --> Drop
Drop --> AtomicStore
Lock --> CompareExchange
Lock --> SpinLoop
TryLock --> CompareExchange

Sources: src/base.rs(L49 - L175)  src/base.rs(L195 - L227) 

BaseSpinLock Structure Analysis

Field Organization and Purpose

The BaseSpinLock<G, T> struct is carefully designed to minimize memory overhead while supporting both SMP and single-core environments:

FieldTypePurposeConditional
_phantomPhantomDataZero-cost guard type markerAlways present
lockAtomicBoolLock state for atomic operationsSMP feature only
dataUnsafeCellProtected data storageAlways present

The conditional compilation using #[cfg(feature = "smp")] allows the lock state to be completely eliminated in single-core builds, reducing memory overhead and eliminating unnecessary atomic operations.

Sources: src/base.rs(L27 - L32) 

Constructor and Data Access Methods

The new() constructor provides compile-time initialization with zero runtime cost:

// Simplified view of the new() method
pub const fn new(data: T) -> Self {
    Self {
        _phantom: PhantomData,
        data: UnsafeCell::new(data),
        #[cfg(feature = "smp")]
        lock: AtomicBool::new(false),
    }
}

The into_inner() and get_mut() methods provide safe data extraction when exclusive access is statically guaranteed, eliminating the need for runtime locking.

Sources: src/base.rs(L52 - L59)  src/base.rs(L63 - L68)  src/base.rs(L170 - L175) 

BaseSpinLockGuard RAII Implementation

Guard Structure and Lifetime Management

The BaseSpinLockGuard<'a, G, T> implements the RAII pattern to ensure automatic lock release:

flowchart TD
subgraph subGraph2["Guard Lifecycle"]
    Create["Guard Creationlock() or try_lock()"]
    Access["Data AccessDeref/DerefMut"]
    Drop["Automatic CleanupDrop trait"]
    subgraph subGraph1["Drop Steps"]
        AtomicRelease["lock.store(false)(SMP only)"]
        GuardRelease["G::release(irq_state)Restore protection"]
    end
    subgraph subGraph0["Creation Steps"]
        Acquire["G::acquire()Get protection state"]
        CAS["compare_exchange(SMP only)"]
        ConstructGuard["Construct guard withdata pointer & state"]
    end
end

Access --> Drop
Acquire --> CAS
AtomicRelease --> GuardRelease
CAS --> ConstructGuard
ConstructGuard --> Access
Create --> Acquire
Drop --> AtomicRelease

Sources: src/base.rs(L77 - L101)  src/base.rs(L218 - L227) 

Memory Safety Through Type System

The guard uses several mechanisms to ensure memory safety:

  1. Lifetime binding: The 'a lifetime ensures the guard cannot outlive the lock
  2. Raw pointer storage: Direct *mut T access eliminates borrowing conflicts
  3. Phantom reference: &'a PhantomData<G> ties the guard lifetime to the lock
  4. Exclusive data access: The guard provides exclusive access through Deref/DerefMut

Sources: src/base.rs(L37 - L43)  src/base.rs(L195 - L210) 

Core Locking Operations

Lock Acquisition Algorithm

The lock() method implements a two-phase spinning algorithm optimized for different contention scenarios:


The algorithm uses compare_exchange_weak for potential efficiency gains on some architectures, falling back to spinning until the lock appears available before retrying.

Sources: src/base.rs(L77 - L101) 

Try-Lock Implementation

The try_lock() method provides non-blocking acquisition using a strong compare-exchange operation:

flowchart TD
subgraph subGraph1["Single-Core Path"]
    AlwaysSuccess["Always succeedsis_unlocked = true"]
    CreateGuard["Create guard"]
end
subgraph subGraph0["SMP Path"]
    StrongCAS["compare_exchange(strong)"]
    Success["Create guard"]
    Failure["Return None"]
end
TryLock["try_lock()"]
Acquire["G::acquire()"]

Acquire --> AlwaysSuccess
Acquire --> StrongCAS
AlwaysSuccess --> CreateGuard
StrongCAS --> Failure
StrongCAS --> Success
TryLock --> Acquire

The use of strong compare-exchange is specifically chosen over weak to avoid unnecessary retry loops in the try-lock scenario.

Sources: src/base.rs(L122 - L149) 

SMP vs Single-Core Optimization

Conditional Compilation Strategy

The crate uses cfg_if! macros to provide dramatically different implementations based on the smp feature:

OperationSMP ImplementationSingle-Core Implementation
Lock stateAtomicBoolfieldNo field (zero bytes)
lock()Atomic CAS + spinningImmediate success
try_lock()Atomic CASAlways succeeds
is_locked()load(Relaxed)Always returnsfalse
Guard dropstore(false, Release)No atomic operation

This approach allows single-core builds to have zero overhead for lock operations while maintaining identical APIs.

Sources: src/base.rs(L13 - L14)  src/base.rs(L29 - L30)  src/base.rs(L111 - L118)  src/base.rs(L125 - L136) 

Memory Ordering Semantics

The SMP implementation uses carefully chosen memory orderings:

  • Acquire ordering on successful CAS: Ensures all subsequent reads see writes from the previous critical section
  • Relaxed ordering on failed CAS: No synchronization needed for failed attempts
  • Release ordering on unlock: Ensures all writes in critical section are visible before lock release
  • Relaxed ordering for is_locked(): Only a hint, no synchronization guarantees

Sources: src/base.rs(L85)  src/base.rs(L131)  src/base.rs(L224)  src/base.rs(L113) 

Thread Safety and Sync Implementation

Safety Trait Implementations

The crate provides the same safety guarantees as std::sync::Mutex:

unsafe impl<G: BaseGuard, T: ?Sized + Send> Sync for BaseSpinLock<G, T> {}
unsafe impl<G: BaseGuard, T: ?Sized + Send> Send for BaseSpinLock<G, T> {}

These implementations are safe because:

  • The lock ensures exclusive access to the data
  • The guard system prevents concurrent access
  • The BaseGuard trait handles interrupt/preemption safety

Sources: src/base.rs(L46 - L47) 

Debug and Display Support

Both BaseSpinLock and BaseSpinLockGuard implement Debug to aid in development:

  • The lock's debug implementation attempts try_lock() to safely display data
  • The guard's debug implementation directly displays the protected data
  • Locked spinlocks display "SpinLock { <locked> }" to avoid blocking

Sources: src/base.rs(L184 - L193)  src/base.rs(L212 - L216) 

BaseGuard Trait System

Relevant source files

The BaseGuard trait system is the core abstraction that enables different protection behaviors in kspin spinlocks through type parameterization. This system allows BaseSpinLock<G, T> to provide varying levels of interrupt and preemption protection by substituting different guard implementations at compile time.

For information about the specific spinlock types that use these guards, see Spinlock Types and Public API. For details about the underlying BaseSpinLock implementation, see BaseSpinLock and BaseSpinLockGuard.

BaseGuard Trait Interface

The BaseGuard trait is defined in the external kernel_guard crate and provides a standardized interface for managing system-level protection states during critical sections.

classDiagram
class BaseGuard {
    <<trait>>
    +type State
    +acquire() State
    +release(state: State)
}

class NoOp {
    
    +type State =()
    +acquire()()
    +release(state:())
}

class NoPreempt {
    +type State = PreemptState
    +acquire() PreemptState
    +release(state: PreemptState)
}

class NoPreemptIrqSave {
    +type State = IrqState
    +acquire() IrqState
    +release(state: IrqState)
}

BaseGuard  --|>  NoOp
BaseGuard  --|>  NoPreempt
NoPreemptIrqSave  --|>  NoPreempt

BaseGuard Trait Interface

Sources: src/base.rs(L16)  src/lib.rs(L6) 

Integration with BaseSpinLock

The BaseSpinLock<G, T> struct uses the BaseGuard trait as a type parameter to customize protection behavior without runtime overhead. The guard type G determines what system-level protections are applied during lock acquisition and release.

flowchart TD
subgraph subGraph1["Lock Operations"]
    Lock["lock()"]
    TryLock["try_lock()"]
    Drop["Drop::drop()"]
end
subgraph subGraph0["Guard Integration Points"]
    Acquire["G::acquire()"]
    Release["G::release(state)"]
    StateStorage["irq_state: G::State"]
end
BaseSpinLock["BaseSpinLock<G: BaseGuard, T>"]
BaseSpinLockGuard["BaseSpinLockGuard<G, T>"]

Acquire --> StateStorage
BaseSpinLock --> Lock
BaseSpinLock --> TryLock
BaseSpinLockGuard --> Drop
Drop --> Release
Lock --> Acquire
StateStorage --> BaseSpinLockGuard
TryLock --> Acquire

BaseGuard Integration Flow

The integration occurs at three key points in src/base.rs:

  1. State acquisition during lock operations src/base.rs(L78)  src/base.rs(L123) 
  2. State storage in the guard struct src/base.rs(L39) 
  3. State release during guard destruction src/base.rs(L225) 

Sources: src/base.rs(L27)  src/base.rs(L37 - L43)  src/base.rs(L77 - L101)  src/base.rs(L218 - L227) 

Concrete Guard Implementations

The kspin crate uses three specific implementations of BaseGuard from the kernel_guard crate, each providing different levels of system protection.

Guard TypeProtection LevelState TypeUse Context
NoOpNone()IRQ-disabled, preemption-disabled
NoPreemptDisable preemptionPreemptStateIRQ-disabled contexts
NoPreemptIrqSaveDisable preemption + IRQsIrqStateAny context
flowchart TD
subgraph subGraph1["Spinlock Types"]
    SpinRaw["SpinRaw<T>"]
    SpinNoPreempt["SpinNoPreempt<T>"]
    SpinNoIrq["SpinNoIrq<T>"]
end
subgraph subGraph0["Protection Levels"]
    NoOp["NoOpRaw Protection"]
    NoPreempt["NoPreemptPreemption Protection"]
    NoPreemptIrqSave["NoPreemptIrqSaveFull Protection"]
end

NoOp --> SpinRaw
NoPreempt --> SpinNoPreempt
NoPreemptIrqSave --> SpinNoIrq

Guard Type to Spinlock Type Mapping

Sources: src/lib.rs(L6)  src/lib.rs(L15)  src/lib.rs(L24)  src/lib.rs(L33) 

Type-Driven Protection Behavior

The BaseGuard trait system enables compile-time selection of protection behavior through Rust's type system. Each guard implementation provides different acquire() and release() semantics without requiring runtime branching.

sequenceDiagram
    participant Client as Client
    participant BaseSpinLock as BaseSpinLock
    participant GBaseGuard as "G: BaseGuard"
    participant KernelSubsystems as "Kernel Subsystems"

    Client ->> BaseSpinLock: lock()
    BaseSpinLock ->> GBaseGuard: G::acquire()
    GBaseGuard ->> KernelSubsystems: Disable preemption/IRQs
    KernelSubsystems -->> GBaseGuard: Return saved state
    GBaseGuard -->> BaseSpinLock: G::State
    BaseSpinLock ->> BaseSpinLock: Acquire atomic lock
    BaseSpinLock -->> Client: BaseSpinLockGuard
    Note over Client: Use protected data
    Client ->> BaseSpinLock: Drop guard
    BaseSpinLock ->> BaseSpinLock: Release atomic lock
    BaseSpinLock ->> GBaseGuard: G::release(state)
    GBaseGuard ->> KernelSubsystems: Restore preemption/IRQs
    KernelSubsystems -->> GBaseGuard: Complete

Protection State Management Sequence

The type-driven approach ensures that:

  • NoOp: acquire() and release() are no-ops, providing zero overhead
  • NoPreempt: Manages preemption state to prevent task switching
  • NoPreemptIrqSave: Manages both preemption and interrupt state for maximum protection

Sources: src/base.rs(L77 - L79)  src/base.rs(L122 - L124)  src/base.rs(L222 - L225) 

State Management in Guard Lifecycle

The BaseSpinLockGuard stores the protection state returned by G::acquire() and ensures proper cleanup through Rust's RAII pattern. This guarantees that system protection state is always restored, even in the presence of panics.


Guard State Lifecycle

The state management implementation in BaseSpinLockGuard:

This design ensures that protection state is never leaked and that the system always returns to its original state when the critical section ends.

Sources: src/base.rs(L37 - L43)  src/base.rs(L94 - L100)  src/base.rs(L138 - L145)  src/base.rs(L218 - L227) 

SMP vs Single-Core Implementation

Relevant source files

Purpose and Scope

This document explains how the kspin crate uses feature flags to provide dramatically different implementations for multi-core (SMP) and single-core environments. The smp feature flag enables compile-time optimization that completely eliminates atomic operations and lock state when they are unnecessary in single-core systems.

For details about the underlying BaseSpinLock structure, see BaseSpinLock and BaseSpinLockGuard. For information about how different guard types interact with both implementations, see BaseGuard Trait System. For technical details about atomic operations used in SMP mode, see Memory Ordering and Atomic Operations.

Feature Flag Architecture

The kspin crate uses conditional compilation through the smp feature flag to generate entirely different code paths for multi-core and single-core environments. This approach enables zero-cost abstractions where single-core systems pay no performance penalty for multi-core synchronization primitives.

Conditional Compilation Flow

flowchart TD
subgraph subGraph1["Single-Core Implementation"]
    SingleStruct["BaseSpinLock {_phantom: PhantomData"]
    SingleGuard["BaseSpinLockGuard {_phantom: &PhantomData"]
    SingleOps["No Atomic Operations:• lock() always succeeds• try_lock() always succeeds• is_locked() always false"]
end
subgraph subGraph0["SMP Implementation"]
    SMPStruct["BaseSpinLock {_phantom: PhantomData"]
    SMPGuard["BaseSpinLockGuard {_phantom: &PhantomData"]
    SMPOps["Atomic Operations:• compare_exchange_weak• load/store with ordering• spin_loop hints"]
end
FeatureFlag["smp feature flag"]
SMPBuild["SMP Build Target"]
SingleBuild["Single-Core Build Target"]

FeatureFlag --> SMPBuild
FeatureFlag --> SingleBuild
SMPBuild --> SMPGuard
SMPBuild --> SMPOps
SMPBuild --> SMPStruct
SingleBuild --> SingleGuard
SingleBuild --> SingleOps
SingleBuild --> SingleStruct

Sources: Cargo.toml(L14 - L16)  src/base.rs(L13 - L14)  src/base.rs(L29 - L31)  src/base.rs(L41 - L43) 

SMP Implementation Details

In SMP environments, the BaseSpinLock maintains actual lock state using atomic operations to coordinate between multiple CPU cores. The implementation provides true mutual exclusion through hardware-level atomic instructions.

SMP Lock Structure and Operations

flowchart TD
subgraph subGraph2["Memory Ordering"]
    AcquireOrder["Ordering::Acquire on success"]
    RelaxedOrder["Ordering::Relaxed on failure"]
    ReleaseOrder["Ordering::Release on unlock"]
end
subgraph subGraph1["Lock Acquisition Process"]
    AcquireGuard["G::acquire()"]
    CompareExchange["compare_exchange_weak(false, true)"]
    SpinLoop["spin_loop() while locked"]
    CreateGuard["BaseSpinLockGuard creation"]
end
subgraph subGraph0["BaseSpinLock SMP Structure"]
    SMPLock["BaseSpinLock"]
    Phantom["_phantom: PhantomData"]
    AtomicLock["lock: AtomicBool"]
    Data["data: UnsafeCell"]
end

AcquireGuard --> CompareExchange
AtomicLock --> CompareExchange
CompareExchange --> AcquireOrder
CompareExchange --> CreateGuard
CompareExchange --> RelaxedOrder
CompareExchange --> SpinLoop
CreateGuard --> ReleaseOrder
SMPLock --> AtomicLock
SMPLock --> Data
SMPLock --> Phantom
SpinLoop --> CompareExchange

The SMP implementation uses a two-phase locking strategy:

  1. Guard Acquisition: First acquires the protection guard (disabling preemption/IRQs)
  2. Atomic Lock: Then attempts to acquire the atomic lock using compare-and-swap operations

Key SMP Code Paths:

Sources: src/base.rs(L27 - L32)  src/base.rs(L77 - L101)  src/base.rs(L122 - L149)  src/base.rs(L159 - L162) 

Single-Core Implementation Details

In single-core environments, the BaseSpinLock completely eliminates the atomic lock state. Since only one CPU core exists, proper guard acquisition (disabling preemption/IRQs) provides sufficient mutual exclusion without any atomic operations.

Single-Core Optimization Strategy

flowchart TD
subgraph subGraph2["Eliminated Operations"]
    NoAtomic["❌ No AtomicBool field"]
    NoCompareExchange["❌ No compare_exchange"]
    NoSpinning["❌ No spinning loops"]
    NoMemoryOrdering["❌ No memory ordering"]
    AcquireGuard2["G::acquire()"]
    SingleLock["BaseSpinLock"]
end
subgraph subGraph0["BaseSpinLock Single-Core Structure"]
    NoCompareExchange["❌ No compare_exchange"]
    SingleLock["BaseSpinLock"]
    Data2["data: UnsafeCell"]
    subgraph subGraph1["Simplified Lock Process"]
        NoAtomic["❌ No AtomicBool field"]
        AcquireGuard2["G::acquire()"]
        DirectAccess["Direct data access"]
        Phantom2["_phantom: PhantomData"]
    end
end

AcquireGuard2 --> DirectAccess
SingleLock --> Data2
SingleLock --> Phantom2

Single-Core Behavior:

  • lock() always succeeds immediately after guard acquisition
  • try_lock() always returns Some(guard)
  • is_locked() always returns false
  • force_unlock() performs no atomic operations

Key Single-Core Code Paths:

Sources: src/base.rs(L25 - L26)  src/base.rs(L133 - L135)  src/base.rs(L114 - L116) 

Compile-Time Optimization Benefits

The feature flag approach provides significant performance and size benefits for single-core targets by eliminating unnecessary code at compile time.

Performance Comparison

OperationSMP ImplementationSingle-Core Implementation
Lock AcquisitionGuard + Atomic CAS loopGuard only
Try LockGuard + Atomic CASGuard only
Lock CheckAtomic loadConstantfalse
UnlockAtomic store + Guard releaseGuard release only
Memory Usage+1AtomicBoolper lockNo additional fields
Code SizeFull atomic operation codegenOptimized away

Optimization Mechanisms


Sources: src/base.rs(L111 - L117)  src/base.rs(L125 - L136)  Cargo.toml(L14 - L16) 

Code Generation Differences

The conditional compilation results in fundamentally different assembly code generation for the two target environments.

Structure Layout Differences

flowchart TD
subgraph subGraph0["SMP Memory Layout"]
    SMPData["data: UnsafeCell(sizeof T bytes)"]
    subgraph subGraph2["Guard Layout Differences"]
        SMPGuardLayout["SMP Guard:• phantom reference• irq_state• data pointer• lock reference"]
        SingleGuardLayout["Single-Core Guard:• phantom reference• irq_state• data pointer"]
        SingleStruct2["BaseSpinLock"]
        SinglePhantom["_phantom: PhantomData"]
        SingleData["data: UnsafeCell(sizeof T bytes)"]
        SMPStruct2["BaseSpinLock"]
        SMPPhantom["_phantom: PhantomData"]
        SMPAtomic["lock: AtomicBool(1 byte + padding)"]
    end
    subgraph subGraph1["Single-Core Memory Layout"]
        SMPGuardLayout["SMP Guard:• phantom reference• irq_state• data pointer• lock reference"]
        SingleGuardLayout["Single-Core Guard:• phantom reference• irq_state• data pointer"]
        SingleStruct2["BaseSpinLock"]
        SinglePhantom["_phantom: PhantomData"]
        SingleData["data: UnsafeCell(sizeof T bytes)"]
        SMPStruct2["BaseSpinLock"]
        SMPPhantom["_phantom: PhantomData"]
        SMPAtomic["lock: AtomicBool(1 byte + padding)"]
    end
end

SMPStruct2 --> SMPAtomic
SMPStruct2 --> SMPData
SMPStruct2 --> SMPPhantom
SingleStruct2 --> SingleData
SingleStruct2 --> SinglePhantom

Function Implementation Differences

The same function signatures produce completely different implementations:

This design ensures that single-core embedded systems receive highly optimized code while SMP systems get full multi-core safety guarantees.

Sources: src/base.rs(L27 - L32)  src/base.rs(L37 - L43)  src/base.rs(L52 - L59)  src/base.rs(L77 - L149)  src/base.rs(L218 - L226) 

Memory Ordering and Atomic Operations

Relevant source files

This document covers the atomic operations and memory ordering semantics used in the kspin crate's spinlock implementation. It details how the BaseSpinLock uses platform-specific atomic primitives to ensure thread safety in multi-core environments, and how these operations are optimized away for single-core systems.

For information about the overall spinlock architecture and guard types, see Core Implementation Architecture. For details about the SMP feature flag system, see SMP vs Single-Core Implementation.

Conditional Atomic Operations

The kspin crate uses conditional compilation to include atomic operations only when targeting multi-core systems. The smp feature flag controls whether atomic synchronization primitives are compiled into the final binary.

flowchart TD
subgraph subGraph2["Lock Operations"]
    AtomicOps["compare_exchange_weakcompare_exchangeload/store operations"]
    NoOps["Always succeedsNo atomic operationsOptimized away"]
end
subgraph subGraph1["Data Structures"]
    AtomicLock["lock: AtomicBool"]
    NoLockField["No lock field"]
end
subgraph subGraph0["Compilation Paths"]
    SMPEnabled["SMP Feature Enabled"]
    SMPDisabled["SMP Feature Disabled"]
end

AtomicLock --> AtomicOps
NoLockField --> NoOps
SMPDisabled --> NoLockField
SMPEnabled --> AtomicLock

The BaseSpinLock struct conditionally includes an AtomicBool field based on the smp feature:

Compilation ModeLock FieldBehavior
SMP enabledlock: AtomicBoolFull atomic synchronization
SMP disabledNo lock fieldAll lock operations succeed immediately

Sources: src/base.rs(L13 - L14)  src/base.rs(L29 - L30)  src/base.rs(L111 - L117) 

Memory Ordering Semantics

The spinlock implementation uses three specific memory orderings to ensure correct synchronization semantics while minimizing performance overhead:

flowchart TD
subgraph Operations["Operations"]
    CompareExchange["compare_exchange(_weak)Acquire + Relaxed"]
    Store["store(false)Release"]
    Load["load()Relaxed"]
end
subgraph subGraph0["Memory Ordering Types"]
    Acquire["Ordering::AcquireLock acquisition"]
    Release["Ordering::ReleaseLock release"]
    Relaxed["Ordering::RelaxedStatus checks"]
end

Acquire --> CompareExchange
Relaxed --> CompareExchange
Relaxed --> Load
Release --> Store

Memory Ordering Usage Patterns

OperationSuccess OrderingFailure OrderingPurpose
compare_exchange_weakAcquireRelaxedLock acquisition with retry
compare_exchangeAcquireRelaxedSingle attempt lock acquisition
store(false)ReleaseN/ALock release
load()RelaxedN/ANon-blocking status check

The Acquire ordering on successful lock acquisition ensures that all subsequent reads and writes cannot be reordered before the lock acquisition. The Release ordering on lock release ensures that all previous reads and writes complete before the lock is released.

Sources: src/base.rs(L85)  src/base.rs(L131)  src/base.rs(L161)  src/base.rs(L224)  src/base.rs(L113) 

Atomic Operation Flow

The spinlock uses a two-phase approach for lock acquisition: an optimistic compare-and-swap followed by a passive wait loop.

flowchart TD
Start["lock() called"]
AcquireGuard["G::acquire()"]
TryLock["compare_exchange_weak(false, true)"]
CheckResult["Success?"]
CreateGuard["Create BaseSpinLockGuard"]
CheckLocked["is_locked()"]
SpinLoop["core::hint::spin_loop()"]
StillLocked["Still locked?"]

AcquireGuard --> TryLock
CheckLocked --> StillLocked
CheckResult --> CheckLocked
CheckResult --> CreateGuard
SpinLoop --> CheckLocked
Start --> AcquireGuard
StillLocked --> SpinLoop
StillLocked --> TryLock
TryLock --> CheckResult

Lock Acquisition Pattern

The lock() method implements an efficient two-stage spinning strategy:

  1. Active spinning: Attempts to acquire the lock using compare_exchange_weak
  2. Passive waiting: When acquisition fails, enters a read-only spin loop checking is_locked()
  3. CPU optimization: Uses core::hint::spin_loop() to signal the processor during busy waiting

Sources: src/base.rs(L77 - L101)  src/base.rs(L83 - L92)  src/base.rs(L89 - L91) 

Try-Lock Pattern

The try_lock() method uses a single-shot approach with compare_exchange (strong semantics) rather than the weak variant used in the spinning loop:

flowchart TD
TryLock["try_lock()"]
AcquireGuard["G::acquire()"]
CompareExchange["compare_exchange(false, true)"]
CheckResult["Success?"]
ReturnSome["Some(guard)"]
ReturnNone["None"]

AcquireGuard --> CompareExchange
CheckResult --> ReturnNone
CheckResult --> ReturnSome
CompareExchange --> CheckResult
TryLock --> AcquireGuard

The strong compare-exchange is used because there's no retry loop, making the single operation more likely to succeed on architectures where weak operations can fail spuriously.

Sources: src/base.rs(L122 - L149)  src/base.rs(L129 - L132) 

Lock Release Mechanism

Lock release occurs automatically through the RAII Drop implementation of BaseSpinLockGuard. The release process ensures proper memory ordering and guard state cleanup:

sequenceDiagram
    participant BaseSpinLockGuard as "BaseSpinLockGuard"
    participant AtomicBoollock as "AtomicBool lock"
    participant GBaseGuard as "G: BaseGuard"

    Note over BaseSpinLockGuard: Guard goes out of scope
    BaseSpinLockGuard ->> AtomicBoollock: store(false, Ordering::Release)
    BaseSpinLockGuard ->> GBaseGuard: G::release(irq_state)
    Note over BaseSpinLockGuard: Guard destroyed

The release ordering ensures that all memory operations performed while holding the lock are visible to other threads before the lock becomes available.

Sources: src/base.rs(L218 - L227)  src/base.rs(L224)  src/base.rs(L225) 

Performance Optimizations

CPU Spin Loop Hints

The implementation uses core::hint::spin_loop() to provide architecture-specific optimizations during busy waiting:

  • x86/x86_64: Translates to the PAUSE instruction, reducing power consumption
  • ARM: May use YIELD or similar instructions
  • RISC-V: Architecture-specific power management hints

Weak vs Strong Compare-Exchange

The spinlock strategically chooses between weak and strong compare-exchange operations:

ContextOperationRationale
Spinning loopcompare_exchange_weakAcceptable spurious failures in retry context
Single attemptcompare_exchangeMust succeed if possible, no retry mechanism

Sources: src/base.rs(L85)  src/base.rs(L131)  src/base.rs(L127 - L128) 

Single-Core Optimization

When compiled without the smp feature, all atomic operations are eliminated:

flowchart TD
subgraph Benefits["Benefits"]
    ZeroCost["Zero-cost abstraction"]
    NoMemoryBarriers["No memory barriers"]
    OptimizedBinary["Smaller binary size"]
end
subgraph subGraph0["SMP Disabled Path"]
    LockCall["lock()"]
    GuardAcquire["G::acquire()"]
    CreateGuard["Create guard immediately"]
    NoAtomics["No atomic operations"]
end

CreateGuard --> NoAtomics
GuardAcquire --> CreateGuard
LockCall --> GuardAcquire
NoAtomics --> NoMemoryBarriers
NoAtomics --> OptimizedBinary
NoAtomics --> ZeroCost

This optimization is particularly important for embedded systems where multi-core synchronization overhead would be unnecessary.

Sources: src/base.rs(L79 - L93)  src/base.rs(L126 - L136)  src/base.rs(L160 - L161)  src/base.rs(L223 - L224) 

Development and Building

Relevant source files

This document provides comprehensive information for developers who want to build, test, or contribute to the kspin crate. It covers the build system configuration, feature flag usage, continuous integration pipeline, and development environment setup. For information about the actual spinlock APIs and usage patterns, see Spinlock Types and Public API. For details about the internal implementation architecture, see Core Implementation Architecture.

Build System Overview

The kspin crate uses Cargo as its primary build system with support for multiple target platforms and optional feature flags. The build configuration is centralized in the project's Cargo.toml file, which defines dependencies, metadata, and feature gates that control compilation behavior.

Package Configuration

The crate is configured as a library package targeting kernel-space environments with no-std compatibility. The package metadata includes licensing under multiple schemes (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) and categorization for operating system and no-std use cases Cargo.toml(L1 - L13) 

Target Platform Support

The build system supports multiple target architectures commonly used in kernel and embedded development:

TargetArchitectureUse Case
x86_64-unknown-linux-gnux86-64Hosted testing environment
x86_64-unknown-nonex86-64Bare metal/kernel
riscv64gc-unknown-none-elfRISC-V 64-bitEmbedded/kernel
aarch64-unknown-none-softfloatARM64Embedded/kernel

Build System Architecture

flowchart TD
subgraph subGraph0["Build Targets"]
    x86Linux["x86_64-unknown-linux-gnu"]
    x86None["x86_64-unknown-none"]
    RiscV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
end
CargoToml["Cargo.toml"]
Dependencies["Dependencies"]
Features["Feature Flags"]
Metadata["Package Metadata"]
KernelGuard["kernel_guard: 0.1"]
CfgIf["cfg-if: 1.0"]
SMP["smp feature"]
Default["default = []"]
Name["name: kspin"]
Version["version: 0.1.0"]
License["Triple license"]
MultiCore["Multi-core compilation"]
SingleCore["Single-core compilation"]

CargoToml --> ARM
CargoToml --> Dependencies
CargoToml --> Features
CargoToml --> Metadata
CargoToml --> RiscV
CargoToml --> x86Linux
CargoToml --> x86None
Default --> SingleCore
Dependencies --> CfgIf
Dependencies --> KernelGuard
Features --> Default
Features --> SMP
Metadata --> License
Metadata --> Name
Metadata --> Version
SMP --> MultiCore

Sources: Cargo.toml(L1 - L22)  .github/workflows/ci.yml(L12) 

Feature Flag System

The kspin crate uses Cargo feature flags to enable compile-time optimization and conditional compilation based on the target environment. The primary feature flag is smp, which controls whether the spinlock implementation includes multi-core synchronization primitives.

Feature Configuration

The feature system is defined in the [features] section of Cargo.toml:

  • smp: Enables multi-core environment support with atomic operations
  • default: Empty default feature set for maximum compatibility

When the smp feature is disabled, the implementation optimizes away atomic operations and lock state for single-core environments Cargo.toml(L14 - L17) 

Feature Flag Compilation Flow

flowchart TD
subgraph subGraph2["cfg-if Usage"]
    SMPCheck["smp feature enabled?"]
    CfgIf["cfg-if crate"]
end
subgraph subGraph1["Single-core Build Path"]
    SingleBuild["Single-core Build"]
    NoAtomic["No atomic operations"]
    NoLockState["No lock state field"]
    OptimizedAway["Lock always succeeds"]
end
subgraph subGraph0["SMP Build Path"]
    SMPBuild["SMP Build"]
    AtomicOps["Include AtomicBool"]
    CompareExchange["compare_exchange operations"]
    MemoryOrdering["Memory ordering constraints"]
end
FeatureInput["Feature Selection"]
FinalBinary["Final Binary"]

AtomicOps --> CompareExchange
CfgIf --> SMPCheck
CompareExchange --> MemoryOrdering
FeatureInput --> SMPCheck
MemoryOrdering --> FinalBinary
NoAtomic --> NoLockState
NoLockState --> OptimizedAway
OptimizedAway --> FinalBinary
SMPBuild --> AtomicOps
SMPCheck --> SMPBuild
SMPCheck --> SingleBuild
SingleBuild --> NoAtomic

Sources: Cargo.toml(L14 - L17)  Cargo.toml(L20 - L21) 

Continuous Integration Pipeline

The project uses GitHub Actions for automated testing, building, and documentation deployment. The CI pipeline is defined in .github/workflows/ci.yml and consists of two main jobs: ci for code validation and doc for documentation generation.

CI Job Matrix

The ci job uses a matrix strategy to test across multiple dimensions:

Matrix DimensionValues
Rust Toolchainnightly
Target Platforms4 platforms (see table above)
Feature CombinationsEach feature individually viacargo-hack

CI Pipeline Stages

CI/CD Pipeline Architecture

flowchart TD
subgraph subGraph2["Documentation Job"]
    DocJob["doc job"]
    DocCheckout["actions/checkout@v4"]
    DocRust["dtolnay/rust-toolchain@nightly"]
    CargoDoc["cargo doc --no-deps --all-features"]
    IndexHTML["Generate index.html redirect"]
    GitHubPages["Deploy to gh-pages"]
end
subgraph subGraph1["CI Steps"]
    Checkout["actions/checkout@v4"]
    InstallHack["install cargo-hack"]
    InstallRust["dtolnay/rust-toolchain@nightly"]
    Components["rust-src, clippy, rustfmt"]
    RustVersion["Check rust version"]
    Format["cargo fmt --check"]
    Clippy["cargo hack clippy --each-feature"]
    Build["cargo hack build --each-feature"]
    Test["cargo hack test --each-feature"]
end
subgraph subGraph0["CI Job Matrix"]
    CIJob["ci job"]
    Matrix["Matrix Strategy"]
    Nightly["rust-toolchain: nightly"]
    Targets["4 target platforms"]
end
Trigger["Push/PR Event"]
Success["CI Success"]
DocSuccess["Documentation Published"]

Build --> Test
CIJob --> Matrix
CargoDoc --> IndexHTML
Checkout --> InstallHack
Clippy --> Build
Components --> RustVersion
DocCheckout --> DocRust
DocJob --> DocCheckout
DocRust --> CargoDoc
Format --> Clippy
GitHubPages --> DocSuccess
IndexHTML --> GitHubPages
InstallHack --> InstallRust
InstallRust --> Components
Matrix --> Checkout
Matrix --> Nightly
Matrix --> Targets
RustVersion --> Format
Test --> Success
Trigger --> CIJob
Trigger --> DocJob

Sources: .github/workflows/ci.yml(L1 - L57) 

Code Quality Checks

The CI pipeline enforces code quality through multiple automated checks:

  1. Code Formatting: cargo fmt --all -- --check ensures consistent code style .github/workflows/ci.yml(L24) 
  2. Linting: cargo hack clippy --target ${{ matrix.targets }} --each-feature -- -D warnings catches potential issues .github/workflows/ci.yml(L26) 
  3. Building: cargo hack build --target ${{ matrix.targets }} --each-feature validates compilation .github/workflows/ci.yml(L28) 
  4. Testing: cargo hack test --target ${{ matrix.targets }} --each-feature runs unit tests (Linux only) .github/workflows/ci.yml(L30 - L31) 

Documentation Deployment

The doc job automatically builds and deploys documentation to GitHub Pages when changes are pushed to the default branch. The documentation is built with strict settings that treat broken links and missing documentation as errors .github/workflows/ci.yml(L41) 

Development Environment Setup

To contribute to the kspin crate, developers need to set up a Rust development environment with specific toolchain components and target support.

Required Toolchain Components

The development environment requires the Rust nightly toolchain with the following components:

  • rust-src: Source code for cross-compilation
  • clippy: Linting tool for code analysis
  • rustfmt: Code formatting tool

Target Installation

Install the required compilation targets:

rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none  
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat

Development Tools

Install cargo-hack for feature flag testing:

cargo install cargo-hack

This tool enables testing with each feature flag combination individually, which is essential for ensuring the crate works correctly across different configurations .github/workflows/ci.yml(L15) 

Local Development Workflow

Development Workflow

flowchart TD
subgraph subGraph1["CI Validation"]
    CITrigger["CI Pipeline Triggered"]
    CIFormat["CI: Format check"]
    CIClippy["CI: Clippy check"]
    CIBuild["CI: Build all targets"]
    CITest["CI: Run tests"]
    CIDoc["CI: Build docs"]
end
subgraph subGraph0["Development Loop"]
    DevLoop["Development Loop"]
    CodeChange["Make code changes"]
    LocalFormat["cargo fmt"]
    LocalClippy["cargo clippy"]
    LocalBuild["cargo build"]
    LocalTest["cargo test"]
    Validation["Changes valid?"]
    Commit["git commit"]
end
DevStart["Start Development"]
Clone["git clone repository"]
Setup["Install toolchain components"]
Targets["Install target platforms"]
Push["git push"]
Success["Ready for merge"]

CIBuild --> CITest
CIClippy --> CIBuild
CIDoc --> Success
CIFormat --> CIClippy
CITest --> CIDoc
CITrigger --> CIFormat
Clone --> Setup
CodeChange --> LocalFormat
Commit --> Push
DevLoop --> CodeChange
DevStart --> Clone
LocalBuild --> LocalTest
LocalClippy --> LocalBuild
LocalFormat --> LocalClippy
LocalTest --> Validation
Push --> CITrigger
Setup --> Targets
Targets --> DevLoop
Validation --> CodeChange
Validation --> Commit

Sources: .github/workflows/ci.yml(L14 - L31)  Cargo.toml(L1 - L22) 

Testing Procedures

The kspin crate employs comprehensive testing through both local development tools and automated CI validation. Testing is performed across multiple feature combinations and target platforms to ensure broad compatibility.

Local Testing Commands

CommandPurpose
cargo testRun tests with default features
cargo hack test --each-featureTest each feature individually
cargo hack test --feature smpTest with SMP feature enabled
cargo test --target x86_64-unknown-linux-gnuTest on specific target

CI Testing Matrix

The CI system automatically tests every feature combination across all supported target platforms. Unit tests are executed only on the x86_64-unknown-linux-gnu target, as this is the only hosted environment that supports test execution .github/workflows/ci.yml(L30 - L31) 

The use of cargo-hack ensures that feature flag interactions are properly validated and that the crate maintains compatibility across different compilation configurations .github/workflows/ci.yml(L26 - L31) 

Sources: .github/workflows/ci.yml(L26 - L31)  Cargo.toml(L14 - L22) 

Build System and Feature Flags

Relevant source files

This document covers the build system configuration, feature flags, and compilation targets for the kspin crate. It explains how to build the crate with different feature combinations and target platforms, and describes the automated build and testing infrastructure.

For information about the development environment setup and tooling, see Development Environment Setup. For details about the testing infrastructure, see Testing and CI Pipeline.

Feature Flags

The kspin crate uses Cargo feature flags to enable compile-time optimization for different deployment scenarios. The primary feature flag is smp, which controls whether the spinlock implementation includes multi-core synchronization logic.

SMP Feature Flag

The smp feature flag is defined in Cargo.toml(L14 - L16)  and controls fundamental compilation behavior:


Build Matrix: Feature Flag Compilation

When the smp feature is enabled:

  • BaseSpinLock includes an AtomicBool state field
  • Lock operations use compare_exchange atomic operations
  • Full memory ordering constraints are enforced
  • Spinning behavior is implemented for contention scenarios

When the smp feature is disabled:

  • BaseSpinLock has no state field (zero-cost abstraction)
  • Lock operations become no-ops or compile-time optimizations
  • No atomic operations are generated
  • Suitable for single-core embedded environments

Sources: Cargo.toml(L14 - L16) 

Target Platform Support

The build system supports multiple target architectures through the CI matrix configuration. Each target represents a different deployment environment with specific requirements.

Supported Targets

Target PlatformArchitectureUse CaseSMP Support
x86_64-unknown-linux-gnux86-64Development/TestingFull
x86_64-unknown-nonex86-64Bare metal kernelFull
riscv64gc-unknown-none-elfRISC-V 64-bitEmbedded kernelFull
aarch64-unknown-none-softfloatARM64Embedded systemsFull
flowchart TD
subgraph subGraph2["Feature Testing"]
    EachFeature[".github/workflows/ci.yml:26,28--each-feature flag"]
    DefaultFeatures["Default buildNo features enabled"]
    SMPFeatures["SMP buildsmp feature enabled"]
end
subgraph subGraph1["Build Tools"]
    RustToolchain[".github/workflows/ci.yml:11rust-toolchain: [nightly]"]
    CargoHack[".github/workflows/ci.yml:15cargo-hack tool"]
    Components[".github/workflows/ci.yml:19rust-src, clippy, rustfmt"]
end
subgraph subGraph0["CI Build Matrix"]
    Matrix[".github/workflows/ci.yml:10-12matrix.targets"]
    x86Linux["x86_64-unknown-linux-gnuStandard Linux targetFull testing capabilities"]
    x86Bare["x86_64-unknown-noneBare metal x86No std library"]
    RISCV["riscv64gc-unknown-none-elfRISC-V embeddedArceOS target"]
    ARM["aarch64-unknown-none-softfloatARM64 embeddedSoftware floating point"]
end

CargoHack --> EachFeature
Components --> Matrix
EachFeature --> DefaultFeatures
EachFeature --> SMPFeatures
Matrix --> ARM
Matrix --> RISCV
Matrix --> x86Bare
Matrix --> x86Linux
RustToolchain --> Matrix

Sources: .github/workflows/ci.yml(L10 - L12)  .github/workflows/ci.yml(L19) 

Build Commands and Configuration

Basic Build Commands

The standard build commands work with cargo's feature flag system:

# Default build (no features)
cargo build

# Build with SMP support
cargo build --features smp

# Build for specific target
cargo build --target x86_64-unknown-none --features smp

# Build all feature combinations
cargo hack build --each-feature

CI Build Pipeline

The automated build pipeline uses cargo-hack to test all feature combinations across all target platforms:

sequenceDiagram
    participant GitHubActions as "GitHub Actions"
    participant BuildMatrix as "Build Matrix"
    participant cargohack as "cargo-hack"
    participant TargetPlatform as "Target Platform"

    GitHubActions ->> BuildMatrix: Trigger CI workflow
    GitHubActions ->> BuildMatrix: .github/workflows/ci.yml:3
    BuildMatrix ->> cargohack: Install cargo-hack tool
    BuildMatrix ->> cargohack: .github/workflows/ci.yml:15
    loop Each Target Platform
        BuildMatrix ->> TargetPlatform: Setup toolchain
        BuildMatrix ->> TargetPlatform: .github/workflows/ci.yml:16-20
        TargetPlatform ->> cargohack: Check format
        TargetPlatform ->> cargohack: .github/workflows/ci.yml:24
        TargetPlatform ->> cargohack: Run clippy
        TargetPlatform ->> cargohack: .github/workflows/ci.yml:26
        cargohack ->> TargetPlatform: --each-feature flag
        TargetPlatform ->> cargohack: Build all features
        TargetPlatform ->> cargohack: .github/workflows/ci.yml:28
    alt x86_64-unknown-linux-gnu
    alt only
        TargetPlatform ->> cargohack: Run tests
        TargetPlatform ->> cargohack: .github/workflows/ci.yml:30-31
    end
    end
    end

Build Steps Detail:

  1. Format Check: cargo fmt --all -- --check .github/workflows/ci.yml(L24) 
  2. Linting: cargo hack clippy --target $target --each-feature .github/workflows/ci.yml(L26) 
  3. Compilation: cargo hack build --target $target --each-feature .github/workflows/ci.yml(L28) 
  4. Testing: cargo hack test --target $target --each-feature (Linux only) .github/workflows/ci.yml(L31) 

Sources: .github/workflows/ci.yml(L24 - L31) 

Dependency Management

The crate's dependencies are managed through Cargo.toml and support the feature flag system:


Dependency Roles:

  • cfg-if: Enables conditional compilation based on feature flags and target platform
  • kernel_guard: Provides NoOp, NoPreempt, and NoPreemptIrqSave guard types
  • smp feature: Controls atomic operation inclusion without additional dependencies

Sources: Cargo.toml(L19 - L21)  Cargo.toml(L14 - L17) 

Documentation Generation

The build system includes automated documentation generation and deployment:

flowchart TD
subgraph Output["Output"]
    TargetDoc["target/doc/Generated documentation"]
    DocsRS["docs.rs/kspinCargo.toml:10"]
end
subgraph subGraph1["Documentation Flags"]
    RustDocFlags["RUSTDOCFLAGS.github/workflows/ci.yml:41-D rustdoc::broken_intra_doc_links-D missing-docs"]
end
subgraph subGraph0["Documentation Pipeline"]
    DocJob[".github/workflows/ci.yml:33doc job"]
    BuildDocs[".github/workflows/ci.yml:48cargo doc --no-deps --all-features"]
    IndexGeneration[".github/workflows/ci.yml:49printf redirect to index.html"]
    GitHubPages[".github/workflows/ci.yml:50-56Deploy to gh-pages branch"]
end

BuildDocs --> IndexGeneration
DocJob --> BuildDocs
GitHubPages --> DocsRS
IndexGeneration --> TargetDoc
RustDocFlags --> BuildDocs
TargetDoc --> GitHubPages

The documentation build enforces strict documentation requirements through RUSTDOCFLAGS and deploys automatically to GitHub Pages for the default branch.

Sources: .github/workflows/ci.yml(L33 - L56)  .github/workflows/ci.yml(L41)  Cargo.toml(L10) 

Testing and CI Pipeline

Relevant source files

This document covers the automated testing infrastructure and continuous integration setup for the kspin crate. The testing and CI pipeline ensures code quality, compatibility across multiple target platforms, and automated documentation deployment.

For information about manually building the crate and configuring development environments, see Build System and Feature Flags and Development Environment Setup.

CI Pipeline Architecture

The kspin crate uses GitHub Actions for continuous integration, configured through a single workflow file that handles both testing and documentation deployment. The pipeline is designed to validate the crate across multiple embedded and general-purpose target platforms.

CI Pipeline Overview

flowchart TD
subgraph subGraph3["Doc Job Steps"]
    DocCheckout["actions/checkout@v4"]
    DocRust["dtolnay/rust-toolchain@nightly"]
    BuildDocs["cargo doc --no-deps --all-features"]
    Deploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["CI Job Steps"]
    Checkout["actions/checkout@v4"]
    InstallHack["taiki-e/install-action@cargo-hack"]
    SetupRust["dtolnay/rust-toolchain@nightly"]
    CheckVersion["rustc --version --verbose"]
    Format["cargo fmt --all -- --check"]
    Clippy["cargo hack clippy"]
    Build["cargo hack build"]
    Test["cargo hack test"]
end
subgraph subGraph1["GitHub Actions Workflow"]
    CIJob["ci jobTesting & Quality"]
    DocJob["doc jobDocumentation"]
end
subgraph subGraph0["Trigger Events"]
    Push["push events"]
    PR["pull_request events"]
end

Build --> Test
BuildDocs --> Deploy
CIJob --> Checkout
CheckVersion --> Format
Checkout --> InstallHack
Clippy --> Build
DocCheckout --> DocRust
DocJob --> DocCheckout
DocRust --> BuildDocs
Format --> Clippy
InstallHack --> SetupRust
PR --> CIJob
PR --> DocJob
Push --> CIJob
Push --> DocJob
SetupRust --> CheckVersion

Sources: .github/workflows/ci.yml(L1 - L57) 

Build Matrix Strategy

The CI pipeline uses a matrix build strategy to validate the codebase across multiple target platforms and feature combinations. This ensures compatibility with the diverse environments where kernel spinlocks are typically deployed.

ComponentConfiguration
Runnerubuntu-latest
Rust Toolchainnightly
Required Componentsrust-src,clippy,rustfmt
Target Platforms4 targets (see below)
Failure Strategyfail-fast: false

Target Platform Matrix

flowchart TD
subgraph subGraph1["Target Platforms"]
    LinuxGNU["x86_64-unknown-linux-gnuStandard Linux userspace"]
    x86None["x86_64-unknown-noneBare metal x86_64"]
    RISCV["riscv64gc-unknown-none-elfRISC-V bare metal"]
    ARM["aarch64-unknown-none-softfloatARM64 bare metal"]
end
subgraph subGraph0["Matrix Strategy"]
    Matrix["matrix.targets"]
end
subgraph subGraph2["Feature Testing"]
    EachFeature["--each-feature flagTests all feature combinations"]
    SMPFeature["smp featureMulti-core vs single-core"]
end

EachFeature --> SMPFeature
Matrix --> ARM
Matrix --> LinuxGNU
Matrix --> RISCV
Matrix --> x86None

The cargo-hack tool is used with the --each-feature flag to test all possible feature combinations across all target platforms, ensuring that feature-conditional compilation works correctly.

Sources: .github/workflows/ci.yml(L8 - L20)  .github/workflows/ci.yml(L26 - L31) 

Quality Assurance Pipeline

The CI pipeline enforces code quality through multiple automated checks that must pass before code can be merged.

Quality Check Sequence

sequenceDiagram
    participant GitHubActions as "GitHub Actions"
    participant RustToolchain as "Rust Toolchain"
    participant cargohack as "cargo-hack"
    participant TargetPlatform as "Target Platform"

    GitHubActions ->> RustToolchain: Setup nightly toolchain
    RustToolchain ->> GitHubActions: Install rust-src, clippy, rustfmt
    Note over GitHubActions,TargetPlatform: For each target in matrix
    GitHubActions ->> RustToolchain: cargo fmt --all -- --check
    RustToolchain ->> GitHubActions: ✓ Code formatting validated
    GitHubActions ->> cargohack: cargo hack clippy --target <target> --each-feature
    cargohack ->> TargetPlatform: Run clippy for each feature combination
    TargetPlatform ->> cargohack: ✓ No warnings (-D warnings)
    cargohack ->> GitHubActions: ✓ Linting complete
    GitHubActions ->> cargohack: cargo hack build --target <target> --each-feature
    cargohack ->> TargetPlatform: Build each feature combination
    TargetPlatform ->> cargohack: ✓ Successful build
    cargohack ->> GitHubActions: ✓ Build complete

Code Formatting

  • Tool: cargo fmt
  • Enforcement: --check flag ensures CI fails if code is not properly formatted
  • Scope: All files (--all flag)

Linting

  • Tool: cargo clippy
  • Configuration: Treats all warnings as errors (-D warnings)
  • Coverage: All target platforms and feature combinations
  • Command: cargo hack clippy --target ${{ matrix.targets }} --each-feature -- -D warnings

Build Validation

  • Tool: cargo build via cargo-hack
  • Coverage: All target platforms and feature combinations
  • Command: cargo hack build --target ${{ matrix.targets }} --each-feature

Sources: .github/workflows/ci.yml(L24 - L28) 

Testing Strategy

The testing approach recognizes the constraints of kernel-space code while maximizing coverage where possible.

Test Execution Matrix

flowchart TD
subgraph subGraph2["Test Execution"]
    TestRun["if: matrix.targets == 'x86_64-unknown-linux-gnu'cargo hack test --target --each-feature -- --nocapture"]
    BuildOnly["Build-only validationfor bare metal targets"]
end
subgraph subGraph1["Test Types"]
    UnitTests["Unit Testscargo hack test"]
    BuildTests["Build Testscargo hack build"]
    FeatureTests["Feature Tests--each-feature"]
end
subgraph subGraph0["Test Targets"]
    LinuxTarget["x86_64-unknown-linux-gnu"]
    BareMetalTargets["Bare metal targetsx86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"]
end

BareMetalTargets --> BuildOnly
BareMetalTargets --> BuildTests
BareMetalTargets --> FeatureTests
FeatureTests --> TestRun
LinuxTarget --> BuildTests
LinuxTarget --> FeatureTests
LinuxTarget --> TestRun
LinuxTarget --> UnitTests
UnitTests --> TestRun

Unit Test Execution

  • Platform Restriction: Tests only run on x86_64-unknown-linux-gnu
  • Rationale: Bare metal targets cannot execute standard Rust test framework
  • Configuration: Tests run with --nocapture for full output visibility
  • Feature Coverage: All feature combinations tested via --each-feature

Build-Only Testing

For bare metal targets (x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat), the pipeline performs build-only validation to ensure:

  • Code compiles successfully for target architecture
  • Feature flags work correctly in no-std environments
  • Cross-compilation succeeds without runtime dependencies

Sources: .github/workflows/ci.yml(L30 - L31) 

Documentation Pipeline

The documentation system automatically builds and deploys API documentation to GitHub Pages, ensuring developers always have access to current documentation.

Documentation Workflow

flowchart TD
subgraph Deployment["Deployment"]
    BranchCheck["if: github.ref == env.default-branch"]
    GHPages["JamesIves/github-pages-deploy-action@v4branch: gh-pagesfolder: target/docsingle-commit: true"]
end
subgraph subGraph1["Build Process"]
    DocBuild["cargo doc --no-deps --all-features"]
    IndexGen["Generate redirect index.html$(cargo tree | head -1 | cut -d' ' -f1)"]
    ContinueOnError["continue-on-error: conditionalBased on branch and event type"]
end
subgraph subGraph0["Documentation Job"]
    DocTrigger["Push to default branchOR pull request"]
    DocPerms["permissions:contents: write"]
    DocEnv["RUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"]
end

BranchCheck --> GHPages
ContinueOnError --> BranchCheck
DocBuild --> IndexGen
DocEnv --> DocBuild
DocPerms --> DocEnv
DocTrigger --> DocPerms
IndexGen --> ContinueOnError

Documentation Quality Enforcement

The documentation build process enforces strict quality standards:

SettingPurpose
-D rustdoc::broken_intra_doc_linksFails build on broken internal documentation links
-D missing-docsRequires documentation for all public items
--no-depsBuilds only crate documentation, not dependencies
--all-featuresDocuments all feature-gated functionality

Deployment Strategy

  • Target Branch: gh-pages
  • Deployment Condition: Only on pushes to the default branch
  • Commit Strategy: single-commit: true keeps deployment history clean
  • Index Generation: Automatically creates redirect to main crate documentation

Sources: .github/workflows/ci.yml(L33 - L57) 

Pipeline Robustness Features

The CI configuration includes several features designed to maintain pipeline reliability and provide useful feedback:

Error Handling

  • Fail-fast disabled: fail-fast: false allows all matrix jobs to complete even if some fail
  • Conditional error handling: Documentation builds continue on error for non-default branches
  • Warning promotion: Clippy warnings treated as errors to maintain code quality

Toolchain Management

  • Nightly Rust: Uses bleeding-edge features required for no-std development
  • Component installation: Automatically installs required components (rust-src, clippy, rustfmt)
  • Version verification: Explicit Rust version checking for debugging

Resource Optimization

  • Single commit deployment: Minimizes repository size growth from documentation updates
  • Targeted testing: Unit tests only run where feasible (Linux target)
  • Efficient caching: Standard GitHub Actions caching for Rust toolchain and dependencies

Sources: .github/workflows/ci.yml(L9)  .github/workflows/ci.yml(L41)  .github/workflows/ci.yml(L46) 

Development Environment Setup

Relevant source files

This document provides guidance for setting up a development environment to build, test, and contribute to the kspin crate. It covers the required toolchain, build processes, and development practices used by the project.

For information about the build system's feature flags and compilation targets, see Build System and Feature Flags. For details about the automated testing infrastructure, see Testing and CI Pipeline.

Prerequisites

The kspin crate requires specific Rust toolchain components and supports multiple target platforms. The development environment must be configured to handle both hosted and no-std embedded targets.

Required Rust Toolchain Components

The project uses the Rust nightly toolchain with specific components required for cross-platform development and code quality checks:

ComponentPurpose
rust-srcSource code for cross-compilation to no-std targets
clippyLinting and static analysis
rustfmtCode formatting

Supported Target Platforms

The CI system validates builds across multiple target architectures:

TargetPlatform TypeUsage Context
x86_64-unknown-linux-gnuHosted LinuxDevelopment and testing
x86_64-unknown-noneBare metal x86-64Kernel environments
riscv64gc-unknown-none-elfBare metal RISC-VEmbedded kernel systems
aarch64-unknown-none-softfloatBare metal ARM64Embedded kernel systems

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L18 - L20) 

Development Toolchain Setup

Rust Toolchain Installation

Install the nightly Rust toolchain with required components and target platforms:

# Install nightly toolchain
rustup toolchain install nightly

# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt

# Add target platforms
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat

Development Tools

Install cargo-hack for comprehensive feature combination testing:

cargo install cargo-hack

The cargo-hack tool enables testing all feature combinations across different targets, matching the CI environment exactly.

Sources: .github/workflows/ci.yml(L15)  .github/workflows/ci.yml(L16 - L20) 

Development Workflow

Development Environment Flow

flowchart TD
subgraph Documentation["Documentation"]
    DocBuild["cargo doc --no-deps --all-featuresLocal documentation"]
    DocView["Open target/doc/kspin/index.htmlView generated docs"]
end
subgraph subGraph2["Local Development Commands"]
    Format["cargo fmt --all --checkCode formatting"]
    Clippy["cargo hack clippy--target TARGET --each-feature-- -D warnings"]
    Build["cargo hack build--target TARGET --each-feature"]
    Test["cargo hack test--target x86_64-unknown-linux-gnu--each-feature -- --nocapture"]
end
subgraph subGraph1["Development Tools"]
    VSCode[".vscode/VS Code configuration"]
    Git[".gitignoreGit ignore rules"]
    CargoToml["Cargo.tomlDependencies & metadata"]
end
subgraph subGraph0["Local Development Setup"]
    RustInstall["rustup install nightly"]
    ComponentAdd["rustup component addrust-src clippy rustfmt"]
    TargetAdd["rustup target addx86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"]
    CargoHack["cargo install cargo-hack"]
end

Build --> Test
CargoHack --> Format
CargoToml --> Build
Clippy --> Build
ComponentAdd --> TargetAdd
DocBuild --> DocView
Format --> Clippy
Git --> VSCode
RustInstall --> ComponentAdd
TargetAdd --> CargoHack
Test --> DocBuild
VSCode --> Format

Sources: .github/workflows/ci.yml(L16 - L31)  .github/workflows/ci.yml(L45 - L49)  .gitignore(L2) 

Local Build and Test Process

Code Quality Checks

Replicate the CI environment locally by running the same commands used in automated testing:

Code Formatting

cargo fmt --all -- --check

Linting with Feature Combinations

# Lint for specific target with all feature combinations
cargo hack clippy --target x86_64-unknown-none --each-feature -- -D warnings
cargo hack clippy --target riscv64gc-unknown-none-elf --each-feature -- -D warnings

Building Across Targets

# Build for all feature combinations on each target
cargo hack build --target x86_64-unknown-none --each-feature
cargo hack build --target riscv64gc-unknown-none-elf --each-feature
cargo hack build --target aarch64-unknown-none-softfloat --each-feature

Testing

# Run tests (only on hosted target)
cargo hack test --target x86_64-unknown-linux-gnu --each-feature -- --nocapture

Sources: .github/workflows/ci.yml(L24)  .github/workflows/ci.yml(L26)  .github/workflows/ci.yml(L28)  .github/workflows/ci.yml(L31) 

Feature Flag Testing

The --each-feature flag tests the following combinations:

  • No features (default)
  • smp feature enabled
  • All features enabled

This ensures the crate works correctly in both single-core and multi-core environments.

Local Development Command Mapping

flowchart TD
subgraph subGraph3["Target Platforms"]
    LinuxGnu["x86_64-unknown-linux-gnu(testing only)"]
    X86None["x86_64-unknown-none(kernel target)"]
    RiscV["riscv64gc-unknown-none-elf(embedded kernel)"]
    ARM["aarch64-unknown-none-softfloat(embedded kernel)"]
end
subgraph subGraph2["Local Development"]
    LocalFormat["cargo fmt"]
    LocalClippy["cargo clippy"]
    LocalBuild["cargo build"]
    LocalTest["cargo test"]
    LocalDoc["cargo doc --no-deps --all-features"]
end
subgraph subGraph1["CI Commands"]
    CIFormat["cargo fmt --all -- --check"]
    CIClippy["cargo hack clippy --target TARGET--each-feature -- -D warnings"]
    CIBuild["cargo hack build --target TARGET--each-feature"]
    CITest["cargo hack test --target TARGET--each-feature -- --nocapture"]
end
subgraph subGraph0["Cargo.toml Configuration"]
    Features["[features]smp = []default = []"]
    Dependencies["[dependencies]cfg-if = '1.0'kernel_guard = '0.1'"]
end

CIBuild --> ARM
CIBuild --> LocalBuild
CIBuild --> RiscV
CIBuild --> X86None
CIClippy --> LocalClippy
CIFormat --> LocalFormat
CITest --> LinuxGnu
CITest --> LocalTest
Dependencies --> LocalBuild
Features --> CIBuild
LocalDoc --> CIBuild

Sources: Cargo.toml(L14 - L17)  Cargo.toml(L19 - L21)  .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L24 - L31) 

Development Environment Configuration

IDE Setup

The project includes VS Code ignore rules, indicating support for Visual Studio Code development:

/.vscode

Developers can create local .vscode/settings.json configurations for:

  • Rust-analyzer settings
  • Target-specific build configurations
  • Code formatting preferences

Git Configuration

The .gitignore file excludes:

  • Build artifacts (/target)
  • IDE configurations (/.vscode)
  • System files (.DS_Store)

Sources: .gitignore(L1 - L3) 

Documentation Development

Local Documentation Building

Generate and view documentation locally:

# Build documentation with all features
cargo doc --no-deps --all-features

# View in browser
open target/doc/kspin/index.html

The documentation build process matches the CI environment, ensuring consistency with the published GitHub Pages documentation.

Documentation Quality Checks

The CI system enforces documentation quality with specific RUSTDOCFLAGS:

RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features

Sources: .github/workflows/ci.yml(L41)  .github/workflows/ci.yml(L45 - L49) 

Continuous Integration Alignment

The local development environment should replicate the CI matrix strategy to ensure compatibility:

Matrix Testing Strategy

ToolchainAll TargetsFeature Testing
nightly--each-feature

CI Job Replication

  1. Format Check: cargo fmt --all -- --check
  2. Lint Check: cargo hack clippy --target TARGET --each-feature -- -D warnings
  3. Build Check: cargo hack build --target TARGET --each-feature
  4. Unit Tests: cargo hack test --target x86_64-unknown-linux-gnu --each-feature -- --nocapture
  5. Documentation: cargo doc --no-deps --all-features

Running these commands locally before committing ensures smooth CI pipeline execution.

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L24 - L31) 

Overview

Relevant source files

Purpose and Scope

The kernel_guard crate provides RAII (Resource Acquisition Is Initialization) wrappers for creating critical sections in kernel-level code where local interrupts (IRQs) and/or preemption must be temporarily disabled. This crate is specifically designed for operating system kernels and bare-metal systems that require fine-grained control over interrupt handling and task scheduling.

The primary use case is implementing synchronization primitives like spinlocks in kernel code, where critical sections must be protected from both interrupt handlers and preemptive task switching. For detailed information about the core architecture and guard implementations, see Core Architecture. For multi-architecture support details, see Multi-Architecture Support. For integration guidance, see Integration Guide.

Sources: Cargo.toml(L6 - L12)  src/lib.rs(L1 - L11)  README.md(L7 - L11) 

Core Guard Types and RAII Pattern

The crate implements four distinct guard types that follow the RAII pattern, automatically managing critical section entry and exit through constructor and destructor semantics:

Guard Type Architecture

flowchart TD
subgraph subGraph2["Target Dependencies"]
    ARCH["arch::local_irq_save_and_disable()arch::local_irq_restore()"]
    INTERFACE["crate_interface::call_interface!KernelGuardIf::disable_preemptKernelGuardIf::enable_preempt"]
end
subgraph subGraph1["Concrete Guards"]
    NOOP["NoOptype State = ()No operation"]
    IRQ["IrqSavetype State = usizeSaves/restores IRQ state"]
    PREEMPT["NoPreempttype State = ()Disables preemption"]
    BOTH["NoPreemptIrqSavetype State = usizeDisables both"]
end
subgraph subGraph0["BaseGuard Trait"]
    BG["BaseGuardacquire() -> Staterelease(State)"]
end

BG --> BOTH
BG --> IRQ
BG --> NOOP
BG --> PREEMPT
BOTH --> ARCH
BOTH --> INTERFACE
IRQ --> ARCH
PREEMPT --> INTERFACE

Sources: src/lib.rs(L68 - L78)  src/lib.rs(L81 - L111)  src/lib.rs(L134 - L179) 

Guard TypeState TypeIRQ ControlPreemption ControlTarget Compatibility
NoOp()NoneNoneAll targets
IrqSaveusizeSave/restoreNonetarget_os = "none"only
NoPreempt()NoneDisable/enableRequirespreemptfeature
NoPreemptIrqSaveusizeSave/restoreDisable/enabletarget_os = "none"+preemptfeature

Sources: src/lib.rs(L113 - L128)  src/lib.rs(L134 - L179)  src/lib.rs(L200 - L237) 

Conditional Compilation Strategy

The crate uses sophisticated conditional compilation to provide different implementations based on the target environment and enabled features:

Compilation Flow and Code Paths


Sources: src/lib.rs(L83 - L111)  src/lib.rs(L130 - L238)  Cargo.toml(L14 - L16) 

Multi-Architecture Support Integration

The crate abstracts platform-specific interrupt handling through a unified architecture interface:

Architecture Module Structure


Sources: src/lib.rs(L56)  src/lib.rs(L139 - L145)  src/lib.rs(L170 - L174) 

User Integration Pattern

The crate uses the crate_interface mechanism to allow users to provide preemption control implementations:

Integration Interface Flow

flowchart TD
subgraph subGraph2["Runtime Behavior"]
    ACQUIRE["acquire() calls disable_preempt"]
    DROP["drop() calls enable_preempt"]
end
subgraph subGraph1["kernel_guard Crate"]
    TRAIT_DEF["#[crate_interface::def_interface]trait KernelGuardIfsrc/lib.rs:59-66"]
    CALL_SITE["crate_interface::call_interface!KernelGuardIf::disable_preemptsrc/lib.rs:154"]
    GUARD_IMPL["NoPreempt BaseGuard implsrc/lib.rs:149-161"]
end
subgraph subGraph0["User Code"]
    USER_IMPL["struct KernelGuardIfImpl"]
    TRAIT_IMPL["#[crate_interface::impl_interface]impl KernelGuardIf for KernelGuardIfImpl"]
    USAGE["let guard = NoPreempt::new()"]
end

CALL_SITE --> TRAIT_IMPL
GUARD_IMPL --> ACQUIRE
GUARD_IMPL --> CALL_SITE
GUARD_IMPL --> DROP
TRAIT_IMPL --> TRAIT_DEF
USAGE --> GUARD_IMPL
USER_IMPL --> TRAIT_IMPL

Sources: src/lib.rs(L58 - L66)  src/lib.rs(L149 - L161)  src/lib.rs(L31 - L52)  README.md(L36 - L58) 

Feature Configuration Summary

The crate provides two primary configuration axes:

ConfigurationEffectCode Path
target_os = "none"Enables real IRQ control implementationssrc/lib.rs86-100
target_os != "none"UsesNoOpaliases for all guardssrc/lib.rs102-110
feature = "preempt"Enables preemption control callssrc/lib.rs153-159
NopreemptfeaturePreemption calls become no-opsConditional compilation removes calls

Sources: src/lib.rs(L83 - L111)  Cargo.toml(L14 - L16)  src/lib.rs(L153 - L159) 

The crate is specifically designed for the ArceOS ecosystem but provides a generic interface suitable for any kernel or bare-metal system requiring interrupt and preemption management. The modular architecture ensures that only the necessary functionality is compiled for each target platform while maintaining a consistent API across all supported architectures.

Core Architecture

Relevant source files

This document describes the core architecture of the kernel_guard crate, focusing on the trait system, RAII guard implementations, and conditional compilation strategy. The architecture provides a unified interface for creating critical sections while abstracting platform-specific interrupt handling and kernel preemption control.

For information about platform-specific implementations, see Multi-Architecture Support. For integration details and feature configuration, see Integration Guide.

Trait System Foundation

The kernel_guard crate is built around two core traits that define the contract for critical section management and kernel integration.

BaseGuard Trait

The BaseGuard trait serves as the fundamental interface that all guard implementations must satisfy. It defines a generic pattern for acquiring and releasing critical section protection.

classDiagram
class BaseGuard {
    <<trait>>
    +type State: Clone + Copy
    +acquire() State
    +release(state: State)
}

class NoOp {
    
    +type State =()
    +acquire()()
    +release(())
}

class IrqSave {
    +type State = usize
    +acquire() usize
    +release(usize)
}

class NoPreempt {
    
    +type State =()
    +acquire()()
    +release(())
}

class NoPreemptIrqSave {
    +type State = usize
    +acquire() usize
    +release(usize)
}

BaseGuard  ..|>  NoOp
BaseGuard  ..|>  IrqSave
BaseGuard  ..|>  NoPreempt
NoPreemptIrqSave  ..|>  IrqSave

BaseGuard Trait Architecture

The trait uses an associated State type to capture platform-specific information needed to restore the system state when the critical section ends. For example, interrupt guards store the previous interrupt flag state, while preemption-only guards use unit type since no state needs preservation.

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

KernelGuardIf Interface

The KernelGuardIf trait provides the integration point between kernel_guard and the user's kernel implementation. This trait must be implemented by crate users when the preempt feature is enabled.

flowchart TD
subgraph subGraph1["kernel_guard Crate"]
    KernelGuardIf["KernelGuardIf Traitenable_preempt()disable_preempt()"]
    CallInterface["crate_interface::call_interface!Runtime dispatch"]
    NoPreempt["NoPreempt Guard"]
    NoPreemptIrqSave["NoPreemptIrqSave Guard"]
end
subgraph subGraph0["User Crate"]
    UserImpl["User ImplementationKernelGuardIfImpl"]
end

CallInterface --> KernelGuardIf
NoPreempt --> CallInterface
NoPreemptIrqSave --> CallInterface
UserImpl --> KernelGuardIf

KernelGuardIf Integration Pattern

The interface uses the crate_interface crate to provide stable ABI boundaries, allowing the kernel implementation to be provided at runtime rather than compile time.

Sources: src/lib.rs(L58 - L66)  src/lib.rs(L154)  src/lib.rs(L159)  src/lib.rs(L168)  src/lib.rs(L177) 

Guard Implementation Hierarchy

The crate provides four distinct guard types, each designed for specific synchronization requirements in kernel contexts.

Guard Type Matrix

Guard TypeIRQ ControlPreemption ControlState TypePrimary Use Case
NoOpNoneNone()Testing, userspace
IrqSaveDisable/RestoreNoneusizeIRQ-sensitive operations
NoPreemptNoneDisable/Enable()Scheduler coordination
NoPreemptIrqSaveDisable/RestoreDisable/EnableusizeComplete critical sections

Implementation Strategy

flowchart TD
subgraph subGraph2["Platform Integration"]
    ArchIRQ["arch::local_irq_save_and_disable()"]
    ArchRestore["arch::local_irq_restore(state)"]
    KernelIF["KernelGuardIf::disable_preempt()"]
    KernelRestore["KernelGuardIf::enable_preempt()"]
end
subgraph subGraph1["State Management"]
    IRQState["IRQ State (usize)Platform flags"]
    NoState["No State (())Simple enable/disable"]
end
subgraph subGraph0["Guard Lifecycle"]
    Create["Guard::new()"]
    Acquire["BaseGuard::acquire()"]
    Critical["Critical SectionUser Code"]
    Drop["impl Drop"]
    Release["BaseGuard::release(state)"]
end

Acquire --> ArchIRQ
Acquire --> Critical
Acquire --> KernelIF
ArchIRQ --> IRQState
Create --> Acquire
Critical --> Drop
Drop --> Release
IRQState --> ArchRestore
KernelIF --> NoState
NoState --> KernelRestore
Release --> ArchRestore
Release --> KernelRestore

RAII Guard Lifecycle and Platform Integration

Sources: src/lib.rs(L134 - L179)  src/lib.rs(L181 - L237) 

Conditional Compilation Strategy

The crate uses sophisticated conditional compilation to provide different implementations based on the target environment and available features.

Target Environment Detection

flowchart TD
subgraph subGraph3["Feature Gates"]
    PreemptEnabled["#[cfg(feature = 'preempt')]crate_interface calls"]
    PreemptDisabled["No preemption control"]
end
subgraph subGraph2["Userspace Path"]
    AliasIrqSave["type IrqSave = NoOp"]
    AliasNoPreempt["type NoPreempt = NoOp"]
    AliasNoPreemptIrqSave["type NoPreemptIrqSave = NoOp"]
end
subgraph subGraph1["Bare Metal Path"]
    RealIrqSave["struct IrqSave(usize)"]
    RealNoPreempt["struct NoPreempt"]
    RealNoPreemptIrqSave["struct NoPreemptIrqSave(usize)"]
    ArchIntegration["arch::local_irq_* calls"]
end
subgraph subGraph0["Compilation Decision"]
    CfgIf["cfg_if! macro"]
    TargetCheck["target_os = 'none' or doc?"]
    FeatureCheck["preempt feature enabled?"]
end

CfgIf --> TargetCheck
FeatureCheck --> PreemptDisabled
FeatureCheck --> PreemptEnabled
RealIrqSave --> ArchIntegration
RealNoPreempt --> PreemptEnabled
RealNoPreemptIrqSave --> ArchIntegration
RealNoPreemptIrqSave --> PreemptEnabled
TargetCheck --> AliasIrqSave
TargetCheck --> AliasNoPreempt
TargetCheck --> AliasNoPreemptIrqSave
TargetCheck --> RealIrqSave
TargetCheck --> RealNoPreempt
TargetCheck --> RealNoPreemptIrqSave

Conditional Compilation Flow

The compilation strategy ensures that:

  1. Bare metal targets (target_os = "none") receive full guard implementations with platform-specific interrupt control
  2. Userspace targets receive no-op aliases to prevent compilation errors while maintaining API compatibility
  3. Feature-gated preemption only compiles preemption control when the preempt feature is enabled

Sources: src/lib.rs(L83 - L111)  src/lib.rs(L130 - L238)  src/lib.rs(L153 - L154)  src/lib.rs(L158 - L159)  src/lib.rs(L167 - L168)  src/lib.rs(L176 - L177) 

Architecture Module Integration

The core library integrates with platform-specific implementations through the arch module, which provides a uniform interface for interrupt control across different CPU architectures.

Architecture Abstraction Interface

flowchart TD
subgraph subGraph2["Platform Implementations"]
    X86["x86::local_irq_*"]
    RISCV["riscv::local_irq_*"]
    AArch64["aarch64::local_irq_*"]
    LoongArch["loongarch64::local_irq_*"]
end
subgraph subGraph1["Architecture Module"]
    ArchMod["mod arch"]
    LocalIrqSave["local_irq_save_and_disable()"]
    LocalIrqRestore["local_irq_restore(state)"]
end
subgraph subGraph0["Core Library"]
    IrqSave["IrqSave::acquire()"]
    IrqRestore["IrqSave::release()"]
    NoPreemptIrqSave["NoPreemptIrqSave"]
end

ArchMod --> LocalIrqRestore
ArchMod --> LocalIrqSave
IrqRestore --> LocalIrqRestore
IrqSave --> LocalIrqSave
LocalIrqRestore --> AArch64
LocalIrqRestore --> LoongArch
LocalIrqRestore --> RISCV
LocalIrqRestore --> X86
LocalIrqSave --> AArch64
LocalIrqSave --> LoongArch
LocalIrqSave --> RISCV
LocalIrqSave --> X86
NoPreemptIrqSave --> LocalIrqRestore
NoPreemptIrqSave --> LocalIrqSave

Architecture Module Integration Pattern

The architecture module provides two critical functions that abstract platform-specific interrupt handling:

  • local_irq_save_and_disable(): Returns current interrupt state and disables interrupts
  • local_irq_restore(state): Restores interrupt state from saved value

Sources: src/lib.rs(L56)  src/lib.rs(L139)  src/lib.rs(L145)  src/lib.rs(L170)  src/lib.rs(L174) 

RAII Lifecycle Management

The crate implements the RAII (Resource Acquisition Is Initialization) pattern to ensure critical sections are properly managed without requiring explicit cleanup calls.

Drop Implementation Pattern

sequenceDiagram
    participant UserCode as "User Code"
    participant GuardInstance as "Guard Instance"
    participant BaseGuardacquirerelease as "BaseGuard::acquire/release"
    participant PlatformLayer as "Platform Layer"

    UserCode ->> GuardInstance: "Guard::new()"
    GuardInstance ->> BaseGuardacquirerelease: "acquire()"
    BaseGuardacquirerelease ->> PlatformLayer: "disable IRQ/preemption"
    PlatformLayer -->> BaseGuardacquirerelease: "return state"
    BaseGuardacquirerelease -->> GuardInstance: "return state"
    GuardInstance -->> UserCode: "return guard instance"
    Note over UserCode: Critical section executes
    UserCode ->> GuardInstance: "drop(guard) or scope end"
    GuardInstance ->> BaseGuardacquirerelease: "release(state)"
    BaseGuardacquirerelease ->> PlatformLayer: "restore IRQ/preemption"
    PlatformLayer -->> BaseGuardacquirerelease: "complete"
    BaseGuardacquirerelease -->> GuardInstance: "complete"
    GuardInstance -->> UserCode: "destruction complete"

RAII Lifecycle Sequence

Each guard type implements the Drop trait to ensure that critical section protection is automatically released when the guard goes out of scope, preventing resource leaks and ensuring system stability.

Sources: src/lib.rs(L126 - L128)  src/lib.rs(L188 - L192)  src/lib.rs(L208 - L212)  src/lib.rs(L227 - L231) 

RAII Guards

Relevant source files

This document describes the four RAII guard types provided by the kernel_guard crate: NoOp, IrqSave, NoPreempt, and NoPreemptIrqSave. These guards implement the Resource Acquisition Is Initialization (RAII) pattern to automatically manage critical sections by disabling interrupts and/or preemption when created and restoring the previous state when dropped.

For information about the underlying trait system that enables these guards, see Trait System. For architecture-specific implementations of interrupt control, see Multi-Architecture Support.

Overview

The RAII guards in kernel_guard provide automatic critical section management through Rust's ownership system. Each guard type disables specific kernel features upon creation and restores them when the guard goes out of scope, ensuring that critical sections are properly bounded even in the presence of early returns or panics.

Guard Type Hierarchy


Sources: src/lib.rs(L68 - L78)  src/lib.rs(L80 - L111)  src/lib.rs(L113 - L128) 

RAII Lifecycle Pattern

sequenceDiagram
    participant UserCode as "User Code"
    participant GuardStruct as "Guard Struct"
    participant BaseGuardacquire as "BaseGuard::acquire()"
    participant archlocal_irq_ as "arch::local_irq_*"
    participant KernelGuardIf as "KernelGuardIf"
    participant Dropdrop as "Drop::drop()"

    UserCode ->> GuardStruct: "new()"
    GuardStruct ->> BaseGuardacquire: "acquire()"
    alt IrqSave or NoPreemptIrqSave
        BaseGuardacquire ->> archlocal_irq_: "local_irq_save_and_disable()"
        archlocal_irq_ -->> BaseGuardacquire: "saved_flags: usize"
    end
    alt NoPreempt or NoPreemptIrqSave
        BaseGuardacquire ->> KernelGuardIf: "disable_preempt()"
    end
    BaseGuardacquire -->> GuardStruct: "state"
    GuardStruct -->> UserCode: "guard_instance"
    Note over UserCode: Critical section code runs
    UserCode ->> Dropdrop: "guard goes out of scope"
    Dropdrop ->> BaseGuardacquire: "release(state)"
    alt IrqSave or NoPreemptIrqSave
        BaseGuardacquire ->> archlocal_irq_: "local_irq_restore(state)"
    end
    alt NoPreempt or NoPreemptIrqSave
        BaseGuardacquire ->> KernelGuardIf: "enable_preempt()"
    end

Sources: src/lib.rs(L134 - L179)  src/lib.rs(L181 - L237) 

Guard Types

NoOp Guard

The NoOp guard provides a no-operation implementation that does nothing around critical sections. It serves as a placeholder when guard functionality is not needed or not available.

PropertyValue
State Type()
Target AvailabilityAll targets
IRQ ControlNone
Preemption ControlNone

The NoOp guard is always available and implements BaseGuard with empty operations:

// Implementation reference from src/lib.rs:113-117
impl BaseGuard for NoOp {
    type State = ();
    fn acquire() -> Self::State {}
    fn release(_state: Self::State) {}
}

Sources: src/lib.rs(L80 - L81)  src/lib.rs(L113 - L128) 

IrqSave Guard

The IrqSave guard disables local interrupts and saves the previous interrupt state, restoring it when dropped. This guard is only available on bare-metal targets (target_os = "none").

PropertyValue
State Typeusize(saved IRQ flags)
Target Availabilitytarget_os = "none"only
IRQ ControlSaves and disables, then restores
Preemption ControlNone

On non-bare-metal targets, IrqSave becomes a type alias to NoOp:

// Conditional compilation from src/lib.rs:83-111
cfg_if::cfg_if! {
    if #[cfg(any(target_os = "none", doc))] {
        pub struct IrqSave(usize);
    } else {
        pub type IrqSave = NoOp;
    }
}

The IrqSave implementation calls architecture-specific interrupt control functions:

Sources: src/lib.rs(L88)  src/lib.rs(L102 - L103)  src/lib.rs(L134 - L147)  src/lib.rs(L181 - L198) 

NoPreempt Guard

The NoPreempt guard disables kernel preemption for the duration of the critical section. It requires the preempt feature to be enabled and a user implementation of KernelGuardIf.

PropertyValue
State Type()
Target Availabilitytarget_os = "none"only
IRQ ControlNone
Preemption ControlDisables, then re-enables

The preemption control is conditional on the preempt feature:

// Feature-gated implementation from src/lib.rs:149-161
impl BaseGuard for NoPreempt {
    type State = ();
    fn acquire() -> Self::State {
        #[cfg(feature = "preempt")]
        crate_interface::call_interface!(KernelGuardIf::disable_preempt);
    }
    fn release(_state: Self::State) {
        #[cfg(feature = "preempt")]
        crate_interface::call_interface!(KernelGuardIf::enable_preempt);
    }
}

Sources: src/lib.rs(L92)  src/lib.rs(L105 - L106)  src/lib.rs(L149 - L161)  src/lib.rs(L200 - L218) 

NoPreemptIrqSave Guard

The NoPreemptIrqSave guard combines both preemption and interrupt control, providing the strongest level of critical section protection. It disables preemption first, then interrupts, and restores them in reverse order.

PropertyValue
State Typeusize(saved IRQ flags)
Target Availabilitytarget_os = "none"only
IRQ ControlSaves and disables, then restores
Preemption ControlDisables, then re-enables

The ordering ensures proper nesting: preemption is disabled before IRQs and re-enabled after IRQs are restored:

// Ordered disable/enable from src/lib.rs:163-179
impl BaseGuard for NoPreemptIrqSave {
    type State = usize;
    fn acquire() -> Self::State {
        // disable preempt first
        #[cfg(feature = "preempt")]
        crate_interface::call_interface!(KernelGuardIf::disable_preempt);
        // then disable IRQs
        super::arch::local_irq_save_and_disable()
    }
    fn release(state: Self::State) {
        // restore IRQs first
        super::arch::local_irq_restore(state);
        // then enable preempt
        #[cfg(feature = "preempt")]
        crate_interface::call_interface!(KernelGuardIf::enable_preempt);
    }
}

Sources: src/lib.rs(L100)  src/lib.rs(L108 - L109)  src/lib.rs(L163 - L179)  src/lib.rs(L220 - L237) 

Conditional Compilation Strategy

The guards use cfg_if macros to provide different implementations based on the target platform. This ensures that the crate can be used in both kernel and userspace contexts.

Target-Based Implementation Selection


Sources: src/lib.rs(L83 - L111)  src/lib.rs(L130 - L238) 

Usage Patterns

All guards follow the same basic usage pattern, differing only in their protection scope:

Guard TypeUse CaseDependencies
NoOpTesting, userspaceNone
IrqSaveInterrupt-safe critical sectionsArchitecture support
NoPreemptPreemption-safe critical sectionspreemptfeature +KernelGuardIfimpl
NoPreemptIrqSaveMaximum protection critical sectionsBoth above dependencies

The RAII pattern ensures automatic cleanup:

// Pattern from src/lib.rs:45-51
let guard = GuardType::new();
// Critical section automatically begins here
// ... critical section code ...
// Critical section automatically ends when guard is dropped

Sources: src/lib.rs(L12 - L19)  src/lib.rs(L45 - L51)  src/lib.rs(L181 - L237) 

Trait System

Relevant source files

This document explains the trait-based architecture that forms the foundation of the kernel_guard crate. The trait system defines the interfaces for implementing critical section guards and provides extension points for user code to integrate preemption control. For information about specific guard implementations, see RAII Guards. For details on architecture-specific implementations, see Multi-Architecture Support.

Core Trait Architecture

The kernel_guard crate is built around two primary traits that define the interface contracts for guard behavior and user integration.

BaseGuard Trait

The BaseGuard trait serves as the foundational interface that all guard types must implement. It defines the basic lifecycle operations for entering and exiting critical sections using the RAII pattern.

flowchart TD
BG["BaseGuard trait"]
ST["Associated Type: State"]
ACQ["acquire() -> State"]
REL["release(state: State)"]
CLONE["Clone + Copy bounds"]
BEFORE["Execute before critical section"]
AFTER["Execute after critical section"]
IMPL1["NoOp"]
IMPL2["IrqSave"]
IMPL3["NoPreempt"]
IMPL4["NoPreemptIrqSave"]

ACQ --> BEFORE
BG --> ACQ
BG --> REL
BG --> ST
IMPL1 --> BG
IMPL2 --> BG
IMPL3 --> BG
IMPL4 --> BG
REL --> AFTER
ST --> CLONE

BaseGuard Trait Definition

The trait defines three key components at src/lib.rs(L68 - L78) :

ComponentPurposeRequirements
Stateassociated typeStores state information during critical sectionMust implementClone + Copy
acquire()methodPerforms setup operations before critical sectionReturnsStatevalue
release(state)methodPerforms cleanup operations after critical sectionConsumes savedState

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

KernelGuardIf Trait

The KernelGuardIf trait provides an extension point for user code to implement preemption control functionality. This trait uses the crate_interface mechanism to enable runtime dispatch to user-provided implementations.

flowchart TD
DEF["KernelGuardIf trait definition"]
ENABLE["enable_preempt()"]
DISABLE["disable_preempt()"]
IMPL["User Implementation"]
CRATE_IMPL["#[crate_interface::impl_interface]"]
RUNTIME["Runtime dispatch"]
CALL1["NoPreempt::acquire()"]
DISPATCH1["crate_interface::call_interface!"]
CALL2["NoPreempt::release()"]
DISPATCH2["crate_interface::call_interface!"]

CALL1 --> DISPATCH1
CALL2 --> DISPATCH2
CRATE_IMPL --> RUNTIME
DEF --> DISABLE
DEF --> ENABLE
DISPATCH1 --> RUNTIME
DISPATCH2 --> RUNTIME
IMPL --> CRATE_IMPL
RUNTIME --> IMPL

Interface Definition

The trait is defined at src/lib.rs(L59 - L66)  with two required methods:

  • enable_preempt(): Called when preemption should be re-enabled
  • disable_preempt(): Called when preemption should be disabled

Sources: src/lib.rs(L59 - L66) 

Runtime Dispatch Mechanism

The crate uses the crate_interface library to implement a stable interface between the kernel_guard crate and user code. This mechanism allows the library to call user-provided functions at runtime without compile-time dependencies.

crate_interface Integration

Interface Declaration

The trait is declared as an interface using #[crate_interface::def_interface] at src/lib.rs(L59)  which generates the necessary infrastructure for runtime dispatch.

User Implementation Pattern

Users must implement the trait using the #[crate_interface::impl_interface] attribute, as shown in the example at src/lib.rs(L35 - L43) :

#![allow(unused)]
fn main() {
#[crate_interface::impl_interface]
impl KernelGuardIf for KernelGuardIfImpl {
    fn enable_preempt() { /* implementation */ }
    fn disable_preempt() { /* implementation */ }
}
}

Runtime Calls

The library invokes user implementations using crate_interface::call_interface! macro at several locations:

Sources: src/lib.rs(L59)  src/lib.rs(L35 - L43)  src/lib.rs(L154)  src/lib.rs(L159)  src/lib.rs(L168)  src/lib.rs(L177) 

Trait Implementation Patterns

Feature-Conditional Implementation

The KernelGuardIf calls are conditionally compiled based on the preempt feature flag. When the feature is disabled, the calls become no-ops:


Conditional Compilation Points:

Sources: src/lib.rs(L153 - L154)  src/lib.rs(L158 - L159)  src/lib.rs(L167 - L168)  src/lib.rs(L176 - L177) 

State Management Patterns

Different guard types use different State type strategies:

Guard TypeState TypePurpose
NoOp()No state needed
IrqSaveusizeStores IRQ flags for restoration
NoPreempt()Preemption state managed externally
NoPreemptIrqSaveusizeStores IRQ flags, preemption managed externally

Sources: src/lib.rs(L114)  src/lib.rs(L135)  src/lib.rs(L150)  src/lib.rs(L164) 

Complete Trait Interaction Flow

The following diagram shows how the traits interact during a complete guard lifecycle:

sequenceDiagram
    participant UserCode as "User Code"
    participant GuardInstance as "Guard Instance"
    participant BaseGuardacquire as "BaseGuard::acquire()"
    participant archlocal_irq_ as "arch::local_irq_*"
    participant KernelGuardIf as "KernelGuardIf"
    participant UserImplementation as "User Implementation"

    UserCode ->> GuardInstance: new()
    GuardInstance ->> BaseGuardacquire: acquire()
    alt NoPreempt or NoPreemptIrqSave
        BaseGuardacquire ->> KernelGuardIf: crate_interface::call_interface!
        KernelGuardIf ->> UserImplementation: disable_preempt()
        UserImplementation -->> KernelGuardIf: ​
        KernelGuardIf -->> BaseGuardacquire: ​
    end
    alt IrqSave or NoPreemptIrqSave
        BaseGuardacquire ->> archlocal_irq_: local_irq_save_and_disable()
        archlocal_irq_ -->> BaseGuardacquire: flags: usize
    end
    BaseGuardacquire -->> GuardInstance: State
    GuardInstance -->> UserCode: Guard instance
    Note over UserCode: Critical Section Execution
    UserCode ->> GuardInstance: drop()
    GuardInstance ->> BaseGuardacquire: release(state)
    alt IrqSave or NoPreemptIrqSave
        BaseGuardacquire ->> archlocal_irq_: local_irq_restore(flags)
        archlocal_irq_ -->> BaseGuardacquire: ​
    end
    alt NoPreempt or NoPreemptIrqSave
        BaseGuardacquire ->> KernelGuardIf: crate_interface::call_interface!
        KernelGuardIf ->> UserImplementation: enable_preempt()
        UserImplementation -->> KernelGuardIf: ​
        KernelGuardIf -->> BaseGuardacquire: ​
    end
    BaseGuardacquire -->> GuardInstance: ​
    GuardInstance -->> UserCode: ​

Sources: src/lib.rs(L183 - L185)  src/lib.rs(L202 - L205)  src/lib.rs(L222 - L224)  src/lib.rs(L134 - L147)  src/lib.rs(L149 - L161)  src/lib.rs(L163 - L179) 

Multi-Architecture Support

Relevant source files

This document covers how the kernel_guard crate provides cross-platform support for multiple CPU architectures through conditional compilation and architecture-specific implementations. It explains the architecture selection mechanism, supported platforms, and how platform-specific interrupt control is abstracted behind a unified interface.

For detailed implementation specifics of each architecture, see Architecture Abstraction Layer, x86/x86_64 Implementation, RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For information about how these implementations integrate with the core guard system, see Core Architecture.

Architecture Selection Mechanism

The kernel_guard crate uses the cfg_if macro to conditionally compile architecture-specific code based on the target platform. The selection logic is centralized in the architecture module, which determines which platform-specific implementation to include at compile time.

flowchart TD
CfgIf["cfg_if::cfg_if! macro"]
X86Check["target_arch = 'x86' or 'x86_64'"]
RiscvCheck["target_arch = 'riscv32' or 'riscv64'"]
ArmCheck["target_arch = 'aarch64'"]
LoongCheck["target_arch = 'loongarch64'"]
X86Mod["mod x86"]
RiscvMod["mod riscv"]
ArmMod["mod aarch64"]
LoongMod["mod loongarch64"]
X86Use["pub use self::x86::*"]
RiscvUse["pub use self::riscv::*"]
ArmUse["pub use self::aarch64::*"]
LoongUse["pub use self::loongarch64::*"]
UnifiedApi["Unified arch:: interface"]

ArmCheck --> ArmMod
ArmMod --> ArmUse
ArmUse --> UnifiedApi
CfgIf --> ArmCheck
CfgIf --> LoongCheck
CfgIf --> RiscvCheck
CfgIf --> X86Check
LoongCheck --> LoongMod
LoongMod --> LoongUse
LoongUse --> UnifiedApi
RiscvCheck --> RiscvMod
RiscvMod --> RiscvUse
RiscvUse --> UnifiedApi
X86Check --> X86Mod
X86Mod --> X86Use
X86Use --> UnifiedApi

The conditional compilation ensures that only the relevant architecture-specific code is included in the final binary, reducing both compile time and binary size. Each architecture module provides the same interface functions but with platform-specific implementations.

Sources: src/arch/mod.rs(L3 - L17) 

Supported Architectures

The crate currently supports four major CPU architecture families, each with specific register and instruction handling for interrupt control:

ArchitectureTarget IdentifiersKey RegistersInstruction Set
x86/x86_64x86,x86_64EFLAGScli,sti
RISC-Vriscv32,riscv64sstatusCSRcsrrc,csrrs
AArch64aarch64DAIFmrs,msr
LoongArch64loongarch64CSRcsrxchg

Each architecture implementation provides the same core functions: local_irq_save, local_irq_restore, local_irq_enable, and local_irq_disable. The specific mechanism for manipulating interrupt state varies by platform but the interface remains consistent.

Sources: src/arch/mod.rs(L4 - L16) 

Conditional Compilation Strategy

The architecture abstraction uses a hierarchical conditional compilation strategy that first selects the appropriate architecture module, then re-exports its symbols to create a unified interface:

flowchart TD
CompileTime["Compile Time"]
TargetArch["target_arch evaluation"]
ModSelection["Module Selection"]
X86Path["x86.rs module"]
RiscvPath["riscv.rs module"]
Aarch64Path["aarch64.rs module"]
LoongPath["loongarch64.rs module"]
ReExport["pub use self::arch::*"]
GuardImpl["Guard implementations access arch functions"]
IrqSave["IrqSave::acquire() calls arch::local_irq_save()"]
NoPreemptIrqSave["NoPreemptIrqSave::acquire() calls arch functions"]

Aarch64Path --> ReExport
CompileTime --> TargetArch
GuardImpl --> IrqSave
GuardImpl --> NoPreemptIrqSave
LoongPath --> ReExport
ModSelection --> Aarch64Path
ModSelection --> LoongPath
ModSelection --> RiscvPath
ModSelection --> X86Path
ReExport --> GuardImpl
RiscvPath --> ReExport
TargetArch --> ModSelection
X86Path --> ReExport

This design allows the core guard implementations to remain architecture-agnostic while delegating platform-specific operations to the appropriate architecture module. The cfg_if macro ensures clean compilation without unused code warnings on platforms where certain modules are not applicable.

Sources: src/arch/mod.rs(L1 - L17)  Cargo.toml(L19) 

Dead Code Handling

The architecture module includes a conditional compilation attribute to handle scenarios where architecture-specific code might not be used on certain targets:

#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))]

This attribute prevents compiler warnings when building for non-bare-metal targets (where target_os != "none"), as some architecture-specific functionality may not be utilized in hosted environments where the operating system handles interrupt management.

Sources: src/arch/mod.rs(L1) 

Integration Points

The multi-architecture support integrates with the broader kernel_guard system through several key mechanisms:

  • Guard Implementations: The IrqSave and NoPreemptIrqSave guards call architecture-specific functions through the unified arch:: interface
  • Feature Gates: The cfg_if dependency enables the conditional compilation logic
  • Target OS Detection: The crate can detect bare-metal vs. hosted environments and adjust behavior accordingly
  • Interface Consistency: All architecture modules provide the same function signatures, ensuring guard implementations work across platforms

This architecture enables the same guard code to work across multiple CPU architectures while maintaining platform-specific optimizations for interrupt control.

Sources: src/arch/mod.rs(L3 - L17)  Cargo.toml(L19) 

Architecture Abstraction Layer

Relevant source files

Purpose and Scope

The Architecture Abstraction Layer provides a unified interface for interrupt control across multiple CPU architectures through conditional compilation. This module serves as the entry point that selects and re-exports the appropriate architecture-specific implementation based on the target compilation architecture.

For details on specific architecture implementations, see x86/x86_64 Implementation, RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For information about how these implementations are used by the core guard system, see RAII Guards.

Conditional Compilation Strategy

The architecture abstraction layer uses the cfg_if macro to perform compile-time architecture selection. This approach ensures that only the relevant code for the target architecture is included in the final binary, reducing both code size and complexity.

Architecture Selection Flow

flowchart TD
START["Compilation Start"]
CFGIF["cfg_if::cfg_if! macro"]
X86_CHECK["target_arch = x86 or x86_64?"]
RISCV_CHECK["target_arch = riscv32 or riscv64?"]
ARM_CHECK["target_arch = aarch64?"]
LOONG_CHECK["target_arch = loongarch64?"]
X86_MOD["mod x86"]
X86_USE["pub use self::x86::*"]
RISCV_MOD["mod riscv"]
RISCV_USE["pub use self::riscv::*"]
ARM_MOD["mod aarch64"]
ARM_USE["pub use self::aarch64::*"]
LOONG_MOD["mod loongarch64"]
LOONG_USE["pub use self::loongarch64::*"]
UNIFIED["Unified arch:: namespace"]
GUARDS["Used by guard implementations"]

ARM_CHECK --> ARM_MOD
ARM_MOD --> ARM_USE
ARM_USE --> UNIFIED
CFGIF --> ARM_CHECK
CFGIF --> LOONG_CHECK
CFGIF --> RISCV_CHECK
CFGIF --> X86_CHECK
LOONG_CHECK --> LOONG_MOD
LOONG_MOD --> LOONG_USE
LOONG_USE --> UNIFIED
RISCV_CHECK --> RISCV_MOD
RISCV_MOD --> RISCV_USE
RISCV_USE --> UNIFIED
START --> CFGIF
UNIFIED --> GUARDS
X86_CHECK --> X86_MOD
X86_MOD --> X86_USE
X86_USE --> UNIFIED

Sources: src/arch/mod.rs(L3 - L17) 

Module Structure and Re-exports

The abstraction layer follows a simple but effective pattern where each supported architecture has its own module that implements the same interface. The main module conditionally includes and re-exports the appropriate implementation:

Target ArchitectureConditionModuleRe-export
x86 32-bittarget_arch = "x86"mod x86pub use self::x86::*
x86 64-bittarget_arch = "x86_64"mod x86pub use self::x86::*
RISC-V 32-bittarget_arch = "riscv32"mod riscvpub use self::riscv::*
RISC-V 64-bittarget_arch = "riscv64"mod riscvpub use self::riscv::*
AArch64target_arch = "aarch64"mod aarch64pub use self::aarch64::*
LoongArch64target_arch = "loongarch64"mod loongarch64pub use self::loongarch64::*

Sources: src/arch/mod.rs(L4 - L16) 

Code Entity Mapping

flowchart TD
subgraph FUNCTIONS["Exported functions"]
    LOCAL_IRQ_SAVE["local_irq_save()"]
    LOCAL_IRQ_RESTORE["local_irq_restore()"]
end
subgraph ARCH_MODULES["Architecture-specific modules"]
    X86_RS["src/arch/x86.rs"]
    RISCV_RS["src/arch/riscv.rs"]
    ARM_RS["src/arch/aarch64.rs"]
    LOONG_RS["src/arch/loongarch64.rs"]
end
subgraph MOD_RS["src/arch/mod.rs"]
    CFGIF_MACRO["cfg_if::cfg_if!"]
    X86_BRANCH["x86/x86_64 branch"]
    RISCV_BRANCH["riscv32/riscv64 branch"]
    ARM_BRANCH["aarch64 branch"]
    LOONG_BRANCH["loongarch64 branch"]
end

ARM_BRANCH --> ARM_RS
ARM_RS --> LOCAL_IRQ_RESTORE
ARM_RS --> LOCAL_IRQ_SAVE
CFGIF_MACRO --> ARM_BRANCH
CFGIF_MACRO --> LOONG_BRANCH
CFGIF_MACRO --> RISCV_BRANCH
CFGIF_MACRO --> X86_BRANCH
LOONG_BRANCH --> LOONG_RS
LOONG_RS --> LOCAL_IRQ_RESTORE
LOONG_RS --> LOCAL_IRQ_SAVE
RISCV_BRANCH --> RISCV_RS
RISCV_RS --> LOCAL_IRQ_RESTORE
RISCV_RS --> LOCAL_IRQ_SAVE
X86_BRANCH --> X86_RS
X86_RS --> LOCAL_IRQ_RESTORE
X86_RS --> LOCAL_IRQ_SAVE

Sources: src/arch/mod.rs(L1 - L18) 

Compiler Attributes and Target Handling

The module includes a conditional compiler attribute that suppresses warnings for unused code when targeting non-bare-metal environments:

#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))]

This attribute serves an important purpose:

  • When target_os = "none" (bare metal/no_std environments), the architecture-specific code is actively used
  • When targeting other operating systems (like Linux), the interrupt control functions may not be used by the guard implementations, leading to dead code warnings
  • The attribute prevents these warnings without removing the code, maintaining consistency across build targets

Sources: src/arch/mod.rs(L1) 

Integration with Core Library

The architecture abstraction layer provides the low-level interrupt control functions that are consumed by the guard implementations in the core library. The local_irq_save() and local_irq_restore() functions exposed through this layer are called by:

  • IrqSave guard for interrupt-only protection
  • NoPreemptIrqSave guard for combined interrupt and preemption protection

This design ensures that the same high-level guard interface can work across all supported architectures while utilizing the most efficient interrupt control mechanism available on each platform.

Sources: src/arch/mod.rs(L3 - L17) 

x86/x86_64 Implementation

Relevant source files

Purpose and Scope

This document covers the x86 and x86_64 architecture-specific implementation of interrupt control within the kernel_guard crate. The x86 implementation provides low-level primitives for disabling and restoring local interrupts by manipulating the EFLAGS register using inline assembly.

This implementation is automatically selected when compiling for target_arch = "x86" or target_arch = "x86_64" through the conditional compilation system. For information about other architecture implementations, see RISC-V Implementation, AArch64 Implementation, and LoongArch64 Implementation. For details about the architecture selection mechanism, see Architecture Abstraction Layer.

x86 Interrupt Control Mechanism

The x86 architecture provides interrupt control through the Interrupt Flag (IF) bit in the EFLAGS register. When this bit is set, the processor responds to maskable hardware interrupts; when cleared, such interrupts are ignored.

x86 Architecture Integration

flowchart TD
CFGIF["cfg_if! macro evaluation"]
X86_CHECK["target_arch == x86 or x86_64?"]
X86_MOD["mod x86"]
OTHER_ARCH["Other architecture modules"]
REEXPORT["pub use self::arch::*"]
LOCAL_IRQ_SAVE["local_irq_save_and_disable()"]
LOCAL_IRQ_RESTORE["local_irq_restore()"]
IRQSAVE_GUARD["IrqSave guard"]
NOPREEMPT_IRQ_GUARD["NoPreemptIrqSave guard"]

CFGIF --> X86_CHECK
LOCAL_IRQ_RESTORE --> IRQSAVE_GUARD
LOCAL_IRQ_RESTORE --> NOPREEMPT_IRQ_GUARD
LOCAL_IRQ_SAVE --> IRQSAVE_GUARD
LOCAL_IRQ_SAVE --> NOPREEMPT_IRQ_GUARD
OTHER_ARCH --> REEXPORT
REEXPORT --> LOCAL_IRQ_RESTORE
REEXPORT --> LOCAL_IRQ_SAVE
X86_CHECK --> OTHER_ARCH
X86_CHECK --> X86_MOD
X86_MOD --> REEXPORT

Sources: src/arch/x86.rs(L1 - L21) 

Implementation Details

The x86 implementation consists of two core functions that manipulate the EFLAGS register through inline assembly.

Interrupt Flag Constants

The implementation defines the Interrupt Flag bit position as a constant:

ConstantValuePurpose
IF_BIT1 << 9Bit mask for the Interrupt Flag in EFLAGS register

Core Functions

local_irq_save_and_disable()

This function atomically saves the current interrupt state and disables interrupts. It returns the previous state of the Interrupt Flag for later restoration.

Implementation Strategy:

  • Uses pushf instruction to push EFLAGS onto the stack
  • Uses pop instruction to retrieve EFLAGS value into a register
  • Uses cli instruction to clear the Interrupt Flag
  • Returns only the IF bit status (masked with IF_BIT)

local_irq_restore(flags)

This function restores the interrupt state based on the previously saved flags value.

Implementation Strategy:

  • Checks if the saved flags indicate interrupts were enabled
  • Uses sti instruction to set the Interrupt Flag if interrupts should be enabled
  • Uses cli instruction to clear the Interrupt Flag if interrupts should remain disabled

Interrupt Control Flow

flowchart TD
ACQUIRE["Guard Acquire"]
SAVE_DISABLE["local_irq_save_and_disable()"]
PUSHF["pushf (push EFLAGS)"]
POP["pop reg (get EFLAGS value)"]
CLI["cli (disable interrupts)"]
MASK["flags & IF_BIT"]
RETURN_FLAGS["return saved_flags"]
CRITICAL["Critical Section Execution"]
RELEASE["Guard Release"]
RESTORE["local_irq_restore(saved_flags)"]
CHECK_FLAGS["saved_flags != 0?"]
STI["sti (enable interrupts)"]
CLI_RESTORE["cli (keep disabled)"]
COMPLETE["Interrupts Restored"]

ACQUIRE --> SAVE_DISABLE
CHECK_FLAGS --> CLI_RESTORE
CHECK_FLAGS --> STI
CLI --> MASK
CLI_RESTORE --> COMPLETE
CRITICAL --> RELEASE
MASK --> RETURN_FLAGS
POP --> CLI
PUSHF --> POP
RELEASE --> RESTORE
RESTORE --> CHECK_FLAGS
RETURN_FLAGS --> CRITICAL
SAVE_DISABLE --> PUSHF
STI --> COMPLETE

Sources: src/arch/x86.rs(L7 - L20) 

Assembly Instructions Reference

The x86 implementation uses specific assembly instructions for interrupt control:

InstructionPurposeUsage in Implementation
pushfPush EFLAGS register onto stackSave current interrupt state
popPop value from stack into registerRetrieve EFLAGS value
cliClear Interrupt FlagDisable maskable interrupts
stiSet Interrupt FlagEnable maskable interrupts

EFLAGS Register Structure

The implementation specifically targets bit 9 of the EFLAGS register:

flowchart TD
EFLAGS["EFLAGS Register (32/64-bit)"]
BIT9["Bit 9: Interrupt Flag (IF)"]
ENABLED["1 = Interrupts Enabled"]
DISABLED["0 = Interrupts Disabled"]
IF_BIT_CONST["IF_BIT = 1 << 9"]
MASK_OP["flags & IF_BIT"]
IF_STATE["Extract IF state"]

BIT9 --> DISABLED
BIT9 --> ENABLED
EFLAGS --> BIT9
IF_BIT_CONST --> BIT9
MASK_OP --> IF_STATE

Sources: src/arch/x86.rs(L3 - L4)  src/arch/x86.rs(L10) 

Integration with Guard System

The x86 interrupt control functions integrate directly with the RAII guard implementations. When guards are created in bare-metal environments (target_os = "none"), they call these architecture-specific functions to provide actual interrupt control functionality.

Function Usage by Guards:

  • IrqSave guard calls local_irq_save_and_disable() on creation and local_irq_restore() on drop
  • NoPreemptIrqSave guard uses the same interrupt control functions in addition to preemption control

The inline assembly ensures minimal overhead and atomic operation for critical section protection in kernel environments.

Sources: src/arch/x86.rs(L1 - L21) 

RISC-V Implementation

Relevant source files

This document covers the RISC-V-specific implementation of interrupt control functionality within the kernel_guard crate. The RISC-V implementation provides low-level interrupt disable/restore operations by manipulating the sstatus Control and Status Register (CSR), specifically the Supervisor Interrupt Enable (SIE) bit.

For the overall architecture abstraction mechanism, see Architecture Abstraction Layer. For other architecture implementations, see x86/x86_64 Implementation, AArch64 Implementation, and LoongArch64 Implementation.

RISC-V CSR Architecture Context

The RISC-V implementation operates within the Supervisor mode privilege level, using the sstatus CSR to control interrupt delivery. The implementation focuses on atomic manipulation of the SIE bit to provide safe critical section entry and exit.

RISC-V Architecture Selection Flow

flowchart TD
cfg_if["cfg_if! macro evaluation"]
riscv_check["target_arch = riscv32 or riscv64?"]
riscv_mod["mod riscv;"]
other_arch["Other architecture modules"]
pub_use["pub use self::riscv::*;"]
local_irq_save["local_irq_save_and_disable()"]
local_irq_restore["local_irq_restore()"]
guard_acquire["BaseGuard::acquire()"]
guard_release["BaseGuard::release()"]
irq_save_guard["IrqSave guard"]
no_preempt_irq_save["NoPreemptIrqSave guard"]

cfg_if --> riscv_check
guard_acquire --> irq_save_guard
guard_acquire --> no_preempt_irq_save
guard_release --> irq_save_guard
guard_release --> no_preempt_irq_save
local_irq_restore --> guard_release
local_irq_save --> guard_acquire
pub_use --> local_irq_restore
pub_use --> local_irq_save
riscv_check --> other_arch
riscv_check --> riscv_mod
riscv_mod --> pub_use

Sources: src/arch/mod.rs(L1 - L50)  src/arch/riscv.rs(L1 - L19) 

CSR Manipulation Implementation

The RISC-V implementation consists of two core functions that provide atomic interrupt state management through direct CSR manipulation.

Core Function Mapping

flowchart TD
subgraph subGraph2["Hardware Registers"]
    sstatus_reg["sstatus CSR"]
    sie_bit["SIE bit (bit 1)"]
end
subgraph subGraph1["RISC-V Assembly"]
    csrrc_instr["csrrc instruction"]
    csrrs_instr["csrrs instruction"]
end
subgraph subGraph0["Code Functions"]
    save_disable["local_irq_save_and_disable()"]
    restore["local_irq_restore(flags)"]
end

csrrc_instr --> sstatus_reg
csrrs_instr --> sstatus_reg
restore --> csrrs_instr
save_disable --> csrrc_instr
sstatus_reg --> sie_bit

Sources: src/arch/riscv.rs(L6 - L18) 

Assembly Instruction Analysis

The implementation uses RISC-V CSR atomic read-modify-write instructions to ensure interrupt state changes are atomic and cannot be interrupted.

CSR Instruction Details

FunctionAssembly InstructionOperationPurpose
local_irq_save_and_disable()csrrc {}, sstatus, {}Clear bits and read old valueAtomically disable interrupts and save state
local_irq_restore()csrrs x0, sstatus, {}Set bits, discard resultAtomically restore interrupt state

The SIE_BIT constant is defined as 1 << 1, targeting bit 1 of the sstatus register which controls Supervisor Interrupt Enable.

CSR Operation Flow

sequenceDiagram
    participant IrqSaveGuard as "IrqSave Guard"
    participant local_irq_save_and_disable as "local_irq_save_and_disable()"
    participant sstatusCSR as "sstatus CSR"
    participant local_irq_restore as "local_irq_restore()"

    IrqSaveGuard ->> local_irq_save_and_disable: acquire()
    local_irq_save_and_disable ->> sstatusCSR: csrrc flags, sstatus, SIE_BIT
    sstatusCSR -->> local_irq_save_and_disable: return old sstatus value
    local_irq_save_and_disable -->> IrqSaveGuard: return (flags & SIE_BIT)
    Note over IrqSaveGuard: Critical section execution
    IrqSaveGuard ->> local_irq_restore: release(flags)
    local_irq_restore ->> sstatusCSR: csrrs x0, sstatus, flags
    sstatusCSR -->> local_irq_restore: (discard result)

Sources: src/arch/riscv.rs(L7 - L11)  src/arch/riscv.rs(L15 - L17) 

Implementation Details

Interrupt Save and Disable

The local_irq_save_and_disable() function src/arch/riscv.rs(L7 - L12)  performs an atomic clear-and-read operation:

  • Uses inline assembly with csrrc (CSR Read and Clear bits)
  • Clears the SIE_BIT in the sstatus register
  • Returns only the relevant bit state (flags & SIE_BIT) rather than the entire register
  • Ensures the returned value is either 0 (interrupts were disabled) or SIE_BIT (interrupts were enabled)

Interrupt Restore

The local_irq_restore() function src/arch/riscv.rs(L15 - L18)  performs an atomic set operation:

  • Uses inline assembly with csrrs (CSR Read and Set bits)
  • Sets bits specified by the flags parameter in the sstatus register
  • Uses x0 as the destination register to discard the read result
  • Only modifies the interrupt state without returning any value

Register Usage and Safety

The implementation uses specific register constraints:

  • out(reg) for capturing the old CSR value
  • in(reg) for providing the restore flags
  • const SIE_BIT for compile-time constant bit mask
  • All operations are marked unsafe due to direct hardware manipulation

Sources: src/arch/riscv.rs(L1 - L19) 

AArch64 Implementation

Relevant source files

This document details the AArch64-specific implementation of interrupt control mechanisms within the kernel_guard crate. It covers the ARM64 architecture's DAIF register manipulation and the assembly instructions used to save and restore interrupt states for RAII guard implementations.

For information about the overall architecture abstraction system, see Architecture Abstraction Layer. For comparisons with other CPU architectures, see x86/x86_64 Implementation, RISC-V Implementation, and LoongArch64 Implementation.

DAIF Register and Interrupt Control

The AArch64 implementation centers around the DAIF (Debug, SError, IRQ, FIQ) system register, which controls various exception types. The kernel_guard crate specifically manipulates the IRQ (Interrupt Request) bit to implement critical sections.

DAIF Register Structure

flowchart TD
subgraph subGraph2["Assembly Instructions"]
    MRS["mrs (Move from System Register)Read DAIF value"]
    MSR_SET["msr daifset, #2Set I bit (disable IRQs)"]
    MSR_RESTORE["msr daif, registerRestore full DAIF"]
end
subgraph subGraph1["kernel_guard Operations"]
    SAVE["local_irq_save_and_disable()Read DAIF + Set I bit"]
    RESTORE["local_irq_restore(flags)Write saved DAIF"]
end
subgraph subGraph0["DAIF Register (64-bit)"]
    D["D (bit 9)Debug exceptions"]
    A["A (bit 8)SError interrupts"]
    I["I (bit 7)IRQ interrupts"]
    F["F (bit 6)FIQ interrupts"]
end

MRS --> A
MRS --> D
MRS --> F
MRS --> I
MSR_RESTORE --> A
MSR_RESTORE --> D
MSR_RESTORE --> F
MSR_RESTORE --> I
MSR_SET --> I
RESTORE --> MSR_RESTORE
SAVE --> MRS
SAVE --> MSR_SET

Sources: src/arch/aarch64.rs(L1 - L15) 

Core Implementation Functions

The AArch64 implementation provides two primary functions that form the foundation for all interrupt-disabling guards on this architecture.

Interrupt Save and Disable

The local_irq_save_and_disable function captures the current interrupt state and disables IRQs atomically:

FunctionReturn TypeOperationAssembly Instructions
local_irq_save_and_disableusizeSave DAIF, disable IRQsmrs {}, daif; msr daifset, #2

The function uses two ARM64 instructions in sequence:

  1. mrs {}, daif - Move the DAIF register value to a general-purpose register
  2. msr daifset, #2 - Set bit 1 (which corresponds to the IRQ mask bit) in DAIF

Interrupt Restore

The local_irq_restore function restores the previously saved interrupt state:

FunctionParametersOperationAssembly Instructions
local_irq_restoreflags: usizeRestore DAIFmsr daif, {}

This function directly writes the saved flags back to the DAIF register, restoring the complete interrupt state.

Sources: src/arch/aarch64.rs(L3 - L14) 

Integration with Guard System

The AArch64 implementation integrates with the broader kernel_guard architecture through the conditional compilation system and provides the low-level primitives for RAII guards.

flowchart TD
subgraph subGraph3["RAII Pattern"]
    ACQUIRE["BaseGuard::acquire()"]
    DROP["Drop implementation"]
end
subgraph subGraph2["Guard Implementations"]
    IRQ_GUARD["IrqSave guard"]
    COMBO_GUARD["NoPreemptIrqSave guard"]
end
subgraph subGraph1["AArch64 Module (src/arch/aarch64.rs)"]
    SAVE_FN["local_irq_save_and_disable()"]
    RESTORE_FN["local_irq_restore(flags)"]
end
subgraph subGraph0["Architecture Selection"]
    CFG_IF["cfg_if! macro"]
    AARCH64_CHECK["target_arch = 'aarch64'"]
end

AARCH64_CHECK --> RESTORE_FN
AARCH64_CHECK --> SAVE_FN
CFG_IF --> AARCH64_CHECK
COMBO_GUARD --> ACQUIRE
COMBO_GUARD --> DROP
IRQ_GUARD --> ACQUIRE
IRQ_GUARD --> DROP
RESTORE_FN --> COMBO_GUARD
RESTORE_FN --> IRQ_GUARD
SAVE_FN --> COMBO_GUARD
SAVE_FN --> IRQ_GUARD

Sources: src/arch/aarch64.rs(L1 - L15) 

Assembly Instruction Details

The AArch64 implementation relies on specific ARM64 assembly instructions for system register manipulation:

System Register Access Instructions

InstructionPurposeSyntaxUsage in kernel_guard
mrsMove from System Registermrs Xt, system_regRead current DAIF state
msrMove to System Registermsr system_reg, XtWrite to DAIF register
msr(immediate)Move immediate to System Registermsr daifset, #immSet specific DAIF bits

Bit Manipulation Strategy

flowchart TD
subgraph subGraph2["Critical Section State"]
    BEFORE["IRQs enabled (I=0)"]
    DURING["IRQs disabled (I=1)"]
    AFTER["IRQs restored"]
end
subgraph subGraph1["daifset #2 Operation"]
    IMM2["Immediate value #2"]
    SHIFT["Left shift by 6 positions"]
    RESULT["Sets bit 7 (IRQ mask)"]
end
subgraph subGraph0["DAIF Bit Positions"]
    BIT9["Bit 9: Debug"]
    BIT8["Bit 8: SError"]
    BIT7["Bit 7: IRQ"]
    BIT6["Bit 6: FIQ"]
end

BEFORE --> DURING
BIT7 --> DURING
DURING --> AFTER
IMM2 --> SHIFT
RESULT --> BIT7
SHIFT --> RESULT

The daifset #2 instruction specifically targets the IRQ bit by using immediate value 2, which when processed by the instruction becomes a mask for bit 7 of the DAIF register.

Sources: src/arch/aarch64.rs(L7 - L13) 

Safety and Inline Optimization

Both functions are marked with #[inline] for performance optimization and use unsafe blocks for assembly code execution:

  • Inline annotation: Ensures the assembly instructions are inlined at call sites for minimal overhead
  • Unsafe blocks: Required for all inline assembly operations in Rust
  • Register constraints: Uses out(reg) and in(reg) to specify register allocation for the assembler

The implementation assumes that the DAIF register is accessible in the current execution context, which is typically true for kernel-level code running at EL1 (Exception Level 1) or higher privilege levels.

Sources: src/arch/aarch64.rs(L3 - L14) 

LoongArch64 Implementation

Relevant source files

Purpose and Scope

This document covers the LoongArch64-specific implementation of interrupt control mechanisms within the kernel_guard crate. The LoongArch64 implementation provides platform-specific functions for saving, disabling, and restoring interrupt states using Control Status Register (CSR) manipulation.

For general architecture abstraction concepts, see Architecture Abstraction Layer. For other architecture implementations, see x86/x86_64 Implementation, RISC-V Implementation, and AArch64 Implementation.

Architecture Integration

The LoongArch64 implementation is conditionally compiled as part of the multi-architecture support system. It provides the same interface as other architectures but uses LoongArch64-specific Control Status Registers and the csrxchg instruction for atomic CSR exchange operations.

Architecture Selection Flow

flowchart TD
CFGIF["cfg_if! macro evaluation"]
LOONG_CHECK["target_arch = loongarch64?"]
LOONG_MOD["mod loongarch64"]
OTHER_ARCH["Other architecture modules"]
PUB_USE["pub use self::loongarch64::*"]
LOCAL_IRQ_SAVE["local_irq_save_and_disable()"]
LOCAL_IRQ_RESTORE["local_irq_restore()"]
IRQ_SAVE_GUARD["IrqSave guard"]
NO_PREEMPT_IRQ_SAVE["NoPreemptIrqSave guard"]

CFGIF --> LOONG_CHECK
IRQ_SAVE_GUARD --> NO_PREEMPT_IRQ_SAVE
LOCAL_IRQ_RESTORE --> IRQ_SAVE_GUARD
LOCAL_IRQ_SAVE --> IRQ_SAVE_GUARD
LOONG_CHECK --> LOONG_MOD
LOONG_CHECK --> OTHER_ARCH
LOONG_MOD --> PUB_USE
PUB_USE --> LOCAL_IRQ_RESTORE
PUB_USE --> LOCAL_IRQ_SAVE

Sources: src/arch/loongarch64.rs(L1 - L18) 

Interrupt Control Implementation

The LoongArch64 implementation centers around two core functions that manipulate the interrupt enable bit in Control Status Registers using the atomic csrxchg instruction.

Core Functions

FunctionPurposeReturn Value
local_irq_save_and_disable()Save current interrupt state and disable interruptsPrevious interrupt enable state (masked)
local_irq_restore(flags)Restore interrupt state from saved flagsNone

CSR Manipulation Details

flowchart TD
subgraph local_irq_restore["local_irq_restore"]
    RESTORE_START["Function entryflags parameter"]
    CSRXCHG_RESTORE["csrxchg {}, {}, 0x0in(reg) flagsin(reg) IE_MASK"]
    RESTORE_END["Function return"]
end
subgraph local_irq_save_and_disable["local_irq_save_and_disable"]
    SAVE_START["Function entry"]
    FLAGS_INIT["flags: usize = 0"]
    CSRXCHG_SAVE["csrxchg {}, {}, 0x0inout(reg) flagsin(reg) IE_MASK"]
    MASK_RETURN["Return flags & IE_MASK"]
end
subgraph subGraph0["LoongArch64 CSR Operations"]
    IE_MASK["IE_MASK constant1 << 2 (bit 2)"]
    CSRXCHG["csrxchg instructionAtomic CSR exchange"]
end

CSRXCHG_RESTORE --> RESTORE_END
CSRXCHG_SAVE --> MASK_RETURN
FLAGS_INIT --> CSRXCHG_SAVE
IE_MASK --> CSRXCHG_RESTORE
IE_MASK --> CSRXCHG_SAVE
RESTORE_START --> CSRXCHG_RESTORE
SAVE_START --> FLAGS_INIT

Sources: src/arch/loongarch64.rs(L3 - L17) 

Control Status Register (CSR) Operations

The implementation uses CSR address 0x0 (likely CRMD - Current Mode) with the csrxchg instruction for atomic read-modify-write operations on the interrupt enable bit.

Interrupt Enable Bit Manipulation

The IE_MASK constant defines bit 2 as the interrupt enable bit:

flowchart TD
subgraph subGraph1["IE_MASK Constant"]
    MASK_VAL["1 << 2 = 0x4"]
    MASK_PURPOSE["Isolates IE bitfor CSR operations"]
end
subgraph subGraph0["CSR Bit Layout"]
    BIT0["Bit 0"]
    BIT1["Bit 1"]
    BIT2["Bit 2IE (Interrupt Enable)"]
    BIT3["Bit 3"]
    BITN["Bit N"]
end

BIT2 --> MASK_VAL
MASK_VAL --> MASK_PURPOSE

Sources: src/arch/loongarch64.rs(L3) 

Assembly Instruction Details

The csrxchg instruction performs atomic exchange operations:

  • Save Operation: csrxchg {flags}, {IE_MASK}, 0x0 clears the IE bit and returns the old CSR value
  • Restore Operation: csrxchg {flags}, {IE_MASK}, 0x0 sets the IE bit based on the flags parameter

The instruction syntax uses:

  • inout(reg) flags for read-modify-write of the flags variable
  • in(reg) IE_MASK for the bit mask input
  • 0x0 as the CSR address

Sources: src/arch/loongarch64.rs(L9 - L16) 

Integration with Guard System

The LoongArch64 functions integrate seamlessly with the kernel_guard's RAII guard system through the architecture abstraction layer.

Guard Usage Flow

sequenceDiagram
    participant IrqSaveGuard as "IrqSave Guard"
    participant ArchitectureLayer as "Architecture Layer"
    participant LoongArch64Implementation as "LoongArch64 Implementation"
    participant LoongArch64CSR as "LoongArch64 CSR"

    Note over IrqSaveGuard: Guard creation
    IrqSaveGuard ->> ArchitectureLayer: BaseGuard::acquire()
    ArchitectureLayer ->> LoongArch64Implementation: local_irq_save_and_disable()
    LoongArch64Implementation ->> LoongArch64CSR: csrxchg (clear IE)
    LoongArch64CSR -->> LoongArch64Implementation: Previous CSR value
    LoongArch64Implementation -->> ArchitectureLayer: Masked IE state
    ArchitectureLayer -->> IrqSaveGuard: Saved flags
    Note over IrqSaveGuard: Critical section execution
    Note over IrqSaveGuard: Guard destruction
    IrqSaveGuard ->> ArchitectureLayer: BaseGuard::release(flags)
    ArchitectureLayer ->> LoongArch64Implementation: local_irq_restore(flags)
    LoongArch64Implementation ->> LoongArch64CSR: csrxchg (restore IE)
    LoongArch64CSR -->> LoongArch64Implementation: Updated CSR
    LoongArch64Implementation -->> ArchitectureLayer: Return
    ArchitectureLayer -->> IrqSaveGuard: Return

Sources: src/arch/loongarch64.rs(L6 - L17) 

Integration Guide

Relevant source files

This page provides a practical guide for integrating the kernel_guard crate into your kernel or OS project. It covers dependency setup, feature configuration, and implementing the required interfaces to enable preemption control.

For detailed information about the available guard types and their behavior, see RAII Guards. For architecture-specific implementation details, see Multi-Architecture Support.

Integration Overview

The kernel_guard crate integration follows a two-phase approach: dependency configuration and interface implementation. The crate uses conditional compilation to provide different functionality levels based on target platform and enabled features.

Integration Flow Diagram

flowchart TD
START["Start Integration"]
CARGO["Add kernel_guard to Cargo.toml"]
FEATURE_CHOICE["Enable preempt feature?"]
IMPL_TRAIT["Implement KernelGuardIf trait"]
USE_GUARDS["Use IRQ-only guards"]
CRATE_INTERFACE["Use crate_interface::impl_interface"]
DEFINE_IMPL["Define enable_preempt() and disable_preempt()"]
FULL_GUARDS["Access all guard types"]
LIMITED_GUARDS["Access IrqSave and NoOp guards"]
USAGE["Use guards in critical sections"]
RUNTIME["Runtime protection active"]

CARGO --> FEATURE_CHOICE
CRATE_INTERFACE --> DEFINE_IMPL
DEFINE_IMPL --> FULL_GUARDS
FEATURE_CHOICE --> IMPL_TRAIT
FEATURE_CHOICE --> USE_GUARDS
FULL_GUARDS --> USAGE
IMPL_TRAIT --> CRATE_INTERFACE
LIMITED_GUARDS --> USAGE
START --> CARGO
USAGE --> RUNTIME
USE_GUARDS --> LIMITED_GUARDS

Sources: Cargo.toml(L14 - L16)  src/lib.rs(L58 - L66)  src/lib.rs(L83 - L111) 

Dependency Configuration

Add kernel_guard to your Cargo.toml dependencies section:

[dependencies]
kernel_guard = "0.1.2"

For preemptive systems that need both IRQ and preemption control:

[dependencies]
kernel_guard = { version = "0.1.2", features = ["preempt"] }

Supported Dependencies

DependencyVersionPurpose
cfg-if1.0Conditional compilation for architecture selection
crate_interface0.1Runtime interface dispatch forKernelGuardIf

Sources: Cargo.toml(L18 - L20) 

Interface Implementation Requirements

When the preempt feature is enabled, you must implement the KernelGuardIf trait to provide preemption control functionality.

KernelGuardIf Implementation Pattern

flowchart TD
subgraph subGraph2["Runtime Dispatch"]
    NOPREEMPT_ACQUIRE["NoPreempt::acquire()"]
    NOPREEMPT_RELEASE["NoPreempt::release()"]
    COMBINED_ACQUIRE["NoPreemptIrqSave::acquire()"]
    COMBINED_RELEASE["NoPreemptIrqSave::release()"]
end
subgraph subGraph1["kernel_guard Crate"]
    TRAIT_DEF["#[crate_interface::def_interface] trait KernelGuardIf"]
    CALL_SITES["crate_interface::call_interface! call sites"]
end
subgraph subGraph0["User Crate"]
    USER_STRUCT["struct KernelGuardIfImpl"]
    IMPL_MACRO["#[crate_interface::impl_interface]"]
    ENABLE_FN["fn enable_preempt()"]
    DISABLE_FN["fn disable_preempt()"]
end

CALL_SITES --> COMBINED_ACQUIRE
CALL_SITES --> COMBINED_RELEASE
CALL_SITES --> NOPREEMPT_ACQUIRE
CALL_SITES --> NOPREEMPT_RELEASE
DISABLE_FN --> CALL_SITES
ENABLE_FN --> CALL_SITES
IMPL_MACRO --> DISABLE_FN
IMPL_MACRO --> ENABLE_FN
TRAIT_DEF --> CALL_SITES
USER_STRUCT --> IMPL_MACRO

Sources: src/lib.rs(L59 - L66)  src/lib.rs(L153 - L159)  src/lib.rs(L167 - L177) 

Implementation Template

The following template shows the required implementation structure:

#![allow(unused)]
fn main() {
use kernel_guard::KernelGuardIf;

struct KernelGuardIfImpl;

#[crate_interface::impl_interface]
impl KernelGuardIf for KernelGuardIfImpl {
    fn enable_preempt() {
        // Platform-specific preemption enable code
    }
    
    fn disable_preempt() {
        // Platform-specific preemption disable code
    }
}
}

Sources: src/lib.rs(L30 - L43)  README.md(L36 - L49) 

Target Platform Considerations

The crate behavior varies based on the target platform:

Platform-Specific Guard Availability

TargetIrqSaveNoPreemptNoPreemptIrqSaveNotes
target_os = "none"✅ Full✅ Full*✅ Full*Real implementations
Other targets❌ NoOp alias❌ NoOp alias❌ NoOp aliasUser-mode safety

*Requires preempt feature and KernelGuardIf implementation

Sources: src/lib.rs(L83 - L111) 

Conditional Compilation Logic

flowchart TD
COMPILE_START["Compilation begins"]
TARGET_CHECK["target_os == 'none' || doc?"]
REAL_IMPL["Real guard implementations"]
ALIAS_IMPL["NoOp type aliases"]
PREEMPT_CHECK["preempt feature enabled?"]
FULL_PREEMPT["Full preemption control"]
IRQ_ONLY["IRQ control only"]
NOOP_GUARDS["All guards = NoOp"]
CALL_INTERFACE["crate_interface::call_interface!"]
ARCH_IRQ["arch::local_irq_* functions"]
USER_IMPL["User KernelGuardIf implementation"]
PLATFORM_CODE["Platform-specific IRQ code"]

ALIAS_IMPL --> NOOP_GUARDS
ARCH_IRQ --> PLATFORM_CODE
CALL_INTERFACE --> USER_IMPL
COMPILE_START --> TARGET_CHECK
FULL_PREEMPT --> CALL_INTERFACE
IRQ_ONLY --> ARCH_IRQ
PREEMPT_CHECK --> FULL_PREEMPT
PREEMPT_CHECK --> IRQ_ONLY
REAL_IMPL --> PREEMPT_CHECK
TARGET_CHECK --> ALIAS_IMPL
TARGET_CHECK --> REAL_IMPL

Sources: src/lib.rs(L83 - L111)  src/lib.rs(L130 - L238) 

Usage Patterns

Basic Guard Usage

All guards follow the RAII pattern where the critical section begins at guard creation and ends when the guard is dropped:

// IRQ protection only
let _guard = IrqSave::new();
// Critical section protected from interrupts

// Preemption protection (requires preempt feature + KernelGuardIf)
let _guard = NoPreempt::new();
// Critical section protected from preemption

// Combined protection
let _guard = NoPreemptIrqSave::new();
// Critical section protected from both interrupts and preemption

Integration Verification

To verify your integration is working correctly:

  1. Compilation Test: Ensure your project compiles with the desired feature set
  2. Runtime Test: Verify that critical sections actually disable the expected mechanisms
  3. Documentation Test: Use cargo doc to check that all guard types are available

Sources: src/lib.rs(L181 - L237)  README.md(L50 - L58) 

Feature Configuration

Relevant source files

This document explains the feature-based configuration system in the kernel_guard crate, specifically focusing on the preempt feature and its impact on guard availability and functionality. For information about implementing the required traits when using features, see Implementing KernelGuardIf.

Overview

The kernel_guard crate uses Cargo features to provide conditional functionality that adapts to different system requirements. The primary feature is preempt, which enables kernel preemption control capabilities in guard implementations. This feature system allows the crate to be used in both simple interrupt-only scenarios and more complex preemptive kernel environments.

Feature Definitions

The crate defines its features in the Cargo manifest with a minimal configuration approach:

FeatureDefaultPurpose
preemptDisabledEnables kernel preemption control in applicable guards
defaultEmptyNo features enabled by default

Sources: Cargo.toml(L14 - L16) 

Preempt Feature Behavior

Feature Impact on Guard Implementations

The preempt feature primarily affects two guard types: NoPreempt and NoPreemptIrqSave. When this feature is enabled, these guards will attempt to disable and re-enable kernel preemption around critical sections.

Compilation-Time Feature Resolution

flowchart TD
COMPILE["Compilation Start"]
FEATURE_CHECK["preempt featureenabled?"]
PREEMPT_ENABLED["Preemption ControlActive"]
PREEMPT_DISABLED["Preemption ControlNo-Op"]
NOPRE_IMPL["NoPreempt::acquire()calls KernelGuardIf::disable_preempt"]
NOPRE_REL["NoPreempt::release()calls KernelGuardIf::enable_preempt"]
NOPRE_NOOP["NoPreempt::acquire()no operation"]
NOPRE_NOOP_REL["NoPreempt::release()no operation"]
COMBO_IMPL["NoPreemptIrqSave::acquire()calls disable_preempt + IRQ disable"]
COMBO_REL["NoPreemptIrqSave::release()IRQ restore + enable_preempt"]
COMBO_IRQ["NoPreemptIrqSave::acquire()IRQ disable only"]
COMBO_IRQ_REL["NoPreemptIrqSave::release()IRQ restore only"]

COMPILE --> FEATURE_CHECK
FEATURE_CHECK --> PREEMPT_DISABLED
FEATURE_CHECK --> PREEMPT_ENABLED
PREEMPT_DISABLED --> COMBO_IRQ
PREEMPT_DISABLED --> COMBO_IRQ_REL
PREEMPT_DISABLED --> NOPRE_NOOP
PREEMPT_DISABLED --> NOPRE_NOOP_REL
PREEMPT_ENABLED --> COMBO_IMPL
PREEMPT_ENABLED --> COMBO_REL
PREEMPT_ENABLED --> NOPRE_IMPL
PREEMPT_ENABLED --> NOPRE_REL

Sources: src/lib.rs(L149 - L161)  src/lib.rs(L163 - L179) 

Guard Behavior Matrix

The following table shows how each guard type behaves based on feature configuration:

Guard Typepreempt Feature Disabledpreempt Feature Enabled
NoOpNo operationNo operation
IrqSaveIRQ disable/restore onlyIRQ disable/restore only
NoPreemptNo operationPreemption disable/enable viaKernelGuardIf
NoPreemptIrqSaveIRQ disable/restore onlyPreemption disable + IRQ disable/restore

Implementation Details

NoPreempt Guard Feature Integration


Sources: src/lib.rs(L149 - L161)  src/lib.rs(L200 - L217) 

NoPreemptIrqSave Combined Behavior

The NoPreemptIrqSave guard demonstrates the most complex feature-dependent behavior, combining both IRQ control and optional preemption control:

sequenceDiagram
    participant UserCode as "User Code"
    participant NoPreemptIrqSave as "NoPreemptIrqSave"
    participant KernelGuardIf as "KernelGuardIf"
    participant archlocal_irq_ as "arch::local_irq_*"

    UserCode ->> NoPreemptIrqSave: new()
    NoPreemptIrqSave ->> NoPreemptIrqSave: acquire()
    alt preempt feature enabled
        NoPreemptIrqSave ->> KernelGuardIf: disable_preempt()
        Note over KernelGuardIf: Kernel preemption disabled
    else preempt feature disabled
        Note over NoPreemptIrqSave: Skip preemption control
    end
    NoPreemptIrqSave ->> archlocal_irq_: local_irq_save_and_disable()
    archlocal_irq_ -->> NoPreemptIrqSave: irq_state
    Note over NoPreemptIrqSave,archlocal_irq_: Critical section active
    UserCode ->> NoPreemptIrqSave: drop()
    NoPreemptIrqSave ->> NoPreemptIrqSave: release(irq_state)
    NoPreemptIrqSave ->> archlocal_irq_: local_irq_restore(irq_state)
    alt preempt feature enabled
        NoPreemptIrqSave ->> KernelGuardIf: enable_preempt()
        Note over KernelGuardIf: Kernel preemption restored
    else preempt feature disabled
        Note over NoPreemptIrqSave: Skip preemption control
    end

Sources: src/lib.rs(L163 - L179)  src/lib.rs(L220 - L237) 

Integration Requirements

Feature Dependencies

When the preempt feature is enabled, user code must provide an implementation of the KernelGuardIf trait using the crate_interface mechanism. The relationship between feature activation and implementation requirements is:

ConfigurationUser Implementation Required
preemptdisabledNone
preemptenabledMust implementKernelGuardIftrait

Runtime Interface Resolution

The crate uses crate_interface::call_interface! macros to dynamically dispatch to user-provided implementations at runtime. This occurs only when the preempt feature is active:

flowchart TD
FEATURE_ENABLED["preempt featureenabled"]
MACRO_CALL["crate_interface::call_interface!(KernelGuardIf::disable_preempt)"]
RUNTIME_DISPATCH["Runtime InterfaceResolution"]
USER_IMPL["User Implementationof KernelGuardIf"]
ACTUAL_DISABLE["Actual PreemptionDisable Operation"]
FEATURE_DISABLED["preempt featuredisabled"]
COMPILE_SKIP["Conditional CompilationSkips Call"]
NO_OPERATION["No PreemptionControl"]

COMPILE_SKIP --> NO_OPERATION
FEATURE_DISABLED --> COMPILE_SKIP
FEATURE_ENABLED --> MACRO_CALL
MACRO_CALL --> RUNTIME_DISPATCH
RUNTIME_DISPATCH --> USER_IMPL
USER_IMPL --> ACTUAL_DISABLE

Sources: src/lib.rs(L153 - L154)  src/lib.rs(L158 - L159)  src/lib.rs(L167 - L168)  src/lib.rs(L176 - L177) 

Configuration Examples

Basic Usage Without Preemption

[dependencies]
kernel_guard = "0.1"

In this configuration, NoPreempt and NoPreemptIrqSave guards will only control IRQs, making them functionally equivalent to IrqSave for the IRQ portion.

Full Preemptive Configuration

[dependencies]
kernel_guard = { version = "0.1", features = ["preempt"] }

This configuration enables full preemption control capabilities, requiring the user to implement KernelGuardIf.

Sources: Cargo.toml(L14 - L16)  src/lib.rs(L21 - L26) 

Implementing KernelGuardIf

Relevant source files

This page provides a comprehensive guide for implementing the KernelGuardIf trait to enable preemption control in the kernel_guard crate. This implementation is required when using guards that need to disable kernel preemption, such as NoPreempt and NoPreemptIrqSave. For information about feature configuration, see Feature Configuration. For details about the core guard types, see RAII Guards.

When KernelGuardIf Implementation is Required

The KernelGuardIf trait must be implemented by users when the preempt feature is enabled and they want to use preemption-aware guards. Without this implementation, preemption control operations become no-ops.

Guard TypeRequires KernelGuardIfIRQ ControlPreemption Control
NoOpNoNoNo
IrqSaveNoYesNo
NoPreemptYes (with preempt feature)NoYes
NoPreemptIrqSaveYes (with preempt feature)YesYes

Sources: src/lib.rs(L80 - L111)  src/lib.rs(L149 - L179) 

Trait Definition and Interface

The KernelGuardIf trait defines the interface for kernel preemption control:

flowchart TD
subgraph subGraph2["Guard Usage"]
    ACQUIRE["BaseGuard::acquire()"]
    RELEASE["BaseGuard::release()"]
    CALL["crate_interface::call_interface!"]
end
subgraph subGraph1["User Implementation"]
    IMPL["User struct implementing KernelGuardIf#[crate_interface::impl_interface]"]
    ENABLE_IMPL["enable_preempt() implementation"]
    DISABLE_IMPL["disable_preempt() implementation"]
end
subgraph subGraph0["KernelGuardIf Trait Interface"]
    TRAIT["KernelGuardIf trait#[crate_interface::def_interface]"]
    ENABLE["fn enable_preempt()"]
    DISABLE["fn disable_preempt()"]
end

ACQUIRE --> CALL
CALL --> DISABLE_IMPL
CALL --> ENABLE_IMPL
IMPL --> DISABLE_IMPL
IMPL --> ENABLE_IMPL
RELEASE --> CALL
TRAIT --> DISABLE
TRAIT --> ENABLE

The trait is defined using the crate_interface::def_interface attribute, which creates a stable interface that can be implemented by external crates. The methods are called through the crate_interface::call_interface! macro.

Sources: src/lib.rs(L58 - L66) 

Implementation Steps

Step 1: Define Implementation Struct

Create a struct that will implement the KernelGuardIf trait:

struct KernelGuardIfImpl;

The struct typically doesn't need any fields, as preemption control is usually a global system operation.

Step 2: Implement the Trait

Use the #[crate_interface::impl_interface] attribute to implement the trait:

flowchart TD
subgraph subGraph0["Implementation Pattern"]
    ATTR["#[crate_interface::impl_interface]"]
    IMPL_BLOCK["impl KernelGuardIf for UserStruct"]
    ENABLE_FN["fn enable_preempt()"]
    DISABLE_FN["fn disable_preempt()"]
end

ATTR --> IMPL_BLOCK
IMPL_BLOCK --> DISABLE_FN
IMPL_BLOCK --> ENABLE_FN

The #[crate_interface::impl_interface] attribute registers the implementation with the crate_interface system, making it available for dynamic dispatch.

Sources: src/lib.rs(L35 - L43)  README.md(L41 - L49) 

Step 3: Implement Method Bodies

Provide platform-specific implementations for enabling and disabling preemption:

  • enable_preempt(): Should re-enable kernel preemption
  • disable_preempt(): Should disable kernel preemption

These implementations are highly platform-specific and depend on the kernel or operating system being used.

Interface Mechanism and Call Flow

The crate_interface mechanism provides runtime dispatch to user implementations:

sequenceDiagram
    participant Guard as Guard
    participant call_interface as call_interface!
    participant UserImplementation as User Implementation

    Note over Guard: Guard creation (e.g., NoPreempt::new())
    Guard ->> call_interface: BaseGuard::acquire()
    call_interface ->> UserImplementation: KernelGuardIf::disable_preempt()
    UserImplementation -->> call_interface: Platform-specific disable
    call_interface -->> Guard: Return
    Note over Guard: Critical section execution
    Note over Guard: Guard drop
    Guard ->> call_interface: BaseGuard::release()
    call_interface ->> UserImplementation: KernelGuardIf::enable_preempt()
    UserImplementation -->> call_interface: Platform-specific enable
    call_interface -->> Guard: Return

The guards call the interface methods conditionally based on feature flags:

  • NoPreempt calls the interface in both acquire() and release() methods
  • NoPreemptIrqSave calls the interface alongside IRQ control operations
  • Calls are wrapped in #[cfg(feature = "preempt")] conditionals

Sources: src/lib.rs(L153 - L154)  src/lib.rs(L158 - L159)  src/lib.rs(L167 - L168)  src/lib.rs(L176 - L177) 

Complete Implementation Example

Here's a complete example showing the implementation pattern:

use kernel_guard::{KernelGuardIf, NoPreempt};

struct KernelGuardIfImpl;

#[crate_interface::impl_interface]
impl KernelGuardIf for KernelGuardIfImpl {
    fn enable_preempt() {
        // Platform-specific implementation
        // Example: atomic decrement of preemption counter
        // or direct scheduler manipulation
    }
    
    fn disable_preempt() {
        // Platform-specific implementation  
        // Example: atomic increment of preemption counter
        // or direct scheduler manipulation
    }
}

// Usage after implementation
let guard = NoPreempt::new();
// Critical section with preemption disabled
drop(guard); // Preemption re-enabled

Sources: src/lib.rs(L30 - L52)  README.md(L36 - L58) 

Guard Behavior with KernelGuardIf

The following diagram shows how different guards utilize the KernelGuardIf implementation:

flowchart TD
subgraph subGraph1["NoPreemptIrqSave Guard"]
    NPIS_IRQ_DISABLE["arch::local_irq_save_and_disable()"]
    NPIS_ENABLE["call_interface!(enable_preempt)"]
    subgraph subGraph0["NoPreempt Guard"]
        NPIS_NEW["NoPreemptIrqSave::new()"]
        NPIS_ACQUIRE["BaseGuard::acquire()"]
        NPIS_DISABLE["call_interface!(disable_preempt)"]
        NPIS_DROP["Drop::drop()"]
        NPIS_RELEASE["BaseGuard::release()"]
        NPIS_IRQ_RESTORE["arch::local_irq_restore()"]
        NP_NEW["NoPreempt::new()"]
        NP_ACQUIRE["BaseGuard::acquire()"]
        NP_DISABLE["call_interface!(disable_preempt)"]
        NP_DROP["Drop::drop()"]
        NP_RELEASE["BaseGuard::release()"]
        NP_ENABLE["call_interface!(enable_preempt)"]
    end
end

NPIS_ACQUIRE --> NPIS_DISABLE
NPIS_DISABLE --> NPIS_IRQ_DISABLE
NPIS_DROP --> NPIS_RELEASE
NPIS_IRQ_RESTORE --> NPIS_ENABLE
NPIS_NEW --> NPIS_ACQUIRE
NPIS_RELEASE --> NPIS_IRQ_RESTORE
NP_ACQUIRE --> NP_DISABLE
NP_DROP --> NP_RELEASE
NP_NEW --> NP_ACQUIRE
NP_RELEASE --> NP_ENABLE

Note the ordering in NoPreemptIrqSave: preemption is disabled first, then IRQs are disabled. On release, IRQs are restored first, then preemption is re-enabled.

Sources: src/lib.rs(L149 - L179) 

Best Practices and Considerations

Implementation Guidelines

  1. Thread Safety: Ensure implementations are thread-safe, as they may be called from multiple contexts
  2. Performance: Keep implementations lightweight, as they're called frequently in critical sections
  3. Error Handling: Implementations should not panic, as this would break RAII guarantees
  4. Platform Specific: Implementations must match the target platform's preemption semantics

Common Implementation Patterns

PatternUse CaseExample
Reference CountingNested preemption disableAtomic counter increment/decrement
Direct ControlSimple scheduler toggleDirect register manipulation
System CallUser-space kernelsSyscall to kernel preemption control
No-op StubTesting/debuggingEmpty implementations for testing

Feature Flag Considerations

When the preempt feature is disabled, all calls to KernelGuardIf methods are compiled out. This means:

  • No runtime overhead when preemption control is not needed
  • Guards still provide IRQ control functionality
  • Implementation is not required when feature is disabled

Sources: src/lib.rs(L23 - L26)  src/lib.rs(L153)  src/lib.rs(L158)  src/lib.rs(L167)  src/lib.rs(L176) 

Development

Relevant source files

This document provides comprehensive information for contributors and maintainers about the development workflow, testing procedures, and CI/CD pipeline for the kernel_guard crate. It covers the automated build system, multi-architecture testing strategy, documentation generation process, and local development environment setup.

For information about integrating kernel_guard into other projects, see Integration Guide. For details about the core architecture and implementation, see Core Architecture.

Development Workflow Overview

The kernel_guard project follows a streamlined development workflow centered around GitHub Actions for continuous integration and automated documentation deployment.

Development Process Flow

flowchart TD
DEV["Developer"]
CLONE["git clone"]
LOCAL["Local Development"]
FORMAT["cargo fmt"]
CLIPPY["cargo clippy"]
BUILD["cargo build"]
TEST["cargo test"]
COMMIT["git commit"]
PUSH["git push / PR"]
TRIGGER["GitHub Actions Trigger"]
CI_JOB["ci job"]
DOC_JOB["doc job"]
MATRIX["Multi-target Matrix"]
FMT_CHECK["cargo fmt --check"]
CLIPPY_CHECK["cargo clippy"]
BUILD_CHECK["cargo build"]
TEST_CHECK["cargo test"]
DOC_BUILD["cargo doc"]
PAGES["GitHub Pages Deploy"]
PASS["All Checks Pass?"]
MERGE["Merge/Deploy"]
FAIL["Build Failure"]

BUILD --> TEST
BUILD_CHECK --> PASS
CI_JOB --> MATRIX
CLIPPY --> BUILD
CLIPPY_CHECK --> PASS
CLONE --> LOCAL
COMMIT --> PUSH
DEV --> CLONE
DOC_BUILD --> PAGES
DOC_JOB --> DOC_BUILD
FAIL --> LOCAL
FMT_CHECK --> PASS
FORMAT --> CLIPPY
LOCAL --> FORMAT
MATRIX --> BUILD_CHECK
MATRIX --> CLIPPY_CHECK
MATRIX --> FMT_CHECK
MATRIX --> TEST_CHECK
PASS --> FAIL
PASS --> MERGE
PUSH --> TRIGGER
TEST --> COMMIT
TEST_CHECK --> PASS
TRIGGER --> CI_JOB
TRIGGER --> DOC_JOB

Sources: .github/workflows/ci.yml(L1 - L56)  Cargo.toml(L1 - L21) 

CI/CD Pipeline Architecture

The continuous integration system is defined in .github/workflows/ci.yml and consists of two primary jobs: ci and doc, each serving distinct purposes in the development pipeline.

CI Job Matrix Configuration

flowchart TD
subgraph subGraph3["Quality Gates"]
    FORMAT["cargo fmt --all --check"]
    CLIPPY["cargo clippy --target TARGET --all-features"]
    BUILD["cargo build --target TARGET --all-features"]
    UNITTEST["cargo test (Linux only)"]
end
subgraph subGraph2["Target Matrix"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
    T5["loongarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Matrix Configuration"]
    TOOLCHAIN["rust-toolchain: nightly"]
    TARGETS["Target Architectures"]
end
subgraph subGraph0["CI Pipeline"]
    TRIGGER["Push/Pull Request"]
    CI["ci job"]
    STRATEGY["fail-fast: false"]
    MATRIX["Matrix Strategy"]
end

BUILD --> UNITTEST
CI --> STRATEGY
CLIPPY --> BUILD
FORMAT --> CLIPPY
MATRIX --> TARGETS
MATRIX --> TOOLCHAIN
STRATEGY --> MATRIX
T1 --> FORMAT
T2 --> FORMAT
T3 --> FORMAT
T4 --> FORMAT
T5 --> FORMAT
TARGETS --> T1
TARGETS --> T2
TARGETS --> T3
TARGETS --> T4
TARGETS --> T5
TRIGGER --> CI

Sources: .github/workflows/ci.yml(L6 - L30) 

Build and Test Matrix

The CI system uses a matrix strategy to test across multiple target architectures simultaneously:

Target ArchitectureOperating SystemTest Execution
x86_64-unknown-linux-gnuLinux (hosted)Full tests
x86_64-unknown-noneBare metalBuild only
riscv64gc-unknown-none-elfBare metalBuild only
aarch64-unknown-none-softfloatBare metalBuild only
loongarch64-unknown-none-softfloatBare metalBuild only

The conditional test execution is controlled by the matrix condition at .github/workflows/ci.yml(L29) : if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}.

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L28 - L30) 

Toolchain and Dependencies

Rust Toolchain Requirements

The project requires Rust nightly toolchain with specific components:

Crate Dependencies

The project maintains minimal dependencies as defined in Cargo.toml(L18 - L20) :

DependencyVersionPurpose
cfg-if1.0Conditional compilation
crate_interface0.1Stable API abstraction

Sources: Cargo.toml(L18 - L20)  .github/workflows/ci.yml(L15 - L19) 

Quality Assurance Process

Code Quality Checks

The CI pipeline enforces code quality through multiple automated checks:

flowchart TD
subgraph subGraph3["Build Verification"]
    MULTI_TARGET["All target architectures"]
    ALL_FEAT["All feature combinations"]
end
subgraph subGraph2["Format Enforcement"]
    FAIL_FMT["Formatting violations fail CI"]
end
subgraph subGraph1["Clippy Configuration"]
    FEATURES["--all-features"]
    ALLOW["-A clippy::new_without_default"]
end
subgraph subGraph0["Quality Gates"]
    FMT["cargo fmt --all --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test"]
end

BUILD --> ALL_FEAT
BUILD --> MULTI_TARGET
BUILD --> TEST
CLIPPY --> ALLOW
CLIPPY --> BUILD
CLIPPY --> FEATURES
FMT --> CLIPPY
FMT --> FAIL_FMT

Sources: .github/workflows/ci.yml(L22 - L27) 

Testing Strategy

Unit testing is architecture-specific and only executed on the Linux target:

  • Test Execution: .github/workflows/ci.yml(L28 - L30) 
  • Test Command: cargo test --target x86_64-unknown-linux-gnu -- --nocapture
  • Rationale: Bare metal targets cannot execute standard unit tests

Sources: .github/workflows/ci.yml(L28 - L30) 

Documentation Pipeline

Automated Documentation Generation

The doc job handles documentation building and deployment:

flowchart TD
subgraph subGraph0["Documentation Flags"]
    RUSTDOC_FLAGS["RUSTDOCFLAGS environment"]
    BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"]
    MISSING_DOCS["-D missing-docs"]
end
DOC_TRIGGER["Push to main branch"]
DOC_JOB["doc job"]
DOC_BUILD["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate index.html redirect"]
DEPLOY_CHECK["Is main branch?"]
PAGES_DEPLOY["GitHub Pages Deploy"]
CONTINUE_ERROR["continue-on-error"]
BRANCH["gh-pages branch"]
FOLDER["target/doc folder"]

DEPLOY_CHECK --> CONTINUE_ERROR
DEPLOY_CHECK --> PAGES_DEPLOY
DOC_BUILD --> INDEX_GEN
DOC_JOB --> RUSTDOC_FLAGS
DOC_TRIGGER --> DOC_JOB
INDEX_GEN --> DEPLOY_CHECK
PAGES_DEPLOY --> BRANCH
PAGES_DEPLOY --> FOLDER
RUSTDOC_FLAGS --> BROKEN_LINKS
RUSTDOC_FLAGS --> DOC_BUILD
RUSTDOC_FLAGS --> MISSING_DOCS

Sources: .github/workflows/ci.yml(L32 - L56)  .github/workflows/ci.yml(L40) 

Documentation Configuration

The documentation system includes several important configurations:

  • Strict Documentation: -D missing-docs flag requires all public items to be documented
  • Link Validation: -D rustdoc::broken_intra_doc_links prevents broken documentation links
  • Automatic Indexing: Dynamic index.html generation using cargo tree output .github/workflows/ci.yml(L48) 

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47 - L48) 

Development Environment Setup

Local Development Requirements

For local development, ensure your environment excludes build artifacts and temporary files as specified in .gitignore(L1 - L4) :

/target
/.vscode
.DS_Store
Cargo.lock

Feature Development

The crate supports feature-based development through Cargo.toml(L14 - L16) :

  • Default Features: None (default = [])
  • Optional Features: preempt for preemption control functionality

Local Testing Commands

For effective local development, use these commands that mirror the CI pipeline:

# Format check
cargo fmt --all -- --check

# Linting
cargo clippy --all-features -- -A clippy::new_without_default

# Multi-target builds
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features

# Unit testing (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

# Documentation generation
cargo doc --no-deps --all-features

Sources: .github/workflows/ci.yml(L22 - L30)  .gitignore(L1 - L4)  Cargo.toml(L14 - L16) 

Build System and CI

Relevant source files

This document covers the automated build system and continuous integration (CI) pipeline for the kernel_guard crate. The CI system is implemented using GitHub Actions and provides multi-architecture testing, code quality enforcement, and automated documentation deployment.

For information about setting up a local development environment, see Development Environment. For details about the specific architectures supported, see Multi-Architecture Support.

CI Pipeline Overview

The kernel_guard project uses GitHub Actions for continuous integration, defined in a single workflow file that handles both build/test operations and documentation deployment. The pipeline is triggered on all push and pull request events.

CI Pipeline Structure

flowchart TD
TRIGGER["on: [push, pull_request]"]
JOBS["GitHub Actions Jobs"]
CI_JOB["ci job"]
DOC_JOB["doc job"]
MATRIX["strategy.matrix"]
CI_STEPS["CI Steps"]
DOC_PERMISSIONS["permissions: contents: write"]
DOC_STEPS["Documentation Steps"]
TOOLCHAIN["rust-toolchain: [nightly]"]
TARGETS["targets: [5 architectures]"]
CHECKOUT["actions/checkout@v4"]
TOOLCHAIN_SETUP["dtolnay/rust-toolchain@nightly"]
FORMAT_CHECK["cargo fmt --all -- --check"]
CLIPPY_CHECK["cargo clippy"]
BUILD_STEP["cargo build"]
TEST_STEP["cargo test"]
DOC_BUILD["cargo doc --no-deps --all-features"]
PAGES_DEPLOY["JamesIves/github-pages-deploy-action@v4"]

CI_JOB --> CI_STEPS
CI_JOB --> MATRIX
CI_STEPS --> BUILD_STEP
CI_STEPS --> CHECKOUT
CI_STEPS --> CLIPPY_CHECK
CI_STEPS --> FORMAT_CHECK
CI_STEPS --> TEST_STEP
CI_STEPS --> TOOLCHAIN_SETUP
DOC_JOB --> DOC_PERMISSIONS
DOC_JOB --> DOC_STEPS
DOC_STEPS --> DOC_BUILD
DOC_STEPS --> PAGES_DEPLOY
JOBS --> CI_JOB
JOBS --> DOC_JOB
MATRIX --> TARGETS
MATRIX --> TOOLCHAIN
TRIGGER --> JOBS

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Job Configuration

The primary ci job runs on ubuntu-latest and uses a matrix strategy to test multiple configurations simultaneously. The job is configured with fail-fast: false to ensure all matrix combinations are tested even if some fail.

Matrix Strategy

The build matrix tests against multiple target architectures using the nightly Rust toolchain:

Target ArchitecturePurpose
x86_64-unknown-linux-gnuStandard Linux testing and unit tests
x86_64-unknown-noneBare metal x86_64
riscv64gc-unknown-none-elfRISC-V 64-bit bare metal
aarch64-unknown-none-softfloatARM64 bare metal
loongarch64-unknown-none-softfloatLoongArch64 bare metal

Matrix Configuration Flow

flowchart TD
MATRIX_START["strategy.matrix"]
TOOLCHAIN_CONFIG["rust-toolchain: nightly"]
TARGET_CONFIG["targets: 5 architectures"]
NIGHTLY["nightly toolchain"]
X86_LINUX["x86_64-unknown-linux-gnu"]
X86_NONE["x86_64-unknown-none"]
RISCV["riscv64gc-unknown-none-elf"]
AARCH64["aarch64-unknown-none-softfloat"]
LOONG["loongarch64-unknown-none-softfloat"]
COMPONENTS["components: rust-src, clippy, rustfmt"]
TEST_ENABLED["Unit tests enabled"]
BUILD_ONLY["Build only"]

AARCH64 --> BUILD_ONLY
LOONG --> BUILD_ONLY
MATRIX_START --> TARGET_CONFIG
MATRIX_START --> TOOLCHAIN_CONFIG
NIGHTLY --> COMPONENTS
RISCV --> BUILD_ONLY
TARGET_CONFIG --> AARCH64
TARGET_CONFIG --> LOONG
TARGET_CONFIG --> RISCV
TARGET_CONFIG --> X86_LINUX
TARGET_CONFIG --> X86_NONE
TOOLCHAIN_CONFIG --> NIGHTLY
X86_LINUX --> TEST_ENABLED
X86_NONE --> BUILD_ONLY

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L16 - L19) 

Quality Assurance Steps

The CI job enforces code quality through a series of automated checks:

Quality Assurance Workflow

flowchart TD
SETUP["Rust Toolchain Setup"]
VERSION_CHECK["rustc --version --verbose"]
FORMAT_CHECK["cargo fmt --all -- --check"]
CLIPPY_CHECK["cargo clippy --target TARGET --all-features"]
BUILD_STEP["cargo build --target TARGET --all-features"]
TEST_DECISION["TARGET == x86_64-unknown-linux-gnu?"]
UNIT_TEST["cargo test --target TARGET -- --nocapture"]
COMPLETE["Job Complete"]
CLIPPY_ALLOW["-A clippy::new_without_default"]

BUILD_STEP --> TEST_DECISION
CLIPPY_CHECK --> BUILD_STEP
CLIPPY_CHECK --> CLIPPY_ALLOW
FORMAT_CHECK --> CLIPPY_CHECK
SETUP --> VERSION_CHECK
TEST_DECISION --> COMPLETE
TEST_DECISION --> UNIT_TEST
UNIT_TEST --> COMPLETE
VERSION_CHECK --> FORMAT_CHECK

The clippy step includes a specific allowance for the new_without_default lint, configured via the -A clippy::new_without_default flag.

Sources: .github/workflows/ci.yml(L20 - L30) 

Documentation Job and GitHub Pages

The doc job handles automated documentation generation and deployment to GitHub Pages. This job requires write permissions to the repository contents for deployment.

Documentation Build Process

The documentation job uses specific environment variables and build flags to ensure high-quality documentation:

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

Documentation Deployment Flow

flowchart TD
DOC_TRIGGER["doc job trigger"]
PERMISSIONS_CHECK["permissions.contents: write"]
ENV_SETUP["RUSTDOCFLAGS environment"]
BRANCH_CHECK["default-branch variable"]
DOC_BUILD["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate index.html redirect"]
BRANCH_CONDITION["github.ref == default-branch?"]
DEPLOY_PAGES["JamesIves/github-pages-deploy-action@v4"]
SKIP_DEPLOY["Skip deployment"]
PAGES_CONFIG["single-commit: truebranch: gh-pagesfolder: target/doc"]
STRICT_DOCS["Broken links fail buildMissing docs fail build"]

BRANCH_CHECK --> DOC_BUILD
BRANCH_CONDITION --> DEPLOY_PAGES
BRANCH_CONDITION --> SKIP_DEPLOY
DEPLOY_PAGES --> PAGES_CONFIG
DOC_BUILD --> INDEX_GEN
DOC_TRIGGER --> PERMISSIONS_CHECK
ENV_SETUP --> BRANCH_CHECK
ENV_SETUP --> STRICT_DOCS
INDEX_GEN --> BRANCH_CONDITION
PERMISSIONS_CHECK --> ENV_SETUP

The index.html generation uses a shell command to extract the crate name and create a redirect:

printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Sources: .github/workflows/ci.yml(L32 - L56)  .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L46 - L48) 

Build and Test Execution

The CI system executes different build and test strategies based on the target architecture:

Target-Specific Behavior

flowchart TD
BUILD_START["cargo build --target TARGET --all-features"]
TARGET_CHECK["Check target architecture"]
LINUX_TARGET["x86_64-unknown-linux-gnu"]
BAREMETAL_X86["x86_64-unknown-none"]
BAREMETAL_RISCV["riscv64gc-unknown-none-elf"]
BAREMETAL_ARM["aarch64-unknown-none-softfloat"]
BAREMETAL_LOONG["loongarch64-unknown-none-softfloat"]
BUILD_SUCCESS["Build Success"]
TEST_CHECK["if: matrix.targets == 'x86_64-unknown-linux-gnu'"]
UNIT_TESTS["cargo test --target TARGET -- --nocapture"]
NO_TESTS["Skip unit tests"]
TEST_COMPLETE["All checks complete"]

BAREMETAL_ARM --> BUILD_SUCCESS
BAREMETAL_LOONG --> BUILD_SUCCESS
BAREMETAL_RISCV --> BUILD_SUCCESS
BAREMETAL_X86 --> BUILD_SUCCESS
BUILD_START --> TARGET_CHECK
BUILD_SUCCESS --> TEST_CHECK
LINUX_TARGET --> BUILD_SUCCESS
NO_TESTS --> TEST_COMPLETE
TARGET_CHECK --> BAREMETAL_ARM
TARGET_CHECK --> BAREMETAL_LOONG
TARGET_CHECK --> BAREMETAL_RISCV
TARGET_CHECK --> BAREMETAL_X86
TARGET_CHECK --> LINUX_TARGET
TEST_CHECK --> NO_TESTS
TEST_CHECK --> UNIT_TESTS
UNIT_TESTS --> TEST_COMPLETE

Unit tests are only executed on the x86_64-unknown-linux-gnu target because the bare metal targets cannot run standard Rust test frameworks.

Sources: .github/workflows/ci.yml(L26 - L30) 

The CI pipeline ensures that the kernel_guard crate builds correctly across all supported architectures while maintaining code quality standards through automated formatting, linting, and testing.

Development Environment

Relevant source files

This section provides setup instructions for local development of the kernel_guard crate, including toolchain requirements, dependency management, and project configuration. For information about the CI/CD pipeline and automated testing, see Build System and CI.

Prerequisites and Toolchain Setup

The kernel_guard crate is designed for kernel-level development and supports multiple target architectures. Development requires a properly configured Rust toolchain with cross-compilation capabilities.

Rust Toolchain Requirements

The project uses Rust 2021 edition and requires a recent stable Rust compiler. The crate is designed for no-std environments, making it suitable for bare-metal kernel development.

flowchart TD
RUST["rustc (stable)"]
EDITION["2021 edition"]
NOSTD["no-std compatibility"]
TARGETS["Multi-target support"]
X86["x86_64-unknown-linux-gnux86_64-unknown-none"]
RISCV["riscv64gc-unknown-none-elf"]
AARCH64["aarch64-unknown-none-softfloat"]
LOONG["loongarch64-unknown-none-softfloat"]
CARGO["Cargo build system"]
KERNEL["Kernel development"]

EDITION --> CARGO
NOSTD --> KERNEL
RUST --> EDITION
RUST --> NOSTD
RUST --> TARGETS
TARGETS --> AARCH64
TARGETS --> LOONG
TARGETS --> RISCV
TARGETS --> X86

Rust Toolchain Setup

# Install required targets for cross-compilation
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
rustup target add loongarch64-unknown-none-softfloat

Sources: Cargo.toml(L4)  Cargo.toml(L12) 

Project Dependencies and Configuration

The project maintains minimal dependencies to ensure compatibility with kernel environments. All dependencies are carefully selected for no-std compatibility.

Core Dependencies

The project relies on two primary dependencies that enable its architecture-agnostic design and conditional compilation features.

DependencyVersionPurpose
cfg-if1.0Conditional compilation macros
crate_interface0.1Stable API abstraction layer
flowchart TD
CARGO["Cargo.toml"]
DEPS["Dependencies"]
CFGIF["cfg-if = \1.0\"]
CRATEIF["crate_interface = \0.1\"]
ARCH["Architecture selection"]
TRAIT["KernelGuardIf trait"]
COMPILE["Conditional compilation"]
INTERFACE["User implementation"]

ARCH --> COMPILE
CARGO --> DEPS
CFGIF --> ARCH
CRATEIF --> TRAIT
DEPS --> CFGIF
DEPS --> CRATEIF
TRAIT --> INTERFACE

Sources: Cargo.toml(L18 - L20) 

Feature Configuration

The crate provides optional features that control guard functionality and compilation behavior.

FeatureDefaultDescription
defaultEmpty default feature set
preemptEnables preemption control guards
flowchart TD
FEATURES["Crate Features"]
DEFAULT["default = []"]
PREEMPT["preempt"]
BASIC["Basic IRQ guards only"]
ADVANCED["NoPreempt + NoPreemptIrqSave"]
IRQSAVE["IrqSave guard"]
NOPREEMPT["NoPreempt guard"]
COMBINED["NoPreemptIrqSave guard"]

ADVANCED --> COMBINED
ADVANCED --> NOPREEMPT
BASIC --> IRQSAVE
DEFAULT --> BASIC
FEATURES --> DEFAULT
FEATURES --> PREEMPT
PREEMPT --> ADVANCED

Sources: Cargo.toml(L14 - L16) 

Local Development Workflow

Project Structure

The development environment excludes common build artifacts and editor-specific files to maintain a clean repository state.

Excluded Files and Directories

  • /target - Cargo build artifacts and dependencies
  • /.vscode - Visual Studio Code workspace settings
  • .DS_Store - macOS file system metadata
  • Cargo.lock - Dependency lock file (excluded for libraries)
flowchart TD
REPO["Repository Root"]
SRC["src/"]
CARGO["Cargo.toml"]
GITIGNORE[".gitignore"]
EXCLUDED["Excluded Files"]
TARGET["/target"]
VSCODE["/.vscode"]
DSSTORE[".DS_Store"]
LOCK["Cargo.lock"]
BUILD["Build artifacts"]
DEPS["Downloaded dependencies"]
EDITOR["Editor configuration"]
VERSIONS["Dependency versions"]

EXCLUDED --> DSSTORE
EXCLUDED --> LOCK
EXCLUDED --> TARGET
EXCLUDED --> VSCODE
GITIGNORE --> EXCLUDED
LOCK --> VERSIONS
REPO --> CARGO
REPO --> GITIGNORE
REPO --> SRC
TARGET --> BUILD
TARGET --> DEPS
VSCODE --> EDITOR

Sources: .gitignore(L1 - L4) 

Build Commands

Basic Development Commands

# Build for host target
cargo build

# Build for specific target
cargo build --target x86_64-unknown-none

# Enable preempt feature
cargo build --features preempt

# Check formatting
cargo fmt --check

# Run linting
cargo clippy

Cross-Compilation Workflow

# Build for all supported architectures
cargo build --target x86_64-unknown-none
cargo build --target riscv64gc-unknown-none-elf
cargo build --target aarch64-unknown-none-softfloat
cargo build --target loongarch64-unknown-none-softfloat

Testing Limitations

Due to the kernel-specific nature of the crate, comprehensive testing is only available on hosted environments. The guards provide no-op implementations when not running on bare metal, allowing basic compilation and unit testing on development machines.

flowchart TD
ENV["Development Environment"]
HOSTED["Hosted (Linux/macOS)"]
BAREMETAL["Bare Metal Target"]
NOOP["NoOp guard implementations"]
TESTS["Unit tests available"]
REAL["Real guard implementations"]
LIMITED["Limited testing"]
COMPILE["Compilation verification"]
CI["CI pipeline validation"]

BAREMETAL --> LIMITED
BAREMETAL --> REAL
ENV --> BAREMETAL
ENV --> HOSTED
HOSTED --> NOOP
HOSTED --> TESTS
NOOP --> COMPILE
TESTS --> CI

Sources: Cargo.toml(L12) 

Documentation Development

The project uses standard Rust documentation tools with GitHub Pages integration for hosting.

Documentation Commands

# Generate local documentation
cargo doc --open

# Generate documentation with all features
cargo doc --all-features --open

# Check documentation examples
cargo test --doc

The documentation is automatically deployed to GitHub Pages through the CI pipeline, providing up-to-date API reference at the configured documentation URL.

Sources: Cargo.toml(L10) 

Overview

Relevant source files

This document covers the timer_list crate, a Rust library that provides efficient timer event management for the ArceOS operating system. The crate implements a priority queue-based system for scheduling and triggering time-based events in no-std environments. For detailed API documentation, see Core API Reference. For practical implementation examples, see Usage Guide and Examples.

Purpose and Core Design

The timer_list crate serves as a foundational component for time-based event scheduling in operating system kernels and embedded systems. It provides a TimerList data structure that manages TimerEvent objects, ensuring they are triggered sequentially when their deadlines expire.

Core Design Principles

The following diagram illustrates the core system architecture and maps natural language concepts to specific code entities:

flowchart TD
subgraph subGraph2["Internal Implementation"]
    HEAP["BinaryHeap"]
    WRAP["TimerEventWrapper"]
    ORD["Ord implementation"]
end
subgraph subGraph1["Core Operations"]
    SET["set(deadline, event)"]
    EXP["expire_one(now)"]
    EMPTY["is_empty()"]
    NEXT["next_deadline()"]
end
subgraph subGraph0["Public Interface"]
    TL["TimerList"]
    TE["TimerEvent trait"]
    TEF["TimerEventFn struct"]
    TV["TimeValue type alias"]
end

EXP --> HEAP
HEAP --> WRAP
SET --> HEAP
TE --> TEF
TEF --> TL
TL --> EMPTY
TL --> EXP
TL --> NEXT
TL --> SET
WRAP --> ORD

Sources: Cargo.toml(L1 - L15)  README.md(L12 - L34) 

System Architecture

The timer_list crate implements a min-heap based priority queue system for efficient timer management. The following diagram shows the complete system architecture:

flowchart TD
subgraph subGraph3["ArceOS Integration"]
    KERNEL["Kernel Timer System"]
    NOSTD["no-std Environment"]
    TARGETS["Multi-architecture Support"]
end
subgraph subGraph2["Data Management"]
    PQUEUE["Priority Queue (BinaryHeap)"]
    EVENTS["TimerEventWrapper objects"]
    ORDERING["Min-heap ordering by deadline"]
end
subgraph subGraph1["timer_list Crate"]
    API["TimerList API"]
    TRAIT["TimerEvent trait"]
    WRAPPER["TimerEventFn wrapper"]
end
subgraph subGraph0["Application Layer"]
    APP["User Code"]
    CB["Event Callbacks"]
end

API --> KERNEL
API --> NOSTD
API --> PQUEUE
APP --> API
APP --> TRAIT
CB --> WRAPPER
EVENTS --> ORDERING
NOSTD --> TARGETS
PQUEUE --> ORDERING
TRAIT --> EVENTS
WRAPPER --> EVENTS

Sources: Cargo.toml(L6 - L7)  Cargo.toml(L12)  README.md(L7 - L8) 

Key Capabilities

The timer_list crate provides the following core capabilities:

CapabilityImplementationCode Entity
Event SchedulingPriority queue with O(log n) insertionTimerList::set()
Event ExpirationO(1) access to next expired eventTimerList::expire_one()
Custom EventsTrait-based event systemTimerEventtrait
Function CallbacksWrapper for closure-based eventsTimerEventFn
Queue StatusCheck if events are pendingTimerList::is_empty()
Deadline AccessGet next event deadlineTimerList::next_deadline()

Sequential Processing Model

The crate enforces sequential processing of timer events, where events are triggered one at a time in deadline order. This design ensures deterministic behavior suitable for real-time systems and kernel environments.

sequenceDiagram
    participant UserCode as User Code
    participant TimerList as TimerList
    participant BinaryHeap as BinaryHeap
    participant TimerEvent as TimerEvent

    UserCode ->> TimerList: "set(deadline, event)"
    TimerList ->> BinaryHeap: "push(TimerEventWrapper)"
    Note over BinaryHeap: "Auto-sorts by deadline"
    loop "Event Processing"
        UserCode ->> TimerList: "expire_one(current_time)"
        TimerList ->> BinaryHeap: "peek() - check earliest"
    alt "Event expired"
        BinaryHeap -->> TimerList: "Return wrapper"
        TimerList ->> BinaryHeap: "pop() - remove"
        TimerList -->> UserCode: "Some((deadline, event))"
        UserCode ->> TimerEvent: "event.callback(now)"
    else "No expired events"
        TimerList -->> UserCode: "None"
    end
    end

Sources: README.md(L24 - L33) 

ArceOS Ecosystem Integration

The timer_list crate is designed specifically for integration with the ArceOS operating system ecosystem. Key integration aspects include:

  • No-std Compatibility: Designed for kernel and embedded environments without standard library dependencies
  • Multi-architecture Support: Compatible with x86_64, RISC-V, and ARM architectures
  • License Flexibility: Triple-licensed (GPL-3.0, Apache-2.0, MulanPSL-2.0) for broad ecosystem compatibility
  • Crates.io Publication: Available as a standalone library for reuse in other projects

The crate serves as a foundational component that other ArceOS modules can depend on for timer-based functionality, from interrupt handling to task scheduling.

Sources: Cargo.toml(L7 - L12)  README.md(L3 - L5) 

Core API Reference

Relevant source files

This document provides comprehensive reference documentation for all public types, traits, and methods exposed by the timer_list crate. The API consists of four main components: the TimerList data structure for managing timed events, the TimerEvent trait for defining event callbacks, the TimerEventFn wrapper for closure-based events, and the TimeValue type alias for time representation.

For practical usage examples and integration patterns, see Usage Guide and Examples. For detailed explanations of the internal architecture, see TimerList Data Structure and TimerEvent System.

Type System Overview

flowchart TD
TimeValue["TimeValue(type alias)"]
Duration["core::time::Duration"]
TimerEvent["TimerEvent(trait)"]
callback["callback(self, now: TimeValue)"]
TimerEventFn["TimerEventFn(struct)"]
BoxedClosure["Box<dyn FnOnce(TimeValue)>"]
TimerList["TimerList<E: TimerEvent>(struct)"]
BinaryHeap["BinaryHeap<TimerEventWrapper<E>>"]
TimerEventWrapper["TimerEventWrapper<E>(internal)"]
deadline["deadline: TimeValue"]
event["event: E"]

BinaryHeap --> TimerEventWrapper
TimeValue --> Duration
TimerEvent --> callback
TimerEventFn --> BoxedClosure
TimerEventFn --> TimerEvent
TimerEventWrapper --> deadline
TimerEventWrapper --> event
TimerList --> BinaryHeap
TimerList --> TimerEvent

Type Relationships in timer_list Crate

This diagram shows the complete type hierarchy and relationships within the crate. The TimerList is parameterized over types implementing TimerEvent, while TimerEventFn provides a concrete implementation for closure-based events.

Sources: src/lib.rs(L10 - L32)  src/lib.rs(L108 - L127) 

Core Types

TimeValue

pub type TimeValue = Duration;

The TimeValue type is an alias for core::time::Duration and represents all time values used throughout the timer system. This includes event deadlines, current time measurements, and time deltas.

Usage ContextDescription
Event deadlinesAbsolute time when events should expire
Current timeTime parameter passed toexpire_one()
Callback parameterTime value passed toTimerEvent::callback()

Sources: src/lib.rs(L10 - L13) 

TimerEvent Trait

The TimerEvent trait defines the interface that all timed events must implement. It consists of a single required method:

#![allow(unused)]
fn main() {
pub trait TimerEvent {
    fn callback(self, now: TimeValue);
}
}
MethodParametersDescription
callbackself, now: TimeValueCalled when the timer expires; consumes the event

The callback method takes ownership of the event (self) and receives the current time when the event expires. This design ensures that each event can only be triggered once.

Sources: src/lib.rs(L15 - L19) 

TimerEventFn Wrapper

The TimerEventFn struct provides a convenient way to create timer events from closures without implementing the TimerEvent trait manually.

pub struct TimerEventFn(Box<dyn FnOnce(TimeValue) + 'static>);
MethodSignatureDescription
newpub fn new(f: F) -> Self where F: FnOnce(TimeValue) + 'staticCreates a newTimerEventFnfrom a closure

The wrapper automatically implements TimerEvent by calling the stored closure when the event expires.

Sources: src/lib.rs(L108 - L127) 

TimerList API Methods

Constructor and State

MethodSignatureDescription
newpub fn new() -> SelfCreates a new empty timer list
is_emptypub fn is_empty(&self) -> boolReturnstrueif no events are scheduled

The TimerList also implements Default, which delegates to new().

Sources: src/lib.rs(L55 - L66)  src/lib.rs(L102 - L106) 

Event Management

MethodSignatureDescription
setpub fn set(&mut self, deadline: TimeValue, event: E)Schedules an event to expire at the specified deadline
cancelpub fn cancel(&mut self, condition: F) where F: Fn(&E) -> boolRemoves all events matching the condition function
next_deadlinepub fn next_deadline(&self) -> OptionReturns the deadline of the earliest scheduled event
expire_onepub fn expire_one(&mut self, now: TimeValue) -> Option<(TimeValue, E)>Expires the earliest event if its deadline has passed

The set method has O(log n) complexity due to the underlying min-heap structure. The cancel method currently has O(n) complexity and includes a TODO comment for performance optimization.

Sources: src/lib.rs(L68 - L99) 

Event Processing Flow

sequenceDiagram
    participant ClientCode as "Client Code"
    participant TimerList as "TimerList"
    participant BinaryHeap as "BinaryHeap"
    participant TimerEventWrapper as "TimerEventWrapper"

    Note over ClientCode,TimerEventWrapper: Event Scheduling
    ClientCode ->> TimerList: "set(deadline, event)"
    TimerList ->> TimerEventWrapper: "TimerEventWrapper::new"
    TimerList ->> BinaryHeap: "push(wrapper)"
    Note over BinaryHeap: "Min-heap reorders by deadline"
    Note over ClientCode,TimerEventWrapper: Event Expiration
    ClientCode ->> TimerList: "expire_one(now)"
    TimerList ->> BinaryHeap: "peek()"
    BinaryHeap -->> TimerList: "earliest event"
    alt "deadline <= now"
        TimerList ->> BinaryHeap: "pop()"
        BinaryHeap -->> TimerList: "TimerEventWrapper"
        TimerList -->> ClientCode: "Some((deadline, event))"
        ClientCode ->> ClientCode: "event.callback(now)"
    else "deadline > now"
        TimerList -->> ClientCode: "None"
    end

Event Lifecycle from Scheduling to Expiration

This sequence diagram illustrates the complete flow from event scheduling through expiration processing, showing how the TimerList coordinates with the internal BinaryHeap and TimerEventWrapper structures.

Sources: src/lib.rs(L21 - L24)  src/lib.rs(L68 - L99) 

Internal Implementation Details

TimerEventWrapper

The internal TimerEventWrapper<E> struct combines an event with its deadline and implements the ordering traits required for the min-heap:

struct TimerEventWrapper<E> {
    deadline: TimeValue,
    event: E,
}

The wrapper implements Ord, PartialOrd, Eq, and PartialEq with reversed ordering to create a min-heap from Rust's max-heap BinaryHeap. The comparison is based solely on the deadline field.

Trait ImplementationBehavior
Ord::cmpComparesother.deadlinewithself.deadline(reversed)
PartialOrd::partial_cmpDelegates toOrd::cmp
Eq,PartialEqCompares deadlines for equality

Sources: src/lib.rs(L21 - L24)  src/lib.rs(L34 - L52) 

Min-Heap Architecture

flowchart TD
subgraph subGraph0["Ordering Property"]
    parent["Parent deadline"]
    child1["Child1 deadline"]
    child2["Child2 deadline"]
end
TimerList["TimerList<E>"]
events["events: BinaryHeap"]
root["Root: earliest deadline"]
left["Left child"]
right["Right child"]
leftleft["..."]
leftright["..."]
rightleft["..."]
rightright["..."]

TimerList --> events
events --> root
left --> leftleft
left --> leftright
parent --> child1
parent --> child2
right --> rightleft
right --> rightright
root --> left
root --> right

Internal Min-Heap Structure for Event Ordering

The BinaryHeap maintains the min-heap property where each parent node has a deadline less than or equal to its children, ensuring O(1) access to the earliest event and O(log n) insertion.

Sources: src/lib.rs(L26 - L32)  src/lib.rs(L40 - L43) 

TimerList Data Structure

Relevant source files

This document provides detailed technical documentation of the TimerList struct, which serves as the core data structure for managing timed events in the timer_list crate. It covers the internal min-heap architecture, public methods, performance characteristics, and implementation details.

For information about the TimerEvent trait and event callback system, see TimerEvent System. For practical usage examples, see Usage Guide and Examples.

Core Structure Overview

The TimerList<E: TimerEvent> struct provides an efficient priority queue implementation for managing timed events. It internally uses a binary heap data structure to maintain events in deadline order, enabling O(log n) insertions and O(1) access to the next expiring event.

Primary Components

flowchart TD
subgraph subGraph0["Type Constraints"]
    TC["E: TimerEvent"]
end
TL["TimerList<E>"]
BH["BinaryHeap<TimerEventWrapper<E>>"]
TEW1["TimerEventWrapper<E>"]
TEW2["TimerEventWrapper<E>"]
TEW3["TimerEventWrapper<E>"]
DL1["deadline: TimeValue"]
EV1["event: E"]
DL2["deadline: TimeValue"]
EV2["event: E"]
DL3["deadline: TimeValue"]
EV3["event: E"]

BH --> TEW1
BH --> TEW2
BH --> TEW3
TEW1 --> DL1
TEW1 --> EV1
TEW2 --> DL2
TEW2 --> EV2
TEW3 --> DL3
TEW3 --> EV3
TL --> BH
TL --> TC

Sources: src/lib.rs(L30 - L32)  src/lib.rs(L21 - L24) 

The structure consists of three main components:

ComponentTypePurpose
TimerListPublic structMain interface for timer management
BinaryHeap<TimerEventWrapper>Internal fieldHeap storage for efficient ordering
TimerEventWrapperInternal structWrapper containing deadline and event data

Min-Heap Architecture

The TimerList implements a min-heap through custom ordering logic on TimerEventWrapper<E>. This ensures that events with earlier deadlines are always at the top of the heap for efficient retrieval.

Heap Ordering Implementation

flowchart TD
subgraph subGraph2["Trait Implementations"]
    ORD["Ord"]
    PORD["PartialOrd"]
    EQT["Eq"]
    PEQ["PartialEq"]
end
subgraph subGraph1["Min-Heap Property"]
    ROOT["Earliest Deadline"]
    L1["Later Deadline"]
    R1["Later Deadline"]
    L2["Even Later"]
    R2["Even Later"]
end
subgraph subGraph0["TimerEventWrapper Ordering"]
    CMP["cmp()"]
    REV["other.deadline.cmp(&self.deadline)"]
    PCMP["partial_cmp()"]
    EQ["eq()"]
    COMP["self.deadline == other.deadline"]
end

CMP --> REV
CMP --> ROOT
EQ --> COMP
EQT --> EQ
L1 --> L2
L1 --> R2
ORD --> CMP
PCMP --> CMP
PEQ --> EQ
PORD --> PCMP
ROOT --> L1
ROOT --> R1

Sources: src/lib.rs(L34 - L52) 

The ordering implementation uses reversed comparison logic:

TraitImplementationPurpose
Ord::cmp()other.deadline.cmp(&self.deadline)Reverses natural ordering for min-heap
PartialOrd::partial_cmp()Delegates tocmp()Required for heap operations
Eq/PartialEq::eq()self.deadline == other.deadlineDeadline-based equality

Public Methods

The TimerList provides a clean API for timer management operations:

Core Operations

MethodSignatureComplexityPurpose
new()fn new() -> SelfO(1)Creates empty timer list
set()fn set(&mut self, deadline: TimeValue, event: E)O(log n)Schedules new event
expire_one()fn expire_one(&mut self, now: TimeValue) -> Option<(TimeValue, E)>O(log n)Processes earliest expired event
cancel()fn cancel(&mut self, condition: F)O(n)Removes events matching condition

Query Operations

MethodSignatureComplexityPurpose
is_empty()fn is_empty(&self) -> boolO(1)Checks if any events exist
next_deadline()fn next_deadline(&self) -> OptionO(1)Returns earliest deadline

Sources: src/lib.rs(L54 - L100) 

Internal Implementation Details

Event Scheduling Flow

sequenceDiagram
    participant Client as Client
    participant TimerList as TimerList
    participant BinaryHeap as BinaryHeap
    participant TimerEventWrapper as TimerEventWrapper

    Client ->> TimerList: "set(deadline, event)"
    TimerList ->> TimerEventWrapper: "TimerEventWrapper { deadline, event }"
    TimerList ->> BinaryHeap: "push(wrapper)"
    Note over BinaryHeap: "Auto-reorders via Ord trait"
    BinaryHeap -->> TimerList: "Heap maintains min property"
    TimerList -->> Client: "Event scheduled"

Sources: src/lib.rs(L69 - L71) 

Event Expiration Flow

sequenceDiagram
    participant Client as Client
    participant TimerList as TimerList
    participant BinaryHeap as BinaryHeap

    Client ->> TimerList: "expire_one(now)"
    TimerList ->> BinaryHeap: "peek()"
    BinaryHeap -->> TimerList: "Some(earliest_wrapper)"
    alt "deadline <= now"
        TimerList ->> BinaryHeap: "pop()"
        BinaryHeap -->> TimerList: "TimerEventWrapper"
        TimerList -->> Client: "Some((deadline, event))"
    else "deadline > now"
        TimerList -->> Client: "None"
    end

Sources: src/lib.rs(L92 - L99) 

Data Structure Memory Layout

The TimerList maintains minimal memory overhead with efficient heap allocation:

flowchart TD
subgraph subGraph1["Heap Properties"]
    HP1["Min-heap ordering"]
    HP2["O(log n) rebalancing"]
    HP3["Contiguous memory"]
end
subgraph subGraph0["Memory Layout"]
    TLS["TimerList Struct"]
    BHS["BinaryHeap Storage"]
    VEC["Vec<TimerEventWrapper>"]
    TEW1["TimerEventWrapper { deadline, event }"]
    TEW2["TimerEventWrapper { deadline, event }"]
    TEWN["..."]
end

BHS --> HP1
BHS --> HP2
BHS --> VEC
TLS --> BHS
VEC --> HP3
VEC --> TEW1
VEC --> TEW2
VEC --> TEWN

Sources: src/lib.rs(L30 - L32)  src/lib.rs(L21 - L24) 

Performance Characteristics

OperationTime ComplexitySpace ComplexityNotes
Insert (set)O(log n)O(1) additionalBinary heap insertion
Peek next (next_deadline)O(1)O(1)Heap root access
Pop next (expire_one)O(log n)O(1)Heap rebalancing required
Cancel eventsO(n)O(n)Linear scan with filtering
Check emptyO(1)O(1)Heap size check

The min-heap implementation provides optimal performance for the primary use case of sequential event processing by deadline order.

Sources: src/lib.rs(L54 - L100) 

TimerEvent System

Relevant source files

This document covers the timer event system that defines how callbacks are executed when timers expire in the timer_list crate. It focuses on the TimerEvent trait and its implementations, particularly the TimerEventFn wrapper for closure-based events.

For information about the underlying timer storage and scheduling mechanisms, see TimerList Data Structure.

Purpose and Scope

The TimerEvent system provides the interface and implementations for executable timer callbacks in the timer_list crate. It defines how events are structured and executed when their deadlines are reached, supporting both custom event types and simple closure-based events.

TimerEvent Trait

The TimerEvent trait serves as the core interface that all timer events must implement. It defines a single callback method that consumes the event when executed.

Trait Definition

The trait is defined with a simple interface at src/lib.rs(L15 - L19) :

#![allow(unused)]
fn main() {
pub trait TimerEvent {
    fn callback(self, now: TimeValue);
}
}

Key Characteristics

AspectDescription
ConsumptionThecallbackmethod takesselfby value, consuming the event
Time ParameterReceives the current time asTimeValue(alias forDuration)
Return TypeReturns()- events perform side effects rather than return values
Execution ModelCalled exactly once when the timer expires

The trait design ensures that events cannot be accidentally reused after execution, providing memory safety and preventing common timer-related bugs.

TimerEvent Trait Architecture

classDiagram
class TimerEvent {
    <<trait>>
    
    +callback(self, now: TimeValue)
}

class TimerEventFn {
    
    -Box~dyn FnOnce(TimeValue) ~
    +new(f: F) TimerEventFn
    +callback(self, now: TimeValue)
}

class CustomEvent {
    <<user-defined>>
    
    +callback(self, now: TimeValue)
}

TimerEvent  ..|>  TimerEventFn : implements
TimerEvent  ..|>  CustomEvent : implements

Sources: src/lib.rs(L15 - L19)  src/lib.rs(L108 - L127) 

TimerEventFn Implementation

The TimerEventFn struct provides a convenient wrapper that allows closures to be used as timer events without requiring custom trait implementations.

Structure and Construction

The TimerEventFn wraps a boxed closure at src/lib.rs(L111) :

pub struct TimerEventFn(Box<dyn FnOnce(TimeValue) + 'static>);

Construction is handled by the new method at src/lib.rs(L114 - L121) :

ParameterTypeDescription
fF: FnOnce(TimeValue) + 'staticClosure to execute when timer expires
ReturnTimerEventFnWrapped timer event ready for scheduling

TimerEvent Implementation

The trait implementation at src/lib.rs(L123 - L127)  simply extracts and calls the wrapped closure:

#![allow(unused)]
fn main() {
impl TimerEvent for TimerEventFn {
    fn callback(self, now: TimeValue) {
        (self.0)(now)
    }
}
}

This design enables functional programming patterns while maintaining the trait's consumption semantics.

TimerEventFn Data Flow

flowchart TD
A["closure: FnOnce(TimeValue)"]
B["TimerEventFn::new(closure)"]
C["TimerEventFn(Box::new(closure))"]
D["TimerList::set(deadline, event)"]
E["TimerEventWrapper { deadline, event }"]
F["BinaryHeap::push(wrapper)"]
G["TimerList::expire_one(now)"]
H["BinaryHeap::pop()"]
I["event.callback(now)"]
J["(closure)(now)"]

A --> B
B --> C
C --> D
D --> E
E --> F
G --> H
H --> I
I --> J

Sources: src/lib.rs(L108 - L127)  src/lib.rs(L69 - L71) 

Event Lifecycle Integration

Timer events integrate with the TimerList through a well-defined lifecycle that ensures proper execution timing and resource management.

Event Scheduling Process

Events are scheduled through the TimerList::set method, which wraps them in TimerEventWrapper structures for heap management:

StepComponentAction
1Client CodeCreates event implementingTimerEvent
2TimerList::setWraps event inTimerEventWrapperwith deadline
3BinaryHeapStores wrapper using min-heap ordering
4TimerList::expire_oneChecks earliest deadline against current time
5Event CallbackExecutesevent.callback(now)if expired

Execution Semantics

When events expire, they follow a strict execution pattern defined at src/lib.rs(L92 - L99) :

  • Events are processed one at a time in deadline order
  • Only events with deadline <= now are eligible for execution
  • Events are removed from the heap before callback execution
  • Callbacks receive the actual current time, not the original deadline

Event Execution Flow

sequenceDiagram
    participant ClientCode as "Client Code"
    participant TimerList as "TimerList"
    participant BinaryHeapTimerEventWrapper as "BinaryHeap<TimerEventWrapper>"
    participant TimerEventcallback as "TimerEvent::callback"

    Note over ClientCode,TimerEventcallback: Event Scheduling
    ClientCode ->> TimerList: set(deadline, event)
    TimerList ->> BinaryHeapTimerEventWrapper: push(TimerEventWrapper)
    Note over ClientCode,TimerEventcallback: Event Processing
    loop Processing Loop
        ClientCode ->> TimerList: expire_one(now)
        TimerList ->> BinaryHeapTimerEventWrapper: peek()
    alt deadline <= now
        BinaryHeapTimerEventWrapper -->> TimerList: earliest event
        TimerList ->> BinaryHeapTimerEventWrapper: pop()
        TimerList ->> TimerEventcallback: callback(now)
        TimerEventcallback -->> ClientCode: side effects
        TimerList -->> ClientCode: Some((deadline, event))
    else deadline > now
        TimerList -->> ClientCode: None
    end
    end

Sources: src/lib.rs(L69 - L71)  src/lib.rs(L92 - L99)  src/lib.rs(L21 - L24) 

Custom Event Implementation

While TimerEventFn handles simple closure cases, custom event types can implement TimerEvent directly for more complex scenarios. The test suite demonstrates this pattern at src/lib.rs(L140 - L153) :

Example Pattern

#![allow(unused)]
fn main() {
struct TestTimerEvent(usize, TimeValue);

impl TimerEvent for TestTimerEvent {
    fn callback(self, now: TimeValue) {
        // Custom logic with access to event data
        println!("Event {} executed at {:?}", self.0, now);
    }
}
}

This approach allows events to carry additional data and implement complex callback logic while maintaining the same execution guarantees as TimerEventFn.

Code Entity Mapping

flowchart TD
subgraph subGraph2["Test Examples"]
    H["TestTimerEvent[src/lib.rs:140-153]"]
    I["test_timer_list_fn[src/lib.rs:183-206]"]
end
subgraph subGraph1["Integration Points"]
    E["TimerList::set()[src/lib.rs:69-71]"]
    F["TimerList::expire_one()[src/lib.rs:92-99]"]
    G["TimerEventWrapper[src/lib.rs:21-24]"]
end
subgraph subGraph0["TimerEvent System API"]
    A["TimerEvent trait[src/lib.rs:15-19]"]
    B["TimerEventFn struct[src/lib.rs:111]"]
    C["TimerEventFn::new()[src/lib.rs:114-121]"]
    D["TimeValue type[src/lib.rs:10-13]"]
end

A --> B
A --> D
A --> H
B --> C
B --> I
E --> G
F --> A

Sources: src/lib.rs(L15 - L19)  src/lib.rs(L108 - L127)  src/lib.rs(L69 - L71)  src/lib.rs(L92 - L99)  src/lib.rs(L140 - L153)  src/lib.rs(L183 - L206) 

Usage Guide and Examples

Relevant source files

This document provides practical examples and usage patterns for the timer_list crate, demonstrating how to integrate timer management functionality into applications. The examples range from basic single-timer usage to advanced patterns for managing multiple concurrent timers with custom events.

For detailed API documentation of the core types and methods, see Core API Reference. For information about the internal implementation architecture, see TimerList Data Structure and TimerEvent System.

Basic Timer Usage with Closures

The simplest way to use timer_list is with the TimerEventFn wrapper, which allows you to schedule closures to execute at specific deadlines.

Simple Timer Example

sequenceDiagram
    participant ApplicationCode as "Application Code"
    participant TimerListTimerEventFn as "TimerList<TimerEventFn>"
    participant TimerEventFn as "TimerEventFn"

    ApplicationCode ->> TimerListTimerEventFn: "new()"
    ApplicationCode ->> TimerEventFn: "new(closure)"
    ApplicationCode ->> TimerListTimerEventFn: "set(deadline, event)"
    loop "Event Processing Loop"
        ApplicationCode ->> TimerListTimerEventFn: "expire_one(now)"
    alt "Event Ready"
        TimerListTimerEventFn -->> ApplicationCode: "Some((deadline, event))"
        ApplicationCode ->> TimerEventFn: "callback(now)"
    else "No Events Ready"
        TimerListTimerEventFn -->> ApplicationCode: "None"
    end
    end

Basic Timer Setup and Processing

The core pattern involves creating a TimerList, setting timer events with deadlines, and periodically checking for expired events in a processing loop.

Sources: README.md(L12 - L34)  src/lib.rs(L111 - L127) 

Implementation Pattern

The basic usage follows this pattern:

  1. Create a TimerList<TimerEventFn> instance
  2. Use TimerEventFn::new() to wrap closures as timer events
  3. Call set() method with a TimeValue deadline and the event
  4. Periodically call expire_one() with current time to process expired events
  5. Execute the callback when events are returned
flowchart TD
subgraph subGraph1["Event Processing"]
    D["Current Time"]
    E["timer_list.expire_one(now)"]
    F["Event Expired?"]
    G["event.callback(now)"]
    H["None returned"]
end
subgraph subGraph0["Timer Creation"]
    A["TimerList::new()"]
    B["TimerEventFn::new(closure)"]
    C["timer_list.set(deadline, event)"]
end

A --> B
B --> C
C --> E
D --> E
E --> F
F --> G
F --> H
G --> E
H --> E

Timer Event Lifecycle from Creation to Execution

Sources: README.md(L16 - L33)  src/lib.rs(L69 - L98) 

Custom Timer Events

For more complex use cases, implement the TimerEvent trait directly to create custom timer events with specific behavior and data.

Custom Event Implementation

The TimerEvent trait requires implementing a single callback method that consumes the event and receives the current time.

classDiagram
class TimerEvent {
    <<trait>>
    
    +callback(self, now: TimeValue)
}

class TimerEventFn {
    
    -closure: Box~dyn FnOnce(TimeValue) ~
    +new(f: F) TimerEventFn
    +callback(self, now: TimeValue)
}

class CustomEvent {
    -id: usize
    -expected_deadline: TimeValue
    +new(id: usize, deadline: TimeValue) CustomEvent
    +callback(self, now: TimeValue)
}

TimerEvent  ..|>  TimerEventFn
TimerEvent  ..|>  CustomEvent

TimerEvent Trait Implementation Hierarchy

Sources: src/lib.rs(L15 - L19)  src/lib.rs(L123 - L127)  src/lib.rs(L140 - L153) 

Custom Event Example

The test case in the source code demonstrates a custom TestTimerEvent that tracks execution order and timing accuracy:

ComponentPurposeKey Methods
TestTimerEventCustom event with ID and expected deadlinecallback(self, now: TimeValue)
Event validationVerify execution order and timingComparenowwith stored deadline
Atomic counterTrack execution sequenceAtomicUsizefor thread-safe counting

The custom event pattern allows embedding application-specific data and logic within timer events, enabling complex scheduling scenarios.

Sources: src/lib.rs(L140 - L153) 

Advanced Usage Patterns

Multiple Timer Management

When managing multiple concurrent timers, the TimerList maintains them in deadline order using an internal min-heap structure.

flowchart TD
subgraph subGraph2["Sequential Processing"]
    I["expire_one() - Timer 1"]
    J["expire_one() - Timer 2"]
    K["expire_one() - Timer 3"]
    L["expire_one() - Timer 4"]
    M["Timer 5 (canceled)"]
end
subgraph subGraph1["Internal Organization"]
    G["BinaryHeap"]
    H["Min-heap by deadline"]
end
subgraph subGraph0["Multiple Timer Setup"]
    A["Timer 1 (0.5s)"]
    D["TimerList::set()"]
    B["Timer 2 (1.0s)"]
    C["Timer 3 (2.99s)"]
    E["Timer 4 (3.0s)"]
    F["Timer 5 (4.0s)"]
end

A --> D
B --> D
C --> D
D --> G
E --> D
F --> D
G --> H
H --> I
I --> J
J --> K
K --> L
L --> M

Multiple Timer Processing Order

The timers are processed in deadline order regardless of insertion sequence. The min-heap ensures O(log n) insertion and O(1) access to the earliest deadline.

Sources: src/lib.rs(L157 - L167)  src/lib.rs(L30 - L32)  src/lib.rs(L40 - L43) 

Timer Cancellation

The cancel() method allows removing events that match a specified condition before they expire.

OperationMethod SignatureUse Case
Cancel by conditioncancel(&mut self, condition: F)Remove events matching predicate
Check emptyis_empty(&self) -> boolVerify if any timers remain
Next deadlinenext_deadline(&self) -> OptionGet earliest scheduled time

The cancellation mechanism uses a closure to test each event, providing flexible filtering capabilities. The implementation uses BinaryHeap::retain() to remove matching events.

Sources: src/lib.rs(L73 - L80)  src/lib.rs(L171 - L173) 

Integration Patterns

Event Loop Integration

Timer management typically integrates with application event loops or system schedulers using a polling pattern.

flowchart TD
subgraph subGraph1["Timer State"]
    I["TimerList"]
    J["BinaryHeap ordering"]
    K["next_deadline()"]
    L["Optimal sleep duration"]
end
subgraph subGraph0["Application Event Loop"]
    A["Loop Start"]
    B["Get Current Time"]
    C["timer_list.expire_one(now)"]
    D["Event Expired?"]
    E["Execute Callback"]
    F["Other Processing"]
    G["Continue Loop"]
    H["Sleep/Yield"]
end

A --> B
B --> C
C --> D
C --> I
D --> E
D --> F
E --> G
F --> G
G --> H
H --> B
I --> J
J --> K
K --> L
L --> H

Event Loop Integration Pattern

Applications can use next_deadline() to calculate optimal sleep durations, reducing unnecessary CPU usage while maintaining timer accuracy.

Sources: src/lib.rs(L169 - L178)  src/lib.rs(L84 - L86) 

No-std Environment Usage

The crate is designed for no-std environments, making it suitable for embedded systems and kernel-level code.

RequirementImplementationBenefit
no-stdcompatibility#![cfg_attr(not(test), no_std)]Embedded/kernel usage
Heap allocationextern crate allocDynamic timer storage
Core types onlycore::time::DurationMinimal dependencies
No threadingSequential processingDeterministic behavior

The design ensures predictable performance characteristics essential for real-time and embedded applications.

Sources: src/lib.rs(L1)  src/lib.rs(L4)  src/lib.rs(L10 - L13) 

Common Patterns and Best Practices

Error Handling and Edge Cases

The TimerList API is designed to be infallible for core operations, with Option types indicating state rather than errors.

flowchart TD
subgraph subGraph1["State Handling"]
    I["Some: Event ready"]
    J["None: No events ready"]
    K["Some: Next deadline"]
    L["None: No events scheduled"]
end
subgraph subGraph0["Safe Operations"]
    A["set(deadline, event)"]
    B["Always succeeds"]
    C["expire_one(now)"]
    D["Option<(TimeValue, E)>"]
    E["next_deadline()"]
    F["Option"]
    G["is_empty()"]
    H["bool"]
end

A --> B
C --> D
D --> I
D --> J
E --> F
F --> K
F --> L
G --> H

API Safety and State Management

All operations are memory-safe and return clear indicators of timer list state, eliminating common timer management error scenarios.

Sources: src/lib.rs(L54 - L100) 

Performance Considerations

OperationComplexityNotes
set()O(log n)Heap insertion
expire_one()O(log n)Heap removal when expired
next_deadline()O(1)Heap peek operation
cancel()O(n)Linear scan with rebuild

The min-heap structure provides efficient access to the earliest deadline, but cancellation operations require full heap traversal. For frequent cancellation scenarios, consider alternative approaches like event flags.

Sources: src/lib.rs(L69 - L71)  src/lib.rs(L92 - L98)  src/lib.rs(L74 - L80) 

Development Workflow

Relevant source files

This document provides guidance for contributors working on the timer_list crate. It covers local development setup, build processes, testing procedures, and the automated CI/CD pipeline. For detailed API documentation, see Core API Reference. For usage examples and integration patterns, see Usage Guide and Examples.

Local Development Environment

The timer_list crate requires the Rust nightly toolchain with specific components and target architectures. The development environment must support cross-compilation for embedded and bare-metal targets.

Required Toolchain Components

flowchart TD
A["rust-toolchain: nightly"]
B["rust-src component"]
C["clippy component"]
D["rustfmt component"]
E["Target Architectures"]
F["x86_64-unknown-linux-gnu"]
G["x86_64-unknown-none"]
H["riscv64gc-unknown-none-elf"]
I["aarch64-unknown-none-softfloat"]

A --> B
A --> C
A --> D
A --> E
E --> F
E --> G
E --> H
E --> I

Toolchain Setup

The project requires rustc nightly with cross-compilation support for multiple architectures. Install the required components:

rustup toolchain install nightly
rustup component add rust-src clippy rustfmt --toolchain nightly
rustup target add x86_64-unknown-none riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat --toolchain nightly

Sources: .github/workflows/ci.yml(L15 - L19) 

Build and Development Commands

Core Development Workflow

flowchart TD
A["cargo fmt --all -- --check"]
B["cargo clippy --target TARGET --all-features"]
C["cargo build --target TARGET --all-features"]
D["cargo test --target x86_64-unknown-linux-gnu"]
E["cargo doc --no-deps --all-features"]
F["Format Check"]
G["Lint Analysis"]
H["Cross-Platform Build"]
I["Unit Testing"]
J["Documentation Generation"]

A --> B
B --> C
C --> D
D --> E
F --> A
G --> B
H --> C
I --> D
J --> E

Format Checking

cargo fmt --all -- --check

Lint Analysis

cargo clippy --target <TARGET> --all-features -- -A clippy::new_without_default

Building for Specific Targets

cargo build --target <TARGET> --all-features

Running Tests

cargo test --target x86_64-unknown-linux-gnu -- --nocapture

Sources: .github/workflows/ci.yml(L22 - L30) 

Multi-Target Architecture Support

The crate supports four distinct target architectures, each serving different deployment scenarios:

Target ArchitectureUse CaseTesting Support
x86_64-unknown-linux-gnuStandard Linux developmentFull unit testing
x86_64-unknown-noneBare-metal x86_64 systemsBuild-only
riscv64gc-unknown-none-elfRISC-V embedded systemsBuild-only
aarch64-unknown-none-softfloatARM64 embedded systemsBuild-only
flowchart TD
A["timer_list crate"]
B["x86_64-unknown-linux-gnu"]
C["x86_64-unknown-none"]
D["riscv64gc-unknown-none-elf"]
E["aarch64-unknown-none-softfloat"]
F["Linux DevelopmentUnit Testing"]
G["Bare-metal x86_64Build Verification"]
H["RISC-V EmbeddedBuild Verification"]
I["ARM64 EmbeddedBuild Verification"]

A --> B
A --> C
A --> D
A --> E
B --> F
C --> G
D --> H
E --> I

Unit tests execute only on x86_64-unknown-linux-gnu due to the no-std nature of other targets and the lack of standard library support for test execution.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L29 - L30) 

Continuous Integration Pipeline

CI Job Matrix Strategy

flowchart TD
subgraph subGraph1["Documentation Pipeline"]
    C["Documentation Job"]
    H["cargo doc build"]
    I["GitHub Pages Deploy"]
end
subgraph subGraph0["CI Job Matrix"]
    B["CI Job"]
    D["Format Check"]
    E["Clippy Analysis"]
    F["Multi-Target Build"]
    G["Unit Tests"]
end
A["GitHub Push/PR Trigger"]
J["x86_64-unknown-linux-gnu build"]
K["x86_64-unknown-none build"]
L["riscv64gc-unknown-none-elf build"]
M["aarch64-unknown-none-softfloat build"]
N["x86_64-unknown-linux-gnu tests only"]

A --> B
A --> C
B --> D
B --> E
B --> F
B --> G
C --> H
C --> I
F --> J
F --> K
F --> L
F --> M
G --> N

CI Job Execution Steps:

  1. Environment Setup: Ubuntu latest with nightly Rust toolchain
  2. Format Verification: cargo fmt --all -- --check
  3. Lint Analysis: cargo clippy with custom configuration
  4. Cross-Compilation: Build for all four target architectures
  5. Test Execution: Unit tests on Linux target only

Documentation Job Features:

  • Builds documentation with strict link checking
  • Enforces complete documentation coverage
  • Deploys to GitHub Pages on default branch pushes
  • Generates redirect index page automatically

Sources: .github/workflows/ci.yml(L6 - L31)  .github/workflows/ci.yml(L32 - L55) 

Documentation Standards

Documentation Pipeline Configuration

flowchart TD
A["RUSTDOCFLAGS Environment"]
B["-D rustdoc::broken_intra_doc_links"]
C["-D missing-docs"]
D["cargo doc --no-deps --all-features"]
E["Documentation Build"]
F["Index Page Generation"]
G["GitHub Pages Deployment"]
H["Default Branch Push"]
I["Pull Request"]
J["Build Only"]

A --> B
A --> C
D --> E
E --> F
F --> G
H --> G
I --> J

The documentation system enforces strict standards:

  • Broken Link Detection: All intra-doc links must resolve correctly
  • Complete Coverage: Missing documentation generates build failures
  • Dependency Exclusion: Only project documentation is generated (--no-deps)
  • Feature Complete: All features are documented (--all-features)

The system automatically generates an index redirect page using the crate name extracted from cargo tree output.

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47 - L48) 

Repository Structure and Ignored Files

Git Configuration

flowchart TD
A["Repository Root"]
B["Source Files"]
C["Ignored Artifacts"]
D["src/"]
E["Cargo.toml"]
F[".github/workflows/"]
G["/target"]
H["/.vscode"]
I[".DS_Store"]
J["Cargo.lock"]

A --> B
A --> C
B --> D
B --> E
B --> F
C --> G
C --> H
C --> I
C --> J

Ignored Files and Directories:

  • /target: Rust build artifacts and compiled binaries
  • /.vscode: Visual Studio Code workspace settings
  • .DS_Store: macOS filesystem metadata files
  • Cargo.lock: Dependency lock file (excluded for library crates)

The Cargo.lock exclusion indicates this crate follows Rust library conventions, allowing downstream projects to resolve their own dependency versions.

Sources: .gitignore(L1 - L4) 

Contributing Guidelines

Code Quality Requirements

All contributions must pass the complete CI pipeline:

  1. Format Compliance: Code must conform to rustfmt standards
  2. Lint Compliance: Must pass clippy analysis with project-specific exceptions
  3. Cross-Platform Compatibility: Must build successfully on all four target architectures
  4. Test Coverage: New functionality requires corresponding unit tests
  5. Documentation: All public APIs must include comprehensive documentation

Development Branch Strategy

The project uses a straightforward branching model:

  • Default Branch: Primary development and release branch
  • Feature Branches: Individual contributions via pull requests
  • GitHub Pages: Automatic deployment from default branch

Documentation deployment occurs automatically on default branch pushes, ensuring the published documentation remains current with the latest code changes.

Sources: .github/workflows/ci.yml(L3)  .github/workflows/ci.yml(L50) 

Building and Testing

Relevant source files

This page documents the automated build and testing infrastructure for the timer_list crate, including the CI/CD pipeline configuration, supported target architectures, and local development commands. The information covers both automated workflows triggered by Git events and manual testing procedures for local development.

For information about the project file structure and development environment setup, see Project Structure.

CI/CD Pipeline Overview

The timer_list crate uses GitHub Actions for continuous integration and deployment. The pipeline consists of two main jobs that ensure code quality, cross-platform compatibility, and automated documentation deployment.

Pipeline Architecture

flowchart TD
A["push/pull_request"]
B["GitHub Actions Trigger"]
C["ci job"]
D["doc job"]
E["ubuntu-latest runner"]
F["ubuntu-latest runner"]
G["Matrix Strategy"]
H["rust-toolchain: nightly"]
I["4 target architectures"]
J["Format Check"]
K["Clippy Linting"]
L["Multi-target Build"]
M["Unit Tests"]
N["Documentation Build"]
O["GitHub Pages Deploy"]
P["x86_64-unknown-linux-gnu only"]
Q["gh-pages branch"]

A --> B
B --> C
B --> D
C --> E
D --> F
E --> G
E --> J
E --> K
E --> L
E --> M
F --> N
F --> O
G --> H
G --> I
M --> P
O --> Q

The pipeline implements a fail-fast strategy set to false, allowing all target builds to complete even if one fails, providing comprehensive feedback across all supported architectures.

Sources: .github/workflows/ci.yml(L1 - L56) 

Build Matrix Configuration

The CI system uses a matrix strategy to test across multiple configurations simultaneously:

ComponentValue
Rust Toolchainnightly
Runner OSubuntu-latest
Fail Fastfalse
Target Architectures4 cross-compilation targets
flowchart TD
A["Matrix Strategy"]
B["nightly toolchain"]
C["Target Matrix"]
D["x86_64-unknown-linux-gnu"]
E["x86_64-unknown-none"]
F["riscv64gc-unknown-none-elf"]
G["aarch64-unknown-none-softfloat"]
H["rust-src component"]
I["clippy component"]
J["rustfmt component"]

A --> B
A --> C
B --> H
B --> I
B --> J
C --> D
C --> E
C --> F
C --> G

The pipeline installs additional Rust components (rust-src, clippy, rustfmt) and configures targets for each architecture in the matrix.

Sources: .github/workflows/ci.yml(L8 - L19) 

Target Architecture Support

The crate supports four target architectures, reflecting its use in embedded and operating system environments:

Architecture Details

TargetPurposeTesting
x86_64-unknown-linux-gnuStandard Linux developmentFull testing enabled
x86_64-unknown-noneBare metal x86_64Build verification only
riscv64gc-unknown-none-elfRISC-V embedded systemsBuild verification only
aarch64-unknown-none-softfloatARM64 embedded systemsBuild verification only
flowchart TD
A["timer_list Crate"]
B["Cross-Platform Support"]
C["x86_64-unknown-linux-gnu"]
D["x86_64-unknown-none"]
E["riscv64gc-unknown-none-elf"]
F["aarch64-unknown-none-softfloat"]
G["cargo test enabled"]
H["cargo build only"]
I["cargo build only"]
J["cargo build only"]
K["Unit test execution"]
L["no-std compatibility"]
M["RISC-V validation"]
N["ARM64 validation"]

A --> B
B --> C
B --> D
B --> E
B --> F
C --> G
D --> H
E --> I
F --> J
G --> K
H --> L
I --> M
J --> N

Unit tests execute only on x86_64-unknown-linux-gnu due to the testing framework requirements, while other targets verify no-std compatibility and cross-compilation success.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L29 - L30) 

Build and Test Commands

The CI pipeline executes a specific sequence of commands for each target architecture. These commands can also be run locally for development purposes.

Core CI Commands

sequenceDiagram
    participant DeveloperCI as "Developer/CI"
    participant RustToolchain as "Rust Toolchain"
    participant Cargo as "Cargo"
    participant TargetBinary as "Target Binary"

    DeveloperCI ->> RustToolchain: "rustc --version --verbose"
    RustToolchain -->> DeveloperCI: "Version information"
    DeveloperCI ->> Cargo: "cargo fmt --all -- --check"
    Cargo -->> DeveloperCI: "Format validation"
    DeveloperCI ->> Cargo: "cargo clippy --target TARGET --all-features"
    Cargo -->> DeveloperCI: "Lint results"
    DeveloperCI ->> Cargo: "cargo build --target TARGET --all-features"
    Cargo ->> TargetBinary: "Compile for target"
    TargetBinary -->> DeveloperCI: "Build artifacts"
    alt "x86_64-unknown-linux-gnu only"
        DeveloperCI ->> Cargo: "cargo test --target TARGET -- --nocapture"
        Cargo -->> DeveloperCI: "Test results"
    end

Local Development Commands

For local development, developers can run the same commands used in CI:

CommandPurpose
cargo fmt --all -- --checkVerify code formatting
cargo clippy --all-featuresRun linter with all features
cargo build --all-featuresBuild with all features enabled
cargo test -- --nocaptureRun unit tests with output

The --all-features flag ensures that all conditional compilation features are enabled during builds and linting.

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation Generation

The doc job handles automated documentation generation and deployment to GitHub Pages.

Documentation Workflow

flowchart TD
A["doc job trigger"]
B["ubuntu-latest"]
C["Rust nightly setup"]
D["cargo doc build"]
E["RUSTDOCFLAGS validation"]
F["broken_intra_doc_links check"]
G["missing-docs check"]
H["target/doc generation"]
I["index.html redirect"]
J["default branch?"]
K["Deploy to gh-pages"]
L["Skip deployment"]
M["GitHub Pages"]

A --> B
B --> C
C --> D
D --> E
D --> H
E --> F
E --> G
H --> I
I --> J
J --> K
J --> L
K --> M

The documentation job includes strict validation using RUSTDOCFLAGS to ensure documentation quality by failing on broken internal links and missing documentation.

Documentation Commands

The documentation generation process uses these key commands:

  • cargo doc --no-deps --all-features - Generate documentation without dependencies
  • cargo tree | head -1 | cut -d' ' -f1 - Extract crate name for redirect
  • Auto-generated index.html redirect to the main crate documentation

Sources: .github/workflows/ci.yml(L32 - L55) 

Error Handling and Permissions

The CI configuration includes specific error handling strategies:

  • Continue on Error: Documentation builds continue on error for non-default branches and non-PR events
  • Permissions: The doc job requires contents: write permission for GitHub Pages deployment
  • Conditional Deployment: Pages deployment only occurs on pushes to the default branch

The clippy command includes the flag -A clippy::new_without_default to suppress specific linting warnings that are not relevant to this crate's design patterns.

Sources: .github/workflows/ci.yml(L25)  .github/workflows/ci.yml(L36 - L37)  .github/workflows/ci.yml(L45)  .github/workflows/ci.yml(L50) 

Project Structure

Relevant source files

This document provides an overview of the timer_list repository organization, key configuration files, and development environment setup. It is intended for contributors who need to understand how the codebase is structured and how to set up a local development environment.

For information about building and testing the crate, see Building and Testing. For details about the core API implementation, see Core API Reference.

Repository Layout

The timer_list repository follows a standard Rust crate structure with additional configuration for CI/CD and documentation.

Overall Repository Structure

flowchart TD
subgraph workflows_directory["workflows/ Directory"]
    ci_yml["ci.yml"]
end
subgraph github_directory[".github/ Directory"]
    workflows_dir["workflows/"]
end
subgraph src_directory["src/ Directory"]
    lib_rs["lib.rs"]
end
subgraph timer_list_repository["timer_list Repository Root"]
    cargo_toml["Cargo.toml"]
    src_dir["src/"]
    readme["README.md"]
    gitignore[".gitignore"]
    github_dir[".github/"]
end

cargo_toml --> src_dir
github_dir --> workflows_dir
src_dir --> lib_rs
workflows_dir --> ci_yml

Sources: Cargo.toml(L1 - L15)  .gitignore(L1 - L5) 

File Organization Principles

The repository follows these organizational principles:

Directory/FilePurposeKey Contents
Cargo.tomlPackage configurationDependencies, metadata, build settings
src/lib.rsCore implementationTimerList,TimerEventtrait, main API
.gitignoreVersion control exclusionsBuild artifacts, IDE files, OS files
.github/workflows/CI/CD automationFormat checking, linting, multi-arch builds
README.mdProject documentationUsage examples, feature overview

Sources: Cargo.toml(L1 - L15)  .gitignore(L1 - L5) 

Core Configuration Files

Package Configuration

The Cargo.toml file defines the crate's identity, dependencies, and metadata:

flowchart TD
subgraph cargo_toml_structure["Cargo.toml Structure"]
    name_field["name = timer_list"]
    package_section["[package]"]
    dependencies_section["[dependencies]"]
end
subgraph package_metadata["Package Metadata"]
    name_field["name = timer_list"]
    version_field["version = 0.1.0"]
    edition_field["edition = 2021"]
    author_field["authors"]
    description_field["description"]
    license_field["license"]
    homepage_field["homepage"]
    repository_field["repository"]
    documentation_field["documentation"]
    keywords_field["keywords"]
    categories_field["categories"]
    package_section["[package]"]
end
empty_deps["(empty)"]

dependencies_section --> empty_deps

Sources: Cargo.toml(L1 - L15) 

Key Package Metadata

FieldValuePurpose
name"timer_list"Crate identifier for cargo and crates.io
version"0.1.0"Semantic version following semver
edition"2021"Rust edition for language features
descriptionTimer events descriptionBrief summary for crate discovery
licenseTriple licenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
categories["no-std", "data-structures", "date-and-time"]Categorization for crates.io
keywords["arceos", "timer", "events"]Search tags for discoverability

Sources: Cargo.toml(L2 - L12) 

External References

The package configuration includes several external links:

Sources: Cargo.toml(L8 - L10) 

Dependencies

The crate currently has no external dependencies, as shown by the empty [dependencies] section at Cargo.toml(L14 - L15)  This supports the crate's no-std design and minimal footprint for embedded environments.

Sources: Cargo.toml(L14 - L15) 

Git Configuration

The .gitignore file excludes development artifacts and environment-specific files:

flowchart TD
subgraph ignored_patterns["Ignored Patterns"]
    target_dir["/target"]
    vscode_dir["/.vscode"]
    ds_store[".DS_Store"]
    cargo_lock["Cargo.lock"]
end
subgraph git_ignore_categories["Git Ignore Categories"]
    build_artifacts["Build Artifacts"]
    ide_files["IDE Files"]
    os_files["OS Files"]
    security_files["Security Files"]
end

build_artifacts --> cargo_lock
build_artifacts --> target_dir
ide_files --> vscode_dir
os_files --> ds_store

Sources: .gitignore(L1 - L5) 

Ignored File Types

PatternCategoryReason for Exclusion
/targetBuild artifactsCargo build output directory
/.vscodeIDE configurationVisual Studio Code workspace settings
.DS_StoreOS metadatamacOS directory metadata files
Cargo.lockDependency lockfileGenerated file, not needed for libraries

Sources: .gitignore(L1 - L4) 

Development Environment Setup

Prerequisites

To contribute to the timer_list crate, developers need:

  1. Rust Toolchain: Edition 2021 or later
  2. Target Support: Multiple architectures supported by CI
  3. Development Tools: cargo fmt, cargo clippy for code quality

Local Development Workflow

sequenceDiagram
    participant Developer as "Developer"
    participant LocalEnvironment as "Local Environment"
    participant GitRepository as "Git Repository"
    participant CIPipeline as "CI Pipeline"

    Developer ->> LocalEnvironment: "cargo check"
    Developer ->> LocalEnvironment: "cargo fmt"
    Developer ->> LocalEnvironment: "cargo clippy"
    Developer ->> LocalEnvironment: "cargo test"
    Developer ->> GitRepository: "git commit & push"
    GitRepository ->> CIPipeline: "trigger workflows"
    CIPipeline ->> CIPipeline: "multi-arch builds"
    CIPipeline ->> CIPipeline: "documentation generation"

Sources: Cargo.toml(L1 - L15)  .gitignore(L1 - L5) 

File Modification Guidelines

When modifying the repository structure:

  • Core Implementation: Changes to src/lib.rs affect the main API
  • Package Configuration: Changes to Cargo.toml affect crate metadata and dependencies
  • CI Configuration: Changes to .github/workflows/ affect automated testing
  • Documentation: Changes to README.md affect project overview and examples

The minimal file structure supports the crate's focused purpose as a lightweight timer event management system for no-std environments.

Sources: Cargo.toml(L12)  .gitignore(L1 - L5) 

Overview

Relevant source files

This document provides a comprehensive overview of the slab_allocator crate, a hybrid memory allocation system designed for no_std environments. The allocator combines the performance benefits of slab allocation for small, fixed-size blocks with the flexibility of buddy allocation for larger, variable-size requests.

For detailed implementation specifics of individual components, see Core Architecture. For practical usage examples and setup instructions, see Getting Started.

Purpose and Target Environment

The slab_allocator crate addresses the critical need for efficient memory management in resource-constrained environments where the standard library is unavailable. It targets embedded systems, kernel development, and real-time applications that require deterministic allocation performance.

The allocator implements a hybrid strategy that provides O(1) allocation for blocks ≤ 4096 bytes through slab allocation, while falling back to a buddy system allocator for larger requests. This design balances performance predictability with memory utilization efficiency.

Sources: Cargo.toml(L8 - L9)  src/lib.rs(L1 - L3) 

System Architecture

The allocator's architecture centers around the Heap struct, which orchestrates allocation requests between multiple specialized components based on request size and alignment requirements.

Core Components Mapping

flowchart TD
subgraph subGraph4["Internal Slab Components"]
    FreeBlockList["FreeBlockList<BLK_SIZE>(src/slab.rs:65)"]
    FreeBlock["FreeBlock(src/slab.rs:112)"]
end
subgraph subGraph3["Large Block Allocation"]
    BuddyAllocator["buddy_allocatorbuddy_system_allocator::Heap<32>(src/lib.rs:48)"]
end
subgraph subGraph2["Slab Allocation System"]
    Slab64["slab_64_bytes: Slab<64>(src/lib.rs:41)"]
    Slab128["slab_128_bytes: Slab<128>(src/lib.rs:42)"]
    Slab256["slab_256_bytes: Slab<256>(src/lib.rs:43)"]
    Slab512["slab_512_bytes: Slab<512>(src/lib.rs:44)"]
    Slab1024["slab_1024_bytes: Slab<1024>(src/lib.rs:45)"]
    Slab2048["slab_2048_bytes: Slab<2048>(src/lib.rs:46)"]
    Slab4096["slab_4096_bytes: Slab<4096>(src/lib.rs:47)"]
end
subgraph subGraph1["Allocation Routing"]
    HeapAllocator["HeapAllocator enum(src/lib.rs:27)"]
    layout_to_allocator["layout_to_allocator()(src/lib.rs:208)"]
end
subgraph subGraph0["Primary Interface"]
    Heap["Heap(src/lib.rs:40)"]
end

FreeBlockList --> FreeBlock
Heap --> HeapAllocator
Heap --> layout_to_allocator
Slab1024 --> FreeBlockList
Slab128 --> FreeBlockList
Slab2048 --> FreeBlockList
Slab256 --> FreeBlockList
Slab4096 --> FreeBlockList
Slab512 --> FreeBlockList
Slab64 --> FreeBlockList
layout_to_allocator --> BuddyAllocator
layout_to_allocator --> Slab1024
layout_to_allocator --> Slab128
layout_to_allocator --> Slab2048
layout_to_allocator --> Slab256
layout_to_allocator --> Slab4096
layout_to_allocator --> Slab512
layout_to_allocator --> Slab64

Sources: src/lib.rs(L40 - L49)  src/lib.rs(L27 - L36)  src/slab.rs(L4 - L7)  src/slab.rs(L65 - L68) 

Allocation Decision Flow

The system routes allocation requests through a deterministic decision tree based on the Layout parameter's size and alignment requirements.

flowchart TD
Request["allocate(layout: Layout)(src/lib.rs:135)"]
Router["layout_to_allocator(&layout)(src/lib.rs:208-226)"]
SizeCheck["Size > 4096?(src/lib.rs:209)"]
Buddy["HeapAllocator::BuddyAllocator(src/lib.rs:210)"]
AlignCheck["Check size and alignmentconstraints (src/lib.rs:211-225)"]
Slab64Check["≤64 && align≤64?(src/lib.rs:211-212)"]
Slab128Check["≤128 && align≤128?(src/lib.rs:213-214)"]
Slab256Check["≤256 && align≤256?(src/lib.rs:215-216)"]
Slab512Check["≤512 && align≤512?(src/lib.rs:217-218)"]
Slab1024Check["≤1024 && align≤1024?(src/lib.rs:219-220)"]
Slab2048Check["≤2048 && align≤2048?(src/lib.rs:221-222)"]
SlabDefault["HeapAllocator::Slab4096Bytes(src/lib.rs:224)"]
Slab64Enum["HeapAllocator::Slab64Bytes"]
Slab128Enum["HeapAllocator::Slab128Bytes"]
Slab256Enum["HeapAllocator::Slab256Bytes"]
Slab512Enum["HeapAllocator::Slab512Bytes"]
Slab1024Enum["HeapAllocator::Slab1024Bytes"]
Slab2048Enum["HeapAllocator::Slab2048Bytes"]
BuddyImpl["buddy_allocator.alloc()(src/lib.rs:158-162)"]
SlabImpl["slab_X_bytes.allocate()(src/lib.rs:137-157)"]

AlignCheck --> Slab1024Check
AlignCheck --> Slab128Check
AlignCheck --> Slab2048Check
AlignCheck --> Slab256Check
AlignCheck --> Slab512Check
AlignCheck --> Slab64Check
AlignCheck --> SlabDefault
Buddy --> BuddyImpl
Request --> Router
Router --> SizeCheck
SizeCheck --> AlignCheck
SizeCheck --> Buddy
Slab1024Check --> Slab1024Enum
Slab1024Enum --> SlabImpl
Slab128Check --> Slab128Enum
Slab128Enum --> SlabImpl
Slab2048Check --> Slab2048Enum
Slab2048Enum --> SlabImpl
Slab256Check --> Slab256Enum
Slab256Enum --> SlabImpl
Slab512Check --> Slab512Enum
Slab512Enum --> SlabImpl
Slab64Check --> Slab64Enum
Slab64Enum --> SlabImpl
SlabDefault --> SlabImpl

Sources: src/lib.rs(L135 - L164)  src/lib.rs(L208 - L226) 

Memory Management Strategy

The allocator employs a two-tier memory management approach that optimizes for different allocation patterns:

Allocation SizeStrategyAllocatorTime ComplexityUse Case
≤ 64 bytesFixed-size slabSlab<64>O(1)Small objects, metadata
65-128 bytesFixed-size slabSlab<128>O(1)Small structures
129-256 bytesFixed-size slabSlab<256>O(1)Medium structures
257-512 bytesFixed-size slabSlab<512>O(1)Small buffers
513-1024 bytesFixed-size slabSlab<1024>O(1)Medium buffers
1025-2048 bytesFixed-size slabSlab<2048>O(1)Large structures
2049-4096 bytesFixed-size slabSlab<4096>O(1)Page-sized allocations
> 4096 bytesBuddy systembuddy_system_allocatorO(log n)Large buffers, dynamic data

Sources: src/lib.rs(L208 - L226)  src/lib.rs(L41 - L48) 

Dynamic Growth Mechanism

When a slab exhausts its available blocks, the allocator automatically requests additional memory from the buddy allocator in fixed-size chunks defined by SET_SIZE (64 blocks per growth operation).

flowchart TD
SlabEmpty["Slab free_block_list empty(src/slab.rs:42)"]
RequestGrowth["Request SET_SIZE * BLK_SIZEfrom buddy allocator(src/slab.rs:43-47)"]
GrowSlab["grow() creates new FreeBlockList(src/slab.rs:26-33)"]
MergeBlocks["Merge new blocks intoexisting free_block_list(src/slab.rs:30-32)"]
ReturnBlock["Return first available block(src/slab.rs:49)"]

GrowSlab --> MergeBlocks
MergeBlocks --> ReturnBlock
RequestGrowth --> GrowSlab
SlabEmpty --> RequestGrowth

Sources: src/slab.rs(L35 - L55)  src/slab.rs(L26 - L33)  src/lib.rs(L24) 

Platform Support and Testing

The allocator supports multiple embedded architectures and maintains compatibility through extensive CI/CD testing:

Target PlatformPurposeTest Coverage
x86_64-unknown-linux-gnuDevelopment/testingFull unit tests
x86_64-unknown-noneBare metal x86_64Build verification
riscv64gc-unknown-none-elfRISC-V embeddedBuild verification
aarch64-unknown-none-softfloatARM64 embeddedBuild verification

The test suite includes allocation patterns from simple double-usize allocations to complex multi-size scenarios with varying alignment requirements, ensuring robust behavior across different usage patterns.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L28 - L30)  src/tests.rs(L39 - L163) 

Key Features and Constraints

Design Features

  • no_std compatibility: No dependency on the standard library
  • Deterministic performance: O(1) allocation for blocks ≤ 4096 bytes
  • Automatic growth: Dynamic expansion when slabs become exhausted
  • Memory statistics: Runtime visibility into allocation patterns
  • Multiple alignment support: Handles various alignment requirements efficiently

System Constraints

  • Minimum heap size: 32KB (MIN_HEAP_SIZE = 0x8000)
  • Page alignment requirement: Heap start address must be 4096-byte aligned
  • Growth granularity: Memory added in 4096-byte increments
  • Slab threshold: Fixed 4096-byte boundary between slab and buddy allocation

Sources: src/lib.rs(L25)  src/lib.rs(L59 - L71)  src/lib.rs(L95 - L106) 

Getting Started

Relevant source files

This page provides a practical introduction to using the slab_allocator crate, covering dependency setup, basic initialization, and simple usage patterns. The examples shown here demonstrate core allocation patterns using code from the test suite. For detailed architectural information, see Core Architecture. For complete API documentation, see API Reference.

Adding the Dependency

Add the slab_allocator crate to your Cargo.toml file. The crate is designed for no_std environments and requires the allocator_api feature.

[dependencies]
slab_allocator = "0.3.1"

The crate automatically includes buddy_system_allocator version 0.10 as a dependency for handling large allocations over 4096 bytes.

Sources: Cargo.toml(L1 - L13) 

Basic Setup and Initialization

Memory Requirements

The heap allocator requires page-aligned memory with specific size constraints defined in the core library:

RequirementValuePurpose
Minimum heap size32KB (MIN_HEAP_SIZE = 0x8000)Ensures sufficient space for slab initialization
Address alignment4096 bytesPage boundary alignment for memory management
Size alignmentMultiple ofMIN_HEAP_SIZESimplifies buddy allocator integration

Heap Initialization Flow

flowchart TD
A["Memory Region"]
B["Heap::new()"]
C["Address Validation"]
D["Size Validation"]
E["Initialize Slabs"]
F["Initialize buddy_system_allocator::Heap"]
G["Ready for Allocation"]
H["heap_start_addr % 4096 == 0"]
I["heap_size >= MIN_HEAP_SIZE"]
J["heap_size % MIN_HEAP_SIZE == 0"]
K["Slab<64>::new()"]
L["Slab<128>::new()"]
M["...up to Slab<4096>"]
N["buddy.init(heap_start_addr, heap_size)"]

A --> B
B --> C
C --> D
C --> H
D --> E
D --> I
D --> J
E --> F
E --> K
E --> L
E --> M
F --> G
F --> N

Sources: src/lib.rs(L52 - L86)  src/lib.rs(L24 - L26) 

Basic Initialization Example

The test suite demonstrates standard heap initialization patterns:

// From test suite - basic heap setup
const HEAP_SIZE: usize = 16 * 4096;  // 64KB

#[repr(align(4096))]
struct TestHeap {
    heap_space: [u8; HEAP_SIZE],
}

fn new_heap() -> Heap {
    let test_heap = TestHeap {
        heap_space: [0u8; HEAP_SIZE],
    };
    unsafe { 
        Heap::new(&test_heap.heap_space[0] as *const u8 as usize, HEAP_SIZE) 
    }
}

Sources: src/tests.rs(L5)  src/tests.rs(L8 - L24) 

Simple Usage Examples

Basic Allocation and Deallocation

The fundamental allocation pattern uses Layout to specify size and alignment requirements:

use alloc::alloc::Layout;
use core::mem::{size_of, align_of};

let mut heap = new_heap();
let layout = Layout::from_size_align(size_of::<usize>() * 2, align_of::<usize>()).unwrap();

// Allocate memory
let addr = heap.allocate(layout.clone()).unwrap();

// Use the memory
unsafe {
    *(addr as *mut (usize, usize)) = (0xdeafdeadbeafbabe, 0xdeafdeadbeafbabe);
    
    // Deallocate when done
    heap.deallocate(addr, layout);
}

Sources: src/tests.rs(L47 - L68) 

Multi-Size Allocation Example

The allocator efficiently handles multiple allocation sizes simultaneously:

let mut heap = new_heap();
let base_size = size_of::<usize>();
let base_align = align_of::<usize>();

let layout_1 = Layout::from_size_align(base_size * 2, base_align).unwrap();
let layout_2 = Layout::from_size_align(base_size * 3, base_align).unwrap();
let layout_3 = Layout::from_size_align(base_size * 3, base_align * 8).unwrap();
let layout_4 = Layout::from_size_align(base_size * 10, base_align).unwrap();

let x = heap.allocate(layout_1.clone()).unwrap();
let y = heap.allocate(layout_2.clone()).unwrap();
let z = heap.allocate(layout_3.clone()).unwrap();

Sources: src/tests.rs(L90 - L117) 

Allocation Strategy Overview

Layout-to-Allocator Routing

The heap automatically routes allocation requests to the appropriate allocator based on size and alignment requirements:

flowchart TD
A["Layout::from_size_align()"]
B["Heap::allocate()"]
C["Heap::layout_to_allocator()"]
D["layout.size() > 4096?"]
E["HeapAllocator::BuddyAllocator"]
F["Select Slab Allocator"]
G["size <= 64 && align <= 64?"]
H["HeapAllocator::Slab64Bytes"]
I["size <= 128 && align <= 128?"]
J["HeapAllocator::Slab128Bytes"]
K["... up to Slab4096Bytes"]
L["slab_64_bytes.allocate()"]
M["slab_128_bytes.allocate()"]
N["buddy_allocator.alloc()"]
O["O(1) allocation"]
P["O(n) allocation"]

A --> B
B --> C
C --> D
D --> E
D --> F
E --> N
F --> G
G --> H
G --> I
H --> L
I --> J
I --> K
J --> M
L --> O
M --> O
N --> P

Sources: src/lib.rs(L208 - L226)  src/lib.rs(L135 - L164) 

Slab Size Categories

The allocator maintains seven fixed-size slab categories, each optimized for specific allocation patterns:

Slab TypeBlock SizeTarget Use Cases
Slab<64>64 bytesSmall structures, basic types
Slab<128>128 bytesMedium structures
Slab<256>256 bytesLarger data structures
Slab<512>512 bytesSmall buffers
Slab<1024>1024 bytesMedium buffers
Slab<2048>2048 bytesLarge buffers
Slab<4096>4096 bytesPage-sized allocations
Buddy Allocator>4096 bytesVariable large allocations

Sources: src/lib.rs(L27 - L36)  src/lib.rs(L40 - L49) 

Memory Statistics

The heap provides real-time memory usage statistics:

let total = heap.total_bytes();      // Total heap capacity
let used = heap.used_bytes();        // Currently allocated bytes  
let available = heap.available_bytes(); // Free bytes remaining
let (min_size, max_size) = heap.usable_size(layout); // Allocation bounds

Sources: src/lib.rs(L228 - L255)  src/lib.rs(L192 - L205) 

Error Handling

The allocator returns AllocError for failed allocations, typically due to insufficient memory:

let layout = Layout::from_size_align(HEAP_SIZE + 1, align_of::<usize>()).unwrap();
let result = heap.allocate(layout);
assert!(result.is_err()); // Out of memory

Sources: src/tests.rs(L40 - L45) 

Next Steps

Sources: src/lib.rs(L1 - L257)  src/tests.rs(L1 - L164) 

Core Architecture

Relevant source files

This document provides a comprehensive overview of the slab allocator's hybrid memory management architecture. It explains the fundamental design decisions, component interactions, and allocation strategies that enable efficient memory management in no_std environments. For detailed implementation specifics of individual components, see Heap Allocator Design and Slab Implementation.

Hybrid Allocation Strategy

The slab allocator implements a two-tier memory allocation system that optimizes for both small fixed-size allocations and large variable-size allocations. The core design principle is to route allocation requests to the most appropriate allocator based on size and alignment requirements.

Allocation Decision Tree

flowchart TD
A["Layout::size() > 4096"]
B["Size Check"]
C["buddy_system_allocator::Heap<32>"]
D["Size and Alignment Analysis"]
E["size <= 64 && align <= 64"]
F["Slab<64>"]
G["size <= 128 && align <= 128"]
H["Slab<128>"]
I["size <= 256 && align <= 256"]
J["Slab<256>"]
K["size <= 512 && align <= 512"]
L["Slab<512>"]
M["size <= 1024 && align <= 1024"]
N["Slab<1024>"]
O["size <= 2048 && align <= 2048"]
P["Slab<2048>"]
Q["Slab<4096>"]

A --> B
B --> C
B --> D
D --> E
E --> F
E --> G
G --> H
G --> I
I --> J
I --> K
K --> L
K --> M
M --> N
M --> O
O --> P
O --> Q

Sources: src/lib.rs(L207 - L226) 

Component Architecture

The Heap struct orchestrates multiple specialized allocators to provide a unified memory management interface:

flowchart TD
subgraph subGraph1["HeapAllocator Enum"]
    J["Slab64Bytes"]
    K["Slab128Bytes"]
    L["Slab256Bytes"]
    M["Slab512Bytes"]
    N["Slab1024Bytes"]
    O["Slab2048Bytes"]
    P["Slab4096Bytes"]
    Q["BuddyAllocator"]
end
subgraph subGraph0["Heap Struct"]
    A["pub struct Heap"]
    B["slab_64_bytes: Slab<64>"]
    C["slab_128_bytes: Slab<128>"]
    D["slab_256_bytes: Slab<256>"]
    E["slab_512_bytes: Slab<512>"]
    F["slab_1024_bytes: Slab<1024>"]
    G["slab_2048_bytes: Slab<2048>"]
    H["slab_4096_bytes: Slab<4096>"]
    I["buddy_allocator: buddy_system_allocator::Heap<32>"]
end
R["layout_to_allocator()"]

A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
A --> I
J --> B
K --> C
L --> D
M --> E
N --> F
O --> G
P --> H
Q --> I
R --> J
R --> K
R --> L
R --> M
R --> N
R --> O
R --> P
R --> Q

Sources: src/lib.rs(L40 - L49)  src/lib.rs(L27 - L36) 

Memory Organization and Block Management

Each slab allocator manages fixed-size blocks through a linked list of free blocks. The system uses a hybrid approach where slabs can dynamically grow by requesting memory from the buddy allocator.

Slab Internal Structure

flowchart TD
subgraph subGraph1["FreeBlockList Operations"]
    E["pop()"]
    F["Returns available block"]
    G["push()"]
    H["Adds freed block to list"]
    I["new()"]
    J["Initializes from memory range"]
end
subgraph Slab<BLK_SIZE>["Slab"]
    A["free_block_list: FreeBlockList"]
    B["head: Option<&'static mut FreeBlock>"]
    C["len: usize"]
    D["total_blocks: usize"]
end
subgraph subGraph2["Dynamic Growth"]
    K["allocate() fails"]
    L["Request SET_SIZE * BLK_SIZE from buddy"]
    M["grow()"]
    N["Create new FreeBlockList"]
    O["Transfer blocks to main list"]
end

A --> B
A --> C
A --> E
A --> G
B --> F
B --> H
E --> F
G --> H
I --> J
K --> L
L --> M
M --> N
N --> O

Sources: src/slab.rs(L4 - L7)  src/slab.rs(L65 - L68) 

Allocation Flow and Integration Points

The allocation process demonstrates the tight integration between slab allocators and the buddy allocator, with automatic fallback and growth mechanisms.

Allocation Process Flow

sequenceDiagram
    participant Client as Client
    participant Heap as Heap
    participant Slab as Slab
    participant FreeBlockList as FreeBlockList
    participant BuddyAllocator as BuddyAllocator

    Client ->> Heap: allocate(layout)
    Heap ->> Heap: layout_to_allocator(layout)
    alt Size <= 4096
        Heap ->> Slab: allocate(layout, buddy)
        Slab ->> FreeBlockList: pop()
    alt Free block available
        FreeBlockList -->> Slab: Some(block)
        Slab -->> Heap: Ok(block.addr())
    else No free blocks
        Slab ->> BuddyAllocator: alloc(SET_SIZE * BLK_SIZE)
        BuddyAllocator -->> Slab: Ok(ptr)
        Slab ->> Slab: grow(ptr, size)
        Slab ->> FreeBlockList: pop()
        FreeBlockList -->> Slab: Some(block)
        Slab -->> Heap: Ok(block.addr())
    end
    Heap -->> Client: Ok(address)
    else Size > 4096
        Heap ->> BuddyAllocator: alloc(layout)
        BuddyAllocator -->> Heap: Ok(ptr)
        Heap -->> Client: Ok(address)
    end

Sources: src/lib.rs(L135 - L164)  src/slab.rs(L35 - L55) 

Performance Characteristics and Design Rationale

Allocation Complexity

Allocation TypeTime ComplexitySpace OverheadUse Case
Slab (≤ 4096 bytes)O(1)Fixed per slabFrequent small allocations
Buddy (> 4096 bytes)O(log n)VariableLarge or variable-size allocations
Slab growthO(SET_SIZE)Batch allocationSlab expansion

Key Design Constants

const SET_SIZE: usize = 64;           // Blocks per growth operation
const MIN_HEAP_SIZE: usize = 0x8000;  // Minimum heap size (32KB)

The SET_SIZE constant controls the granularity of slab growth operations. When a slab runs out of free blocks, it requests SET_SIZE * BLK_SIZE bytes from the buddy allocator, ensuring efficient batch allocation while minimizing buddy allocator overhead.

Sources: src/lib.rs(L24 - L25)  src/slab.rs(L44 - L48) 

Memory Statistics and Monitoring

The architecture provides comprehensive memory usage tracking across both allocation subsystems:

Statistics Calculation

flowchart TD
subgraph subGraph1["Per-Slab Calculation"]
    I["slab.total_blocks() * BLOCK_SIZE"]
    J["slab.used_blocks() * BLOCK_SIZE"]
end
subgraph subGraph0["Memory Metrics"]
    A["total_bytes()"]
    B["Sum of all slab capacities"]
    C["buddy_allocator.stats_total_bytes()"]
    D["used_bytes()"]
    E["Sum of allocated slab blocks"]
    F["buddy_allocator.stats_alloc_actual()"]
    G["available_bytes()"]
    H["total_bytes() - used_bytes()"]
end

A --> B
A --> C
B --> I
D --> E
D --> F
E --> J
G --> H

Sources: src/lib.rs(L228 - L255) 

Integration with Buddy Allocator

The system maintains a symbiotic relationship with the buddy_system_allocator crate, using it both as a fallback for large allocations and as a memory provider for slab growth operations.

Buddy Allocator Usage Patterns

  1. Direct allocation: Requests over 4096 bytes bypass slabs entirely
  2. Slab growth: Slabs request memory chunks for expansion
  3. Memory initialization: Initial heap setup uses buddy allocator
  4. Memory extension: Additional memory regions added through buddy allocator

The buddy allocator is configured with a 32-level tree (Heap<32>), providing efficient allocation for a wide range of block sizes while maintaining reasonable memory overhead.

Sources: src/lib.rs(L48)  src/lib.rs(L80 - L85)  src/lib.rs(L158 - L163) 

Heap Allocator Design

Relevant source files

This document covers the design and implementation of the main Heap struct, which serves as the primary interface for memory allocation in the slab_allocator crate. It explains the hybrid allocation strategy, routing logic, and integration with the buddy system allocator. For details about individual slab implementations and their internal mechanics, see Slab Implementation. For API usage examples and method documentation, see API Reference.

Core Components Overview

The heap allocator is built around the Heap struct, which coordinates between multiple fixed-size slab allocators and a buddy system allocator for larger requests.

Heap Structure Components

flowchart TD
subgraph subGraph0["Heap Struct Fields"]
    C1["SET_SIZE: usize = 64"]
    C2["MIN_HEAP_SIZE: usize = 0x8000"]
    B["buddy_allocator: buddy_system_allocator::Heap<32>"]
    subgraph subGraph1["HeapAllocator Enum Variants"]
        E["HeapAllocator"]
        E64["Slab64Bytes"]
        E128["Slab128Bytes"]
        E256["Slab256Bytes"]
        E512["Slab512Bytes"]
        E1024["Slab1024Bytes"]
        E2048["Slab2048Bytes"]
        E4096["Slab4096Bytes"]
        EB["BuddyAllocator"]
        H["Heap"]
        S64["slab_64_bytes: Slab<64>"]
        S128["slab_128_bytes: Slab<128>"]
        S256["slab_256_bytes: Slab<256>"]
        S512["slab_512_bytes: Slab<512>"]
        S1024["slab_1024_bytes: Slab<1024>"]
        S2048["slab_2048_bytes: Slab<2048>"]
        S4096["slab_4096_bytes: Slab<4096>"]
        subgraph Constants["Constants"]
            C1["SET_SIZE: usize = 64"]
            C2["MIN_HEAP_SIZE: usize = 0x8000"]
        end
    end
end

E --> E1024
E --> E128
E --> E2048
E --> E256
E --> E4096
E --> E512
E --> E64
E --> EB
H --> B
H --> S1024
H --> S128
H --> S2048
H --> S256
H --> S4096
H --> S512
H --> S64

Sources: src/lib.rs(L40 - L49)  src/lib.rs(L27 - L36)  src/lib.rs(L24 - L25) 

The Heap struct contains seven slab allocators, each handling fixed-size blocks, plus a buddy_system_allocator::Heap for variable-size allocations. The HeapAllocator enum provides a type-safe way to route allocation requests to the appropriate allocator.

Allocation Routing Strategy

The heart of the heap allocator's design is the layout_to_allocator function, which determines which allocator to use based on the requested layout's size and alignment requirements.

Layout Routing Logic

flowchart TD
L["Layout { size, align }"]
Check1["size > 4096?"]
BA["HeapAllocator::BuddyAllocator"]
Check64["size ≤ 64 && align ≤ 64?"]
S64["HeapAllocator::Slab64Bytes"]
Check128["size ≤ 128 && align ≤ 128?"]
S128["HeapAllocator::Slab128Bytes"]
Check256["size ≤ 256 && align ≤ 256?"]
S256["HeapAllocator::Slab256Bytes"]
Check512["size ≤ 512 && align ≤ 512?"]
S512["HeapAllocator::Slab512Bytes"]
Check1024["size ≤ 1024 && align ≤ 1024?"]
S1024["HeapAllocator::Slab1024Bytes"]
Check2048["size ≤ 2048 && align ≤ 2048?"]
S2048["HeapAllocator::Slab2048Bytes"]
S4096["HeapAllocator::Slab4096Bytes"]

Check1 --> BA
Check1 --> Check64
Check1024 --> Check2048
Check1024 --> S1024
Check128 --> Check256
Check128 --> S128
Check2048 --> S2048
Check2048 --> S4096
Check256 --> Check512
Check256 --> S256
Check512 --> Check1024
Check512 --> S512
Check64 --> Check128
Check64 --> S64
L --> Check1

Sources: src/lib.rs(L208 - L226) 

The routing logic follows a cascading pattern where both size and alignment constraints must be satisfied. This ensures that allocations are placed in the smallest slab that can accommodate both the requested size and alignment requirements.

Memory Management Workflow

The allocation and deallocation processes follow a consistent pattern of routing through the HeapAllocator enum to the appropriate underlying allocator.

Allocation Process Flow

sequenceDiagram
    participant ClientCode as "Client Code"
    participant Heapallocate as "Heap::allocate"
    participant layout_to_allocator as "layout_to_allocator"
    participant Slaballocate as "Slab::allocate"
    participant buddy_allocator as "buddy_allocator"

    ClientCode ->> Heapallocate: "allocate(layout)"
    Heapallocate ->> layout_to_allocator: "layout_to_allocator(&layout)"
    layout_to_allocator -->> Heapallocate: "HeapAllocator variant"
    alt "Slab allocation"
        Heapallocate ->> Slaballocate: "slab.allocate(layout, &mut buddy_allocator)"
        Slaballocate ->> Slaballocate: "Check free blocks"
    alt "No free blocks"
        Slaballocate ->> buddy_allocator: "Request memory for growth"
        buddy_allocator -->> Slaballocate: "Memory region"
        Slaballocate ->> Slaballocate: "Initialize new blocks"
    end
    Slaballocate -->> Heapallocate: "Result<usize, AllocError>"
    else "Buddy allocation"
        Heapallocate ->> buddy_allocator: "buddy_allocator.alloc(layout)"
        buddy_allocator -->> Heapallocate: "Result<NonNull<u8>, AllocError>"
    end
    Heapallocate -->> ClientCode: "Result<usize, AllocError>"

Sources: src/lib.rs(L135 - L164)  src/lib.rs(L177 - L190) 

Deallocation Process Flow

sequenceDiagram
    participant ClientCode as "Client Code"
    participant Heapdeallocate as "Heap::deallocate"
    participant layout_to_allocator as "layout_to_allocator"
    participant Slabdeallocate as "Slab::deallocate"
    participant buddy_allocator as "buddy_allocator"

    ClientCode ->> Heapdeallocate: "deallocate(ptr, layout)"
    Heapdeallocate ->> layout_to_allocator: "layout_to_allocator(&layout)"
    layout_to_allocator -->> Heapdeallocate: "HeapAllocator variant"
    alt "Slab deallocation"
        Heapdeallocate ->> Slabdeallocate: "slab.deallocate(ptr)"
        Slabdeallocate ->> Slabdeallocate: "Add block to free list"
        Slabdeallocate -->> Heapdeallocate: "void"
    else "Buddy deallocation"
        Heapdeallocate ->> buddy_allocator: "buddy_allocator.dealloc(ptr, layout)"
        buddy_allocator -->> Heapdeallocate: "void"
    end

Sources: src/lib.rs(L177 - L190) 

Performance Characteristics

The hybrid design provides different performance guarantees depending on allocation size:

Allocation SizeAllocator UsedTime ComplexityCharacteristics
≤ 4096 bytesSlab allocatorO(1)Fixed-size blocks, predictable performance
> 4096 bytesBuddy allocatorO(n)Variable-size blocks, flexible but slower

Sources: src/lib.rs(L134 - L135)  src/lib.rs(L172 - L173) 

The O(1) performance for small allocations is achieved because slab allocators maintain free lists of pre-allocated blocks. The O(n) complexity for large allocations reflects the buddy allocator's tree-based search for appropriately sized free blocks.

Memory Statistics and Monitoring

The Heap struct provides comprehensive memory usage statistics by aggregating data from all component allocators.

Statistics Methods

flowchart TD
subgraph subGraph0["Memory Statistics API"]
    TB["total_bytes()"]
    Calc1["Sum of slab total_blocks() * block_size"]
    Calc2["Unsupported markdown: list"]
    UB["used_bytes()"]
    Calc3["Sum of slab used_blocks() * block_size"]
    Calc4["Unsupported markdown: list"]
    AB["available_bytes()"]
    Calc5["total_bytes() - used_bytes()"]
    US["usable_size(layout)"]
    Route["Route by HeapAllocator"]
    SlabSize["Slab: (layout.size(), slab_size)"]
    BuddySize["Buddy: (layout.size(), layout.size())"]
end

AB --> Calc5
Route --> BuddySize
Route --> SlabSize
TB --> Calc1
TB --> Calc2
UB --> Calc3
UB --> Calc4
US --> Route

Sources: src/lib.rs(L229 - L255)  src/lib.rs(L194 - L205) 

The statistics methods provide real-time visibility into memory usage across both slab and buddy allocators, enabling monitoring and debugging of allocation patterns.

Integration Points

The heap allocator serves as a coordinator between the slab allocators and the buddy system allocator, handling several key integration scenarios:

Slab Growth Mechanism

When a slab runs out of free blocks, it requests additional memory from the buddy allocator through the Slab::allocate method. This allows slabs to grow dynamically based on demand while maintaining their fixed-block-size efficiency.

Memory Addition

The add_memory and _grow methods allow runtime expansion of the heap, with new memory being routed to the appropriate allocator:

Sources: src/lib.rs(L95 - L106)  src/lib.rs(L116 - L129) 

  • Public add_memory: Adds memory directly to the buddy allocator
  • Private _grow: Routes memory to specific slabs based on the HeapAllocator variant

This design enables flexible memory management where the heap can expand as needed while maintaining the performance characteristics of the hybrid allocation strategy.

Slab Implementation

Relevant source files

This document covers the internal implementation of the Slab struct and its associated components, including the free block management system, allocation/deallocation mechanisms, and dynamic growth strategies. This is the core component that provides O(1) fixed-size block allocation within the hybrid memory allocator.

For information about how slabs integrate into the overall heap allocation strategy, see Heap Allocator Design. For complete API documentation, see API Reference.

Slab Structure Overview

The slab allocator is implemented as a generic struct that manages fixed-size memory blocks. Each slab is parameterized by its block size and maintains a linked list of free blocks for rapid allocation and deallocation.

Slab Core Components

flowchart TD
subgraph subGraph0["Key Methods"]
    I["new()"]
    J["allocate()"]
    K["deallocate()"]
    L["grow()"]
    M["total_blocks()"]
    N["used_blocks()"]
end
A["Slab<const BLK_SIZE: usize>"]
B["free_block_list: FreeBlockList<BLK_SIZE>"]
C["total_blocks: usize"]
D["len: usize"]
E["head: Option<&'static mut FreeBlock>"]
F["FreeBlock"]
G["next: Option<&'static mut FreeBlock>"]
H["addr() -> usize"]

A --> B
A --> C
A --> I
A --> J
A --> K
A --> L
A --> M
A --> N
B --> D
B --> E
E --> F
F --> G
F --> H

Sources: src/slab.rs(L4 - L7)  src/slab.rs(L65 - L68)  src/slab.rs(L112 - L114) 

FreeBlockList Management

The FreeBlockList implements a stack-based linked list for managing free memory blocks. Each free block contains a pointer to the next free block, creating an intrusive linked list that uses the free memory itself for storage.

FreeBlockList Structure and Operations

flowchart TD
subgraph subGraph1["Linked List Chain"]
    C["FreeBlock@addr1"]
    D["FreeBlock@addr2"]
    E["FreeBlock@addr3"]
    F["next: None"]
end
subgraph subGraph0["FreeBlockList State"]
    A["head: Some(block_ptr)"]
    B["len: 3"]
end
subgraph Operations["Operations"]
    G["push(free_block)"]
    H["Sets block.next = current head"]
    I["Updates head = new block"]
    J["Increments len"]
    K["pop()"]
    L["Takes current head"]
    M["Updates head = head.next"]
    N["Decrements len"]
    O["Returns old head"]
end

A --> B
A --> C
C --> D
D --> E
E --> F
G --> H
H --> I
I --> J
K --> L
L --> M
M --> N
N --> O

The FreeBlockList provides O(1) operations for both insertion and removal:

OperationMethodComplexityDescription
Insertpush()O(1)Adds block to front of list
Removepop()O(1)Removes block from front of list
Check Lengthlen()O(1)Returns current free block count
Check Emptyis_empty()O(1)Tests if any blocks available

Sources: src/slab.rs(L92 - L98)  src/slab.rs(L100 - L104)  src/slab.rs(L88 - L90) 

Block Allocation Process

The allocation process follows a straightforward strategy: attempt to pop a free block from the list, and if none are available, grow the slab by requesting memory from the buddy allocator.

Allocation Flow Diagram

flowchart TD
subgraph subGraph0["grow() Details"]
    J["Calculate num_of_blocks"]
    K["Create new FreeBlockList"]
    L["Transfer all blocks to main list"]
    M["Update total_blocks"]
end
A["allocate(layout, buddy)"]
B["free_block_list.pop()"]
C["Return block.addr()"]
D["Create Layout(SET_SIZE * BLK_SIZE, 4096)"]
E["buddy.alloc(layout)"]
F["grow(ptr, SET_SIZE * BLK_SIZE)"]
G["Return AllocError"]
H["free_block_list.pop().unwrap()"]
I["Return block.addr()"]

A --> B
B --> C
B --> D
D --> E
E --> F
E --> G
F --> H
F --> J
H --> I
J --> K
K --> L
L --> M

The allocation mechanism includes automatic growth when the free list is exhausted. The growth size is determined by SET_SIZE * BLK_SIZE with 4096-byte alignment, where SET_SIZE is imported from the parent module.

Sources: src/slab.rs(L35 - L55)  src/slab.rs(L26 - L33) 

Block Deallocation Process

Deallocation is simpler than allocation, requiring only the conversion of the memory address back to a FreeBlock and pushing it onto the free list.

Deallocation Mechanism

flowchart TD
subgraph subGraph0["push() Implementation"]
    E["block.next = current head"]
    F["head = Some(block)"]
    G["len += 1"]
end
A["deallocate(ptr: usize)"]
B["Cast ptr as *mut FreeBlock"]
C["Dereference to &mut FreeBlock"]
D["free_block_list.push(block)"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G

The deallocation process assumes the pointer was originally allocated by this slab and is properly aligned for the block size. No validation is performed on the input pointer.

Sources: src/slab.rs(L57 - L62) 

Dynamic Growth Mechanism

When a slab runs out of free blocks, it can dynamically grow by requesting additional memory from the buddy allocator. This growth is performed in chunks to amortize the allocation overhead.

Growth Process Details

flowchart TD
subgraph subGraph0["Memory Layout"]
    L["start_addr"]
    M["Block 0"]
    N["Block 1"]
    O["Block 2"]
    P["... Block n-1"]
end
A["grow(start_addr, slab_size)"]
B["Calculate num_of_blocks = slab_size / BLK_SIZE"]
C["total_blocks += num_of_blocks"]
D["Create temporary FreeBlockList::new()"]
E["Initialize blocks in reverse order"]
F["for i in (0..num_of_blocks).rev()"]
G["block_addr = start_addr + i * BLK_SIZE"]
H["temp_list.push(block_addr as FreeBlock)"]
I["Transfer all blocks to main list"]
J["while let Some(block) = temp_list.pop()"]
K["main_list.push(block)"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
L --> M
M --> N
N --> O
O --> P

The growth mechanism uses reverse iteration to maintain proper block ordering in the free list. Blocks are added to a temporary list first, then transferred to the main free list to ensure correct linkage.

Sources: src/slab.rs(L26 - L33)  src/slab.rs(L71 - L82) 

Memory Statistics and Tracking

The Slab provides methods to track memory usage statistics, essential for monitoring and debugging allocation patterns.

StatisticMethodCalculationPurpose
Total Blockstotal_blocks()Direct field accessMaximum capacity
Used Blocksused_blocks()total_blocks - free_block_list.len()Current utilization
Free BlocksN/A (internal)free_block_list.len()Available capacity

Statistics Calculation Flow

flowchart TD
A["total_blocks: usize"]
B["total_blocks()"]
C["free_block_list.len()"]
D["used_blocks() calculation"]
E["total_blocks - free_list.len()"]

A --> B
A --> D
C --> D
D --> E

These statistics enable the higher-level heap allocator to make informed decisions about memory management and provide usage information to applications.

Sources: src/slab.rs(L18 - L24)  src/slab.rs(L88 - L90) 

API Reference

Relevant source files

This page provides comprehensive documentation for all public APIs in the slab_allocator crate. It covers the Heap struct and its methods, including constructor functions, allocation operations, and memory statistics. For architectural details about how these APIs work internally, see Core Architecture. For practical usage examples and integration patterns, see Getting Started.

Core Types

Heap Struct

The Heap struct is the primary interface for memory allocation in this crate, implementing a hybrid allocation strategy with multiple slab allocators and a buddy system allocator.

pub struct Heap {
    slab_64_bytes: Slab<64>,
    slab_128_bytes: Slab<128>,
    slab_256_bytes: Slab<256>,
    slab_512_bytes: Slab<512>,
    slab_1024_bytes: Slab<1024>,
    slab_2048_bytes: Slab<2048>,
    slab_4096_bytes: Slab<4096>,
    buddy_allocator: buddy_system_allocator::Heap<32>,
}

API Structure

flowchart TD
subgraph subGraph2["Internal Routing"]
    M["layout_to_allocator()"]
    N["HeapAllocator::Slab64Bytes"]
    O["HeapAllocator::Slab128Bytes"]
    P["HeapAllocator::Slab256Bytes"]
    Q["HeapAllocator::Slab512Bytes"]
    R["HeapAllocator::Slab1024Bytes"]
    S["HeapAllocator::Slab2048Bytes"]
    T["HeapAllocator::Slab4096Bytes"]
    U["HeapAllocator::BuddyAllocator"]
end
subgraph subGraph1["Method Categories"]
    J["Constructor Methods"]
    K["Allocation Methods"]
    L["Statistics Methods"]
end
subgraph subGraph0["Public API"]
    A["Heap"]
    B["new()"]
    C["add_memory()"]
    D["allocate()"]
    E["deallocate()"]
    F["usable_size()"]
    G["total_bytes()"]
    H["used_bytes()"]
    I["available_bytes()"]
end

A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
A --> I
D --> M
E --> M
F --> M
J --> B
J --> C
K --> D
K --> E
K --> F
L --> G
L --> H
L --> I
M --> N
M --> O
M --> P
M --> Q
M --> R
M --> S
M --> T
M --> U

Sources: src/lib.rs(L40 - L49)  src/lib.rs(L27 - L36) 

Constructor Methods

new()

Creates a new heap instance with the specified memory region.

#![allow(unused)]
fn main() {
pub unsafe fn new(heap_start_addr: usize, heap_size: usize) -> Heap
}

Parameters:

  • heap_start_addr: usize - Starting address of the heap memory region (must be page-aligned)
  • heap_size: usize - Size of the heap in bytes (must be ≥ MIN_HEAP_SIZE and multiple of MIN_HEAP_SIZE)

Returns: Heap - A new heap instance

Safety Requirements:

  • The start address must be valid and accessible
  • The memory range <FileRef file-url="https://github.com/arceos-org/slab_allocator/blob/3c13499d/heap_start_addr, heap_start_addr + heap_size) must not be used elsewhere\n- heap_start_addr must be page-aligned (4096-byte boundary)\n- heap_size must be at least 0x8000 bytes (32KB)\n- heap_size must be a multiple of 0x8000 bytes\n\nSources#LNaN-LNaN" NaN file-path="heap_start_addr, heap_start_addr + heap_size)must not be used elsewhere\n-heap_start_addrmust be page-aligned (4096-byte boundary)\n-heap_sizemust be at least0x8000bytes (32KB)\n-heap_sizemust be a multiple of0x8000` bytes\n\nSources">Hii src/lib.rs(L25) 

add_memory()

Extends the heap with additional memory region.

#![allow(unused)]
fn main() {
pub unsafe fn add_memory(&mut self, heap_start_addr: usize, heap_size: usize)
}

Parameters:

  • heap_start_addr: usize - Starting address of the additional memory region (must be page-aligned)
  • heap_size: usize - Size of the additional memory in bytes (must be multiple of page size)

Safety Requirements:

  • The start address must be valid and accessible
  • The memory range must not overlap with existing allocations
  • heap_start_addr must be page-aligned (4096-byte boundary)
  • heap_size must be a multiple of page size (4096 bytes)

Sources: src/lib.rs(L88 - L106) 

Allocation Methods

allocate()

Allocates memory according to the specified layout requirements.

#![allow(unused)]
fn main() {
pub fn allocate(&mut self, layout: Layout) -> Result<usize, AllocError>
}

Parameters:

  • layout: Layout - Memory layout specification including size and alignment requirements

Returns:

  • Ok(usize) - Address of the allocated memory block
  • Err(AllocError) - Allocation failed due to insufficient memory

Performance:

  • O(1) for allocations ≤ 4096 bytes (slab allocators)
  • O(n) for allocations > 4096 bytes (buddy allocator)

Allocation Routing Logic

flowchart TD
A["allocate(layout)"]
B["layout_to_allocator(layout)"]
C["Size > 4096?"]
D["HeapAllocator::BuddyAllocator"]
E["Size ≤ 64 && Align ≤ 64?"]
F["HeapAllocator::Slab64Bytes"]
G["Size ≤ 128 && Align ≤ 128?"]
H["HeapAllocator::Slab128Bytes"]
I["Size ≤ 256 && Align ≤ 256?"]
J["HeapAllocator::Slab256Bytes"]
K["Size ≤ 512 && Align ≤ 512?"]
L["HeapAllocator::Slab512Bytes"]
M["Size ≤ 1024 && Align ≤ 1024?"]
N["HeapAllocator::Slab1024Bytes"]
O["Size ≤ 2048 && Align ≤ 2048?"]
P["HeapAllocator::Slab2048Bytes"]
Q["HeapAllocator::Slab4096Bytes"]
R["slab_64_bytes.allocate()"]
S["slab_128_bytes.allocate()"]
T["slab_256_bytes.allocate()"]
U["slab_512_bytes.allocate()"]
V["slab_1024_bytes.allocate()"]
W["slab_2048_bytes.allocate()"]
X["slab_4096_bytes.allocate()"]
Y["buddy_allocator.alloc()"]

A --> B
B --> C
C --> D
C --> E
D --> Y
E --> F
E --> G
F --> R
G --> H
G --> I
H --> S
I --> J
I --> K
J --> T
K --> L
K --> M
L --> U
M --> N
M --> O
N --> V
O --> P
O --> Q
P --> W
Q --> X

Sources: src/lib.rs(L131 - L164)  src/lib.rs(L207 - L226) 

deallocate()

Frees a previously allocated memory block.

#![allow(unused)]
fn main() {
pub unsafe fn deallocate(&mut self, ptr: usize, layout: Layout)
}

Parameters:

  • ptr: usize - Address of the memory block to free (must be returned from previous allocate() call)
  • layout: Layout - Original layout used for allocation (must match exactly)

Safety Requirements:

  • ptr must be a valid pointer returned by a previous call to allocate() with identical layout
  • The memory block must not have been previously freed
  • The layout must match the original allocation layout exactly

Performance:

  • O(1) for blocks ≤ 4096 bytes (slab allocators)
  • O(n) for blocks > 4096 bytes (buddy allocator)

Sources: src/lib.rs(L166 - L190) 

usable_size()

Returns the bounds on the usable size of an allocation with the given layout.

#![allow(unused)]
fn main() {
pub fn usable_size(&self, layout: Layout) -> (usize, usize)
}

Parameters:

  • layout: Layout - Memory layout specification

Returns: (usize, usize) - Tuple of (minimum_usable_size, maximum_usable_size)

Behavior:

  • For slab allocations: returns (layout.size(), slab_block_size)
  • For buddy allocations: returns (layout.size(), layout.size())
Slab SizeUsable Size Range
64 bytes(requested_size, 64)
128 bytes(requested_size, 128)
256 bytes(requested_size, 256)
512 bytes(requested_size, 512)
1024 bytes(requested_size, 1024)
2048 bytes(requested_size, 2048)
4096 bytes(requested_size, 4096)
Buddy allocator(requested_size, requested_size)

Sources: src/lib.rs(L192 - L205) 

Statistics Methods

total_bytes()

Returns the total memory size managed by the heap.

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

Returns: usize - Total memory in bytes across all slab allocators and buddy allocator

Calculation: Sum of (total_blocks × block_size) for each slab plus buddy allocator total bytes.

Sources: src/lib.rs(L228 - L238) 

used_bytes()

Returns the currently allocated memory size.

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

Returns: usize - Currently allocated memory in bytes

Calculation: Sum of (used_blocks × block_size) for each slab plus buddy allocator allocated bytes.

Sources: src/lib.rs(L240 - L250) 

available_bytes()

Returns the available memory size for new allocations.

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

Returns: usize - Available memory in bytes

Calculation: total_bytes() - used_bytes()

Sources: src/lib.rs(L252 - L255) 

Constants and Configuration

Memory Size Constants

ConstantValueDescription
SET_SIZE64Number of blocks allocated when a slab needs to grow
MIN_HEAP_SIZE0x8000 (32KB)Minimum heap size fornew()constructor

Sources: src/lib.rs(L24 - L25) 

Slab Size Configuration

The allocator uses seven fixed-size slab allocators with the following block sizes:

Slab TypeBlock SizeUse Case
Slab<64>64 bytesSmall objects, metadata
Slab<128>128 bytesSmall structures
Slab<256>256 bytesMedium structures
Slab<512>512 bytesLarge structures
Slab<1024>1024 bytesVery large structures
Slab<2048>2048 bytesPage-like allocations
Slab<4096>4096 bytesPage allocations
Buddy AllocatorVariableAllocations > 4096 bytes

Sources: src/lib.rs(L41 - L48) 

Usage Patterns

Memory Statistics Flow

flowchart TD
A["Heap Instance"]
B["total_bytes()"]
C["used_bytes()"]
D["available_bytes()"]
E["Sum all slab total_blocks * block_size"]
F["Add buddy_allocator.stats_total_bytes()"]
G["Sum all slab used_blocks * block_size"]
H["Add buddy_allocator.stats_alloc_actual()"]
I["total_bytes - used_bytes"]
J["Final Total"]
K["Final Used"]
L["Final Available"]

A --> B
A --> C
A --> D
B --> E
B --> F
C --> G
C --> H
D --> I
E --> J
F --> J
G --> K
H --> K
I --> L

Sources: src/lib.rs(L228 - L255) 

Typical API Usage Sequence:

  1. Create heap with unsafe { Heap::new(start_addr, size) }
  2. Optionally extend with unsafe { heap.add_memory(addr, size) }
  3. Allocate memory with heap.allocate(layout)
  4. Use allocated memory
  5. Free memory with unsafe { heap.deallocate(ptr, layout) }
  6. Monitor statistics with heap.total_bytes(), heap.used_bytes(), heap.available_bytes()

Sources: src/lib.rs(L52 - L255) 

Testing and Validation

Relevant source files

This page provides a comprehensive guide to the test suite for the slab_allocator crate. It covers the test infrastructure, validation scenarios, test execution procedures, and analysis of different allocation patterns used to ensure the reliability and correctness of the hybrid memory allocation system.

For information about the core allocation architecture being tested, see Core Architecture. For API usage patterns demonstrated in tests, see API Reference.

Test Infrastructure

The test suite is built around two primary test heap configurations that provide controlled environments for validation.

Test Heap Configurations

The testing infrastructure uses statically allocated, page-aligned memory regions to simulate real-world heap conditions:

ConfigurationSizePurpose
TestHeap64KB (16 × 4096 bytes)Small-scale allocation testing
TestBigHeap640KB (64KB × 10)Large allocation and growth testing
flowchart TD
subgraph subGraph1["Test Infrastructure Architecture"]
    A["TestHeap"]
    B["64KB aligned memory"]
    C["TestBigHeap"]
    D["640KB aligned memory"]
    E["new_heap()"]
    F["new_big_heap()"]
    G["Heap::new()"]
    H["Test Execution"]
    subgraph subGraph0["Memory Layout"]
        I["#[repr(align(4096))]"]
        J["heap_space: [u8; SIZE]"]
    end
end

A --> B
A --> I
B --> E
B --> J
C --> D
C --> I
D --> F
D --> J
E --> G
F --> G
G --> H

The test structures use #[repr(align(4096))] to ensure proper page alignment, which is critical for the underlying buddy allocator's operation.

Sources: src/tests.rs(L8 - L16)  src/tests.rs(L18 - L37) 

Test Heap Factory Functions

Two factory functions provide standardized heap initialization:

  • new_heap() - Creates a 64KB heap instance for basic allocation testing
  • new_big_heap() - Creates a 640KB heap instance for large allocation scenarios

These functions handle the unsafe memory region setup and return initialized Heap instances ready for testing.

Sources: src/tests.rs(L18 - L37) 

Test Categories and Validation Scenarios

The test suite validates the allocator through several distinct categories of allocation patterns and edge cases.

Memory Exhaustion Testing

flowchart TD
subgraph subGraph0["OOM Test Flow"]
    A["oom() test"]
    B["Request HEAP_SIZE + 1 bytes"]
    C["Layout::from_size_align()"]
    D["heap.allocate()"]
    E["Verify Error Result"]
    F["Heap: 64KB"]
    G["Request: 64KB + 1"]
    H["Expected: Allocation Failure"]
end

A --> B
B --> C
C --> D
D --> E
F --> G
G --> H

The oom() test validates out-of-memory handling by requesting more memory than the total heap capacity. This ensures the allocator gracefully handles resource exhaustion rather than causing undefined behavior.

Sources: src/tests.rs(L39 - L45) 

Basic Allocation Patterns

The test suite validates fundamental allocation operations through progressively complex scenarios:

Test FunctionAllocation SizeValidation Focus
allocate_double_usize()2 ×usizeBasic allocation success
allocate_and_free_double_usize()2 ×usizeAllocation + deallocation cycle
reallocate_double_usize()2 ×usizeMemory reuse verification
flowchart TD
subgraph subGraph0["Basic Allocation Test Flow"]
    A["Layout Creation"]
    B["size_of::() * 2"]
    C["align_of::()"]
    D["Layout::from_size_align()"]
    E["heap.allocate()"]
    F["Success Verification"]
    G["Memory Write Test"]
    H["0xdeafdeadbeafbabe pattern"]
    I["heap.deallocate()"]
    J["Memory Reuse Check"]
end

A --> B
A --> C
B --> D
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J

Sources: src/tests.rs(L47 - L87) 

Multi-Size Allocation Testing

The allocate_multiple_sizes() test validates the allocator's ability to handle concurrent allocations of different sizes and alignments:

  • Size Variations: 2×, 3×, 10× base usize
  • Alignment Variations: Standard alignment and 8× alignment
  • Deallocation Patterns: Non-sequential deallocation order

This test ensures the slab allocator correctly routes different sizes to appropriate slabs and handles fragmentation scenarios.

Sources: src/tests.rs(L89 - L117) 

Large Block Allocation Testing

Two specialized tests validate large allocation handling that exceeds the slab threshold:

flowchart TD
subgraph subGraph0["Large Block Test Strategy"]
    A["allocate_one_4096_block()"]
    B["Single 4KB allocation"]
    C["allocate_multiple_4096_blocks()"]
    D["Multiple 4KB+ allocations"]
    E["512 * usize allocation"]
    F["Mixed size allocations"]
    G["Complex deallocation patterns"]
    H["Buddy allocator routing"]
    I["Memory pattern verification"]
    J["0xdeafdeadbeafbabe write test"]
end

A --> B
B --> E
C --> D
D --> F
D --> G
E --> H
F --> H
G --> H
H --> I
I --> J

These tests specifically target the boundary between slab allocation (≤4096 bytes) and buddy allocation (>4096 bytes), ensuring proper routing and memory management across the threshold.

Sources: src/tests.rs(L119 - L163) 

Test Execution and Validation Patterns

Layout-Based Testing

All tests use Rust's Layout type to specify allocation requirements, ensuring compatibility with the standard allocator interface:

// Example pattern from tests
let layout = Layout::from_size_align(size, alignment).unwrap();
let addr = heap.allocate(layout.clone());

This approach validates that the allocator correctly handles:

  • Size requirements
  • Alignment constraints
  • Error propagation
  • Memory safety

Memory Pattern Verification

Critical tests write specific bit patterns to allocated memory to verify:

  • Successful memory access
  • Proper alignment
  • Data integrity across allocation/deallocation cycles

The pattern 0xdeafdeadbeafbabe is used as a recognizable marker for debugging and validation.

Sources: src/tests.rs(L64)  src/tests.rs(L161) 

Allocation Lifecycle Testing

flowchart TD
subgraph subGraph0["Complete Allocation Lifecycle"]
    A["Allocate"]
    B["Write Pattern"]
    C["Verify Access"]
    D["Deallocate"]
    E["Re-allocate"]
    F["Verify Reuse"]
    G["Layout Creation"]
    H["Error Handling"]
    I["Memory Safety"]
    J["Resource Management"]
end

A --> B
B --> C
C --> D
D --> E
E --> F
G --> A
H --> A
I --> C
J --> D

Tests validate the complete allocation lifecycle to ensure:

  • Proper initialization
  • Safe memory access
  • Correct deallocation
  • Memory reuse efficiency
  • No memory leaks

Sources: src/tests.rs(L71 - L87) 

Running the Test Suite

The test suite runs using Rust's standard testing framework:

cargo test

Target Platform Testing

The CI/CD pipeline executes tests across multiple target platforms:

  • x86_64-unknown-linux-gnu (with full test execution)
  • x86_64-unknown-none (build verification)
  • riscv64gc-unknown-none-elf (build verification)
  • aarch64-unknown-none-softfloat (build verification)

This ensures the allocator works correctly across different embedded and no_std environments.

Sources: Based on repository CI/CD configuration referenced in overview diagrams

Test Coverage Analysis

The test suite provides comprehensive coverage of the allocator's functionality:

ComponentTest Coverage
Heap initialization✓ Factory functions
Small allocations✓ Slab routing tests
Large allocations✓ Buddy allocator tests
Memory exhaustion✓ OOM handling
Fragmentation✓ Multi-size patterns
Alignment✓ Various alignment requirements
Memory reuse✓ Allocation/deallocation cycles

The tests validate both the high-level Heap interface and the underlying allocation strategy switching between slab and buddy allocation based on size thresholds.

Sources: src/tests.rs(L1 - L164) 

Development Workflow

Relevant source files

This document provides a comprehensive guide for contributors to the slab_allocator project, covering development environment setup, build system usage, CI/CD pipeline configuration, and project maintenance practices. It focuses on the technical infrastructure that supports development and ensures code quality across multiple target platforms.

For information about running and understanding the test suite, see Testing and Validation. For API usage examples and integration patterns, see Getting Started.

Development Environment Setup

The slab_allocator project is designed for no_std environments and requires specific toolchain configurations to support cross-compilation to embedded targets. The development environment must support Rust nightly toolchain with multiple target architectures.

Required Toolchain Components

The project requires the following Rust toolchain components as defined in the CI configuration:

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and code analysis
rustfmtCode formatting
nightlytoolchainAccess to unstable features required byno_std

Supported Target Architectures

The project supports four primary target architectures:

TargetEnvironmentTesting
x86_64-unknown-linux-gnuLinux developmentFull test suite
x86_64-unknown-noneBare metal x86_64Build only
riscv64gc-unknown-none-elfRISC-V embeddedBuild only
aarch64-unknown-none-softfloatARM64 embeddedBuild only

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L17 - L19) 

Local Development Workflow

flowchart TD
A["Developer Environment"]
B["Local Changes"]
C["cargo fmt --all -- --check"]
D["cargo clippy --all-features"]
E["cargo build --all-features"]
F["cargo test -- --nocapture"]
G["Format Check"]
H["Clippy Analysis"]
I["Build Success"]
J["Test Results"]
K["Ready for Commit"]
L["Fix Formatting"]
M["Fix Clippy Issues"]
N["Fix Build Errors"]
O["Fix Test Failures"]
P["git commit / push"]
Q["CI/CD Pipeline Triggered"]

A --> B
B --> C
B --> D
B --> E
B --> F
C --> G
D --> H
E --> I
F --> J
G --> K
G --> L
H --> K
H --> M
I --> K
I --> N
J --> K
J --> O
K --> P
L --> B
M --> B
N --> B
O --> B
P --> Q

The local development process mirrors the CI pipeline checks to catch issues early. Developers should run the same commands locally that will be executed in the CI environment.

Sources: .github/workflows/ci.yml(L23 - L30) 

CI/CD Pipeline Architecture

Pipeline Overview

flowchart TD
A["GitHub Events"]
B["push/pull_request"]
C["ci job"]
D["doc job"]
E["Matrix Strategy"]
F["nightly toolchain"]
G["4 target platforms"]
H["Format Check"]
I["Clippy Analysis"]
J["Multi-target Build"]
K["Unit Tests"]
L["Documentation Build"]
M["GitHub Pages Deploy"]

A --> B
B --> C
B --> D
C --> E
D --> L
D --> M
E --> F
E --> G
F --> H
F --> I
F --> J
F --> K

The CI/CD system uses GitHub Actions with two parallel job streams: code validation (ci) and documentation generation (doc).

Sources: .github/workflows/ci.yml(L1 - L6)  .github/workflows/ci.yml(L32 - L33) 

CI Job Matrix Configuration

flowchart TD
A["ci job"]
B["Matrix Strategy"]
C["fail-fast: false"]
D["rust-toolchain: [nightly]"]
E["targets array"]
F["x86_64-unknown-linux-gnu"]
G["x86_64-unknown-none"]
H["riscv64gc-unknown-none-elf"]
I["aarch64-unknown-none-softfloat"]
J["Full Testing"]
K["Build Only"]
L["cargo test"]
M["cargo build"]

A --> B
B --> C
B --> D
B --> E
E --> F
E --> G
E --> H
E --> I
F --> J
G --> K
H --> K
I --> K
J --> L
K --> M

The matrix strategy ensures the codebase builds successfully across all supported embedded architectures while running comprehensive tests only on the Linux target where a full runtime environment is available.

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L28 - L30) 

Code Quality Pipeline

Quality Check Sequence

The CI pipeline enforces code quality through a structured sequence of checks:

StepCommandPurposeFailure Impact
Formatcargo fmt --all -- --checkConsistent code styleCI failure
Lintcargo clippy --target $TARGET --all-featuresCode analysisCI failure
Buildcargo build --target $TARGET --all-featuresCompilation validationCI failure
Testcargo test --target x86_64-unknown-linux-gnuFunctional validationCI failure

Clippy Configuration

The project uses clippy with a specific configuration to suppress certain warnings:

cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default

The -A clippy::new_without_default flag allows new() methods without corresponding default() implementations, which is appropriate for allocator constructors that require explicit memory region parameters.

Sources: .github/workflows/ci.yml(L22 - L30)  .github/workflows/ci.yml(L25) 

Documentation System

Documentation Build Process

flowchart TD
A["doc job"]
B["Checkout Repository"]
C["Setup Rust Nightly"]
D["Build Documentation"]
E["cargo doc --no-deps --all-features"]
F["RUSTDOCFLAGS Validation"]
G["Generate index.html"]
H["Branch Check"]
I["Deploy to gh-pages"]
J["Skip Deployment"]
K["Broken Links Check"]
L["Missing Docs Check"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
F --> K
F --> L
G --> H
H --> I
H --> J

Documentation Quality Enforcement

The documentation build enforces strict quality standards through RUSTDOCFLAGS:

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

This configuration treats broken internal documentation links and missing documentation as compilation errors, ensuring comprehensive and accurate documentation.

GitHub Pages Deployment

Documentation deployment follows a conditional strategy:

  • Default branch pushes: Automatic deployment to GitHub Pages
  • Pull requests and other branches: Documentation builds but does not deploy
  • Deployment target: gh-pages branch with single-commit history

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L55) 

Project Configuration

Dependency Management

The project maintains minimal dependencies to support no_std environments:

[dependencies]
buddy_system_allocator = { version = "0.10", default-features = false }

The default-features = false configuration ensures the buddy allocator dependency remains no_std compatible.

Git Configuration

The .gitignore configuration excludes development artifacts:

PatternPurpose
/targetRust build artifacts
/.vscodeIDE configuration
.DS_StoremacOS system files
Cargo.lockDependency lock file (library project)

Sources: Cargo.toml(L11 - L12)  .gitignore(L1 - L4) 

Development Best Practices

Pre-commit Validation

Contributors should run the complete CI validation suite locally before committing:

# Format check
cargo fmt --all -- --check

# Lint analysis
cargo clippy --all-features -- -A clippy::new_without_default

# Multi-target build validation
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features

# Full test suite
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

Release Management

Version management follows semantic versioning as indicated by the current version 0.3.1 in Cargo.toml. The project maintains backwards compatibility within major versions while supporting iterative improvements in minor releases.

Sources: Cargo.toml(L3)  .github/workflows/ci.yml(L22 - L30) 

Overview

Relevant source files

Purpose and Scope

The cpumask library provides a type-safe, memory-efficient implementation of CPU affinity masks for the ArceOS operating system. It represents sets of physical CPUs as compact bitmaps, where each bit position corresponds to a CPU number in the system. This library serves as the foundational component for CPU scheduling, process affinity management, and NUMA-aware operations within ArceOS.

The library is designed as a direct Rust equivalent to Linux's cpumask_t data structure, providing similar functionality while leveraging Rust's type system for compile-time size optimization and memory safety. For detailed API documentation, see API Reference. For implementation details and performance characteristics, see Architecture and Design.

Sources: src/lib.rs(L9 - L16)  README.md(L7 - L16)  Cargo.toml(L6 - L12) 

Core Architecture

The cpumask library is built around the CpuMask<const SIZE: usize> generic struct, which provides a strongly-typed wrapper over the bitmaps crate's Bitmap implementation. This architecture enables automatic storage optimization and type-safe CPU mask operations.

flowchart TD
A["CpuMask"]
B["Bitmap<{ SIZE }>"]
C["BitsImpl: Bits"]
D["BitOps trait methods"]
E["Automatic storage selection"]
F["bool (SIZE = 1)"]
G["u8 (SIZE ≤ 8)"]
H["u16 (SIZE ≤ 16)"]
I["u32 (SIZE ≤ 32)"]
J["u64 (SIZE ≤ 64)"]
K["u128 (SIZE ≤ 128)"]
L["[u128; N] (SIZE > 128)"]
M["CPU-specific operations"]
N["Iterator implementation"]
O["Bitwise operators"]

A --> B
A --> M
A --> N
A --> O
B --> C
C --> D
C --> E
E --> F
E --> G
E --> H
E --> I
E --> J
E --> K
E --> L

Sources: src/lib.rs(L17 - L23)  src/lib.rs(L7) 

Storage Optimization Strategy

The CpuMask type automatically selects the most memory-efficient storage representation based on the compile-time SIZE parameter. This optimization reduces memory overhead and improves cache performance for systems with different CPU counts.

CPU Count RangeStorage TypeMemory Usage
1bool1 bit
2-8u81 byte
9-16u162 bytes
17-32u324 bytes
33-64u648 bytes
65-128u12816 bytes
129-1024[u128; N]16×N bytes
flowchart TD
A["Compile-time SIZE"]
B["Storage Selection"]
C["bool storage"]
D["u8 storage"]
E["u16 storage"]
F["u32 storage"]
G["u64 storage"]
H["u128 storage"]
I["[u128; N] array"]
J["Optimal memory usage"]

A --> B
B --> C
B --> D
B --> E
B --> F
B --> G
B --> H
B --> I
C --> J
D --> J
E --> J
F --> J
G --> J
H --> J
I --> J

Sources: src/lib.rs(L12 - L16)  src/lib.rs(L328 - L410) 

API Surface Categories

The CpuMask API is organized into five main functional categories, providing comprehensive CPU mask manipulation capabilities:

flowchart TD
A["CpuMask API"]
B["Construction Methods"]
C["Query Operations"]
D["Modification Operations"]
E["Iteration Interface"]
F["Bitwise Operations"]
B1["new() - empty mask"]
B2["full() - all CPUs set"]
B3["mask(bits) - range mask"]
B4["one_shot(index) - single CPU"]
B5["from_raw_bits(value)"]
B6["from_value(data)"]
C1["get(index) - test bit"]
C2["len() - count set bits"]
C3["is_empty() / is_full()"]
C4["first_index() / last_index()"]
C5["next_index() / prev_index()"]
C6["*_false_index variants"]
D1["set(index, value)"]
D2["invert() - flip all bits"]
E1["IntoIterator trait"]
E2["Iter<'a, SIZE> struct"]
E3["DoubleEndedIterator"]
F1["BitAnd (&) - intersection"]
F2["BitOr (|) - union"]
F3["BitXor (^) - difference"]
F4["Not (!) - complement"]
F5["*Assign variants"]

A --> B
A --> C
A --> D
A --> E
A --> F
B --> B1
B --> B2
B --> B3
B --> B4
B --> B5
B --> B6
C --> C1
C --> C2
C --> C3
C --> C4
C --> C5
C --> C6
D --> D1
D --> D2
E --> E1
E --> E2
E --> E3
F --> F1
F --> F2
F --> F3
F --> F4
F --> F5

Sources: src/lib.rs(L68 - L235)  src/lib.rs(L237 - L251)  src/lib.rs(L253 - L326) 

Integration with ArceOS Ecosystem

The cpumask library serves as a foundational component within the ArceOS operating system, providing essential CPU affinity management capabilities for kernel subsystems:

flowchart TD
subgraph subGraph3["External Dependencies"]
    N["bitmaps v3.2.1"]
    O["no-std environment"]
end
subgraph subGraph2["Hardware Abstraction"]
    J["Multi-core systems"]
    K["NUMA topologies"]
    L["CPU hotplug events"]
    M["Embedded constraints"]
end
subgraph subGraph1["cpumask Library Core"]
    F["CPU set operations"]
    G["Efficient bit manipulation"]
    H["Iterator support"]
    I["Memory optimization"]
end
subgraph subGraph0["ArceOS Operating System"]
    A["Process Scheduler"]
    E["cpumask::CpuMask"]
    B["Thread Management"]
    C["CPU Affinity Control"]
    D["Load Balancing"]
end

A --> E
B --> E
C --> E
D --> E
E --> F
E --> G
E --> H
E --> I
F --> J
G --> K
H --> L
I --> M
N --> E
O --> E

The library's no_std compatibility makes it suitable for both kernel-space and embedded environments, while its dependency on the bitmaps crate provides battle-tested bitmap operations with optimal performance characteristics.

Sources: Cargo.toml(L12)  Cargo.toml(L14 - L15)  src/lib.rs(L1) 

Usage Example

The following example demonstrates typical CPU mask operations in an operating system context:

use cpumask::CpuMask;

// Create a CPU mask for a 32-CPU system
const SMP: usize = 32;
let mut available_cpus = CpuMask::<SMP>::new();

// Set CPUs 0, 2, 4 as available
available_cpus.set(0, true);
available_cpus.set(2, true);
available_cpus.set(4, true);

// Check system state
assert_eq!(available_cpus.len(), 3);
assert_eq!(available_cpus.first_index(), Some(0));

// Create process affinity mask (restrict to even CPUs)
let even_cpus = CpuMask::<SMP>::mask(16); // CPUs 0-15
let process_affinity = available_cpus & even_cpus;

// Iterate over assigned CPUs
for cpu_id in &process_affinity {
    println!("Process can run on CPU {}", cpu_id);
}

Sources: README.md(L20 - L57)  src/lib.rs(L417 - L427) 

API Reference

Relevant source files

This document provides complete reference documentation for the CpuMask<SIZE> struct and its associated types, methods, and trait implementations. The API enables efficient CPU affinity management through bitset operations optimized for different CPU count scenarios.

For architectural details and storage optimization strategies, see Architecture and Design. For practical usage examples and common patterns, see Usage Guide and Examples.

Core Types and Structure

The cpumask library centers around the CpuMask<const SIZE: usize> generic struct, which provides a compile-time sized bitset for representing CPU sets.

flowchart TD
subgraph subGraph3["Iterator Interface"]
    G1["IntoIterator trait"]
    G2["Iter<'a, SIZE>"]
    G3["Iterator + DoubleEndedIterator"]
end
subgraph subGraph2["Query Operations"]
    D1["get(index)"]
    D2["len()"]
    D3["is_empty()"]
    D4["is_full()"]
    D5["first_index()"]
    D6["last_index()"]
    D7["next_index(index)"]
    D8["prev_index(index)"]
    D9["*_false_index variants"]
end
subgraph subGraph1["Construction Methods"]
    C1["new()"]
    C2["full()"]
    C3["mask(bits)"]
    C4["from_value(data)"]
    C5["from_raw_bits(value)"]
    C6["one_shot(index)"]
end
subgraph subGraph0["Core API Structure"]
    A["CpuMask<SIZE>"]
    B["Bitmap<SIZE> value"]
    C["Construction Methods"]
    D["Query Operations"]
    E["Modification Methods"]
    F["Bitwise Operations"]
    G["Iterator Interface"]
end

A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
C --> C1
C --> C2
C --> C3
C --> C4
C --> C5
C --> C6
D --> D1
D --> D2
D --> D3
D --> D4
D --> D5
D --> D6
D --> D7
D --> D8
D --> D9
G --> G1
G --> G2
G --> G3

Sources: src/lib.rs(L18 - L23)  src/lib.rs(L68 - L235) 

Type Definitions and Constraints

TypeDefinitionConstraints
CpuMaskMain bitset structBitsImpl: Bits
Iter<'a, const SIZE: usize>Iterator over set bit indicesBitsImpl: Bits
Storage Type<BitsImpl as Bits>::StoreAutomatically selected based on SIZE

The storage type is automatically optimized based on the SIZE parameter:

SIZE RangeStorage TypeMemory Usage
1bool1 bit
2-8u88 bits
9-16u1616 bits
17-32u3232 bits
33-64u6464 bits
65-128u128128 bits
129-1024[u128; N]N × 128 bits

Sources: src/lib.rs(L12 - L16)  src/lib.rs(L18 - L23) 

Construction and Conversion Methods

The CpuMask type provides multiple ways to create and convert between different representations.

Basic Construction

MethodSignatureDescriptionTime Complexity
new()fn new() -> SelfCreates empty mask (all bits false)O(1)
full()fn full() -> SelfCreates full mask (all bits true)O(1)
mask(bits)fn mask(bits: usize) -> SelfCreates mask with firstbitsset to trueO(1)
one_shot(index)fn one_shot(index: usize) -> SelfCreates mask with single bit set atindexO(1)

Advanced Construction

MethodSignatureDescriptionPanics
from_value(data)fn from_value(data: Store) -> SelfCreates from backing store valueNever
from_raw_bits(value)fn from_raw_bits(value: usize) -> SelfCreates from raw usize bitsIfvalue >= 2^SIZE

Conversion Methods

MethodSignatureDescription
into_value(self)fn into_value(self) -> StoreConverts to backing store value
as_value(&self)fn as_value(&self) -> &StoreGets reference to backing store
as_bytes(&self)fn as_bytes(&self) -> &[u8]Gets byte slice representation

Sources: src/lib.rs(L72 - L146) 

Query and Inspection Operations

flowchart TD
subgraph subGraph2["Index Finding (False Bits)"]
    I["first_false_index()"]
    I1["Option<usize>"]
    J["last_false_index()"]
    J1["Option<usize>"]
    K["next_false_index(idx)"]
    K1["Option<usize>"]
    E["first_index()"]
    E1["Option<usize>"]
    F["last_index()"]
    F1["Option<usize>"]
    G["next_index(idx)"]
    G1["Option<usize>"]
    H["prev_index(idx)"]
    H1["Option<usize>"]
    A["get(index)"]
    A1["Returns bool"]
    B["len()"]
    B1["Count of set bits"]
    C["is_empty()"]
    C1["No bits set"]
    D["is_full()"]
    D1["All bits set"]
    subgraph subGraph1["Index Finding (True Bits)"]
        L["prev_false_index(idx)"]
        L1["Option<usize>"]
        subgraph subGraph0["Bit Query Operations"]
            I["first_false_index()"]
            I1["Option<usize>"]
            J["last_false_index()"]
            J1["Option<usize>"]
            K["next_false_index(idx)"]
            K1["Option<usize>"]
            E["first_index()"]
            E1["Option<usize>"]
            F["last_index()"]
            F1["Option<usize>"]
            G["next_index(idx)"]
            G1["Option<usize>"]
            H["prev_index(idx)"]
            H1["Option<usize>"]
            A["get(index)"]
            A1["Returns bool"]
            B["len()"]
            B1["Count of set bits"]
            C["is_empty()"]
            C1["No bits set"]
            D["is_full()"]
            D1["All bits set"]
        end
    end
end

A --> A1
B --> B1
C --> C1
D --> D1
E --> E1
F --> F1
G --> G1
H --> H1
I --> I1
J --> J1
K --> K1
L --> L1

Bit Testing Operations

MethodReturn TypeDescriptionTime Complexity
get(index)boolTests if bit at index is setO(1)
len()usizeCounts number of set bitsO(n) for arrays, O(1) for primitives
is_empty()boolTests if no bits are setO(log n)
is_full()boolTests if all bits are setO(log n)

Index Finding Operations

MethodReturn TypeDescriptionTime Complexity
first_index()OptionFinds first set bitO(log n)
last_index()OptionFinds last set bitO(log n)
next_index(index)OptionFinds next set bit after indexO(log n)
prev_index(index)OptionFinds previous set bit before indexO(log n)
first_false_index()OptionFinds first unset bitO(log n)
last_false_index()OptionFinds last unset bitO(log n)
next_false_index(index)OptionFinds next unset bit after indexO(log n)
prev_false_index(index)OptionFinds previous unset bit before indexO(log n)

Sources: src/lib.rs(L148 - L228) 

Modification and Iteration

Modification Operations

MethodSignatureDescriptionReturns
set(&mut self, index, value)fn set(&mut self, index: usize, value: bool) -> boolSets bit at indexPrevious value
invert(&mut self)fn invert(&mut self)Inverts all bits()

Iterator Interface

The CpuMask implements IntoIterator for &CpuMask<SIZE>, yielding indices of set bits.

flowchart TD
subgraph subGraph1["Iter Struct Fields"]
    J["head: Option<usize>"]
    K["tail: Option<usize>"]
    L["data: &'a CpuMask<SIZE>"]
end
subgraph subGraph0["Iterator Implementation"]
    A["&CpuMask<SIZE>"]
    B["IntoIterator"]
    C["Iter<'a, SIZE>"]
    D["Iterator"]
    E["DoubleEndedIterator"]
    F["next()"]
    G["Option<usize>"]
    H["next_back()"]
    I["Option<usize>"]
end

A --> B
B --> C
C --> D
C --> E
C --> J
C --> K
C --> L
D --> F
E --> H
F --> G
H --> I
Iterator TypeItem TypeDescription
Iter<'a, SIZE>usizeIterates over indices of set bits
Trait ImplementationMethodsDescription
Iteratornext()Forward iteration through set bit indices
DoubleEndedIteratornext_back()Reverse iteration through set bit indices

Sources: src/lib.rs(L172 - L180)  src/lib.rs(L230 - L234)  src/lib.rs(L237 - L251)  src/lib.rs(L412 - L518) 

Bitwise Operations and Trait Implementations

Binary Bitwise Operations

flowchart TD
subgraph subGraph0["Binary Operations"]
    E["&="]
    E1["BitAndAssign"]
    D["!CpuMask"]
    D1["Not - Complement"]
    A["CpuMask & CpuMask"]
    A1["BitAnd - Intersection"]
    C["CpuMask ^ CpuMask"]
    C1["BitXor - Symmetric Difference"]
    subgraph subGraph2["Assignment Operations"]
        F["|="]
        F1["BitOrAssign"]
        G["^="]
        G1["BitXorAssign"]
        B["CpuMask | CpuMask"]
        B1["BitOr - Union"]
        subgraph subGraph1["Unary Operations"]
            E["&="]
            E1["BitAndAssign"]
            D["!CpuMask"]
            D1["Not - Complement"]
            A["CpuMask & CpuMask"]
            A1["BitAnd - Intersection"]
        end
    end
end

A --> A1
B --> B1
C --> C1
D --> D1
E --> E1
F --> F1
G --> G1
OperatorTraitDescriptionResult
&BitAndIntersection of two masksSet bits present in both
|BitOrUnion of two masksSet bits present in either
^BitXorSymmetric differenceSet bits present in exactly one
!NotComplement of maskInverts all bits
&=BitAndAssignIn-place intersectionModifies left operand
|=BitOrAssignIn-place unionModifies left operand
^=BitXorAssignIn-place symmetric differenceModifies left operand

Standard Trait Implementations

TraitImplementationNotes
CloneDerivedBitwise copy
CopyDerivedTrivial copy semantics
DefaultDerivedCreates empty mask
EqDerivedBitwise equality
PartialEqDerivedBitwise equality
DebugCustomDisplays as "cpumask: [idx1, idx2, ...]"
HashCustomHashes underlying storage value
PartialOrdCustomCompares underlying storage value
OrdCustomCompares underlying storage value

Sources: src/lib.rs(L17)  src/lib.rs(L25 - L66)  src/lib.rs(L253 - L326) 

Array Conversion Implementations

For large CPU counts, the library provides From trait implementations for array conversions:

Array SizeCpuMask SizeConversion
[u128; 2]CpuMask<256>BidirectionalFrom
[u128; 3]CpuMask<384>BidirectionalFrom
[u128; 4]CpuMask<512>BidirectionalFrom
[u128; 5]CpuMask<640>BidirectionalFrom
[u128; 6]CpuMask<768>BidirectionalFrom
[u128; 7]CpuMask<896>BidirectionalFrom
[u128; 8]CpuMask<1024>BidirectionalFrom

These implementations enable direct conversion between CpuMask instances and their underlying array storage for sizes exceeding 128 bits.

Sources: src/lib.rs(L328 - L410) 

Construction and Conversion Methods

Relevant source files

This page documents all methods for creating CpuMask instances and converting between different representations. It covers the complete set of constructors, factory methods, and type conversion utilities provided by the CpuMask<SIZE> struct.

For information about querying and inspecting existing CpuMask instances, see Query and Inspection Operations. For modifying CpuMask state after construction, see Modification and Iteration.

Construction Methods

The CpuMask<SIZE> struct provides multiple construction patterns to accommodate different use cases and data sources.

Basic Construction

The most fundamental construction methods create CpuMask instances with predefined bit patterns:

MethodPurposeBit Pattern
new()Empty cpumaskAll bits set tofalse
full()Complete cpumaskAll bits set totrue
mask(bits: usize)Range-based maskFirstbitsindices set totrue

The new() method src/lib.rs(L74 - L76)  creates an empty cpumask by delegating to the Default trait implementation. The full() method src/lib.rs(L79 - L84)  creates a cpumask with all valid bits set using the underlying Bitmap::mask(SIZE) operation. The mask(bits: usize) method src/lib.rs(L89 - L94)  creates a mask where indices 0 through bits-1 are set to true.

Data-Driven Construction

For scenarios where CpuMask instances must be created from existing data, several factory methods accept different input formats:

The from_value(data) method src/lib.rs(L97 - L102)  accepts data of the backing store type, which varies based on the SIZE parameter due to storage optimization. The from_raw_bits(value: usize) method src/lib.rs(L106 - L119)  constructs a cpumask from a raw usize value, with assertion checking to ensure the value fits within the specified size. The one_shot(index: usize) method src/lib.rs(L123 - L128)  creates a cpumask with exactly one bit set at the specified index.

Construction Method Flow

flowchart TD
A["CpuMask Construction"]
B["new()"]
C["full()"]
D["mask(bits)"]
E["from_value(data)"]
F["from_raw_bits(value)"]
G["one_shot(index)"]
H["From trait implementations"]
I["Bitmap::new()"]
J["Bitmap::mask(SIZE)"]
K["Bitmap::mask(bits)"]
L["Bitmap::from_value(data)"]
M["Manual bit setting loop"]
N["Bitmap::new() + set(index, true)"]
O["Array to Bitmap conversion"]
P["CpuMask { value: bitmap }"]

A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
B --> I
C --> J
D --> K
E --> L
F --> M
G --> N
H --> O
I --> P
J --> P
K --> P
L --> P
M --> P
N --> P
O --> P

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

Array-Based Construction

For larger cpumasks that use array backing storage, the library provides From trait implementations for u128 arrays of various sizes. These implementations support cpumasks with sizes 256, 384, 512, 640, 768, 896, and 1024 bits:

flowchart TD
A["[u128; 2]"]
B["CpuMask<256>"]
C["[u128; 3]"]
D["CpuMask<384>"]
E["[u128; 4]"]
F["CpuMask<512>"]
G["[u128; 5]"]
H["CpuMask<640>"]
I["[u128; 6]"]
J["CpuMask<768>"]
K["[u128; 7]"]
L["CpuMask<896>"]
M["[u128; 8]"]
N["CpuMask<1024>"]
O["cpumask.into()"]

A --> B
B --> O
C --> D
D --> O
E --> F
F --> O
G --> H
H --> O
I --> J
J --> O
K --> L
L --> O
M --> N
N --> O

Sources: src/lib.rs(L328 - L368) 

Conversion Methods

The CpuMask struct provides multiple methods for extracting data in different formats, enabling interoperability with various system interfaces and storage requirements.

Value Extraction

The primary conversion methods expose the underlying storage in different forms:

The into_value(self) method src/lib.rs(L131 - L134)  consumes the cpumask and returns the backing store value. The as_value(&self) method src/lib.rs(L137 - L140)  provides a reference to the backing store without transferring ownership. The as_bytes(&self) method src/lib.rs(L143 - L146)  returns a byte slice representation of the cpumask data.

Bidirectional Array Conversion

The library implements bidirectional conversion between CpuMask instances and u128 arrays, enabling seamless integration with external APIs that expect array representations:

flowchart TD
A["CpuMask<256>"]
B["[u128; 2]"]
C["CpuMask<384>"]
D["[u128; 3]"]
E["CpuMask<512>"]
F["[u128; 4]"]
G["CpuMask<640>"]
H["[u128; 5]"]
I["CpuMask<768>"]
J["[u128; 6]"]
K["CpuMask<896>"]
L["[u128; 7]"]
M["CpuMask<1024>"]
N["[u128; 8]"]

A --> B
B --> A
C --> D
D --> C
E --> F
F --> E
G --> H
H --> G
I --> J
J --> I
K --> L
L --> K
M --> N
N --> M

Sources: src/lib.rs(L370 - L410) 

Storage Type Relationships

The conversion methods interact with the automatic storage optimization system, where the backing store type depends on the SIZE parameter:

flowchart TD
A["SIZE Parameter"]
B["Storage Selection"]
C["bool"]
D["u8"]
E["u16"]
F["u32"]
G["u64"]
H["u128"]
I["[u128; N]"]
J["from_value(bool)"]
K["from_value(u8)"]
L["from_value(u16)"]
M["from_value(u32)"]
N["from_value(u64)"]
O["from_value(u128)"]
P["from_value([u128; N])"]
Q["CpuMask<1>"]
R["CpuMask"]
S["CpuMask"]

A --> B
B --> C
B --> D
B --> E
B --> F
B --> G
B --> H
B --> I
C --> J
D --> K
E --> L
F --> M
G --> N
H --> O
I --> P
J --> Q
K --> R
L --> R
M --> R
N --> R
O --> R
P --> S

Sources: src/lib.rs(L97 - L102)  src/lib.rs(L12 - L16) 

Type Safety and Validation

Construction methods incorporate compile-time and runtime validation to ensure cpumask integrity:

  • The mask(bits) method includes a debug assertion src/lib.rs(L90)  that bits <= SIZE
  • The from_raw_bits(value) method validates src/lib.rs(L107)  that value >> SIZE == 0
  • The one_shot(index) method asserts src/lib.rs(L124)  that index < SIZE
  • Array-based From implementations use const generics to enforce size compatibility at compile time

These validation mechanisms prevent buffer overflows and ensure that cpumask operations remain within the defined size boundaries.

Sources: src/lib.rs(L90)  src/lib.rs(L107)  src/lib.rs(L124)  src/lib.rs(L328 - L410) 

Query and Inspection Operations

Relevant source files

This section documents the read-only operations available on CpuMask<SIZE> instances for examining their state, testing individual bits, and finding set or unset CPU positions. These operations do not modify the cpumask and provide various ways to inspect its contents.

For information about creating new cpumask instances, see Construction and Conversion Methods. For operations that modify cpumask state, see Modification and Iteration.

Overview of Query Operations

The CpuMask struct provides comprehensive query capabilities organized into several functional categories. These operations leverage the underlying bitmaps::Bitmap functionality while providing a CPU-specific interface.

flowchart TD
subgraph subGraph1["Return Types"]
    U["bool"]
    V["bool / usize"]
    W["Option"]
    X["Option"]
    Y["Store / &Store / &[u8]"]
end
subgraph subGraph0["CpuMask Query API"]
    A["get(index)"]
    B["Basic Bit Testing"]
    C["len()"]
    D["Size and State Queries"]
    E["is_empty()"]
    F["is_full()"]
    G["first_index()"]
    H["True Bit Location"]
    I["last_index()"]
    J["next_index(index)"]
    K["prev_index(index)"]
    L["first_false_index()"]
    M["False Bit Location"]
    N["last_false_index()"]
    O["next_false_index(index)"]
    P["prev_false_index(index)"]
    Q["as_value()"]
    R["Value Access"]
    S["into_value()"]
    T["as_bytes()"]
end

A --> B
B --> U
C --> D
D --> V
E --> D
F --> D
G --> H
H --> W
I --> H
J --> H
K --> H
L --> M
M --> X
N --> M
O --> M
P --> M
Q --> R
R --> Y
S --> R
T --> R

Sources: src/lib.rs(L148 - L234) 

Basic Bit Testing

get Method

The get method tests whether a specific CPU bit is set in the mask.

MethodSignatureDescriptionPerformance
getget(self, index: usize) -> boolReturnstrueif the bit atindexis setO(1)

The method performs bounds checking in debug builds and delegates to the underlying BitsImpl<SIZE>::Store::get implementation.

flowchart TD
A["CpuMask::get(index)"]
B["debug_assert!(index < SIZE)"]
C["self.into_value()"]
D["BitsImpl::Store::get(&value, index)"]
E["bool result"]

A --> B
B --> C
C --> D
D --> E

Sources: src/lib.rs(L167 - L171) 

Size and State Queries

These methods provide high-level information about the cpumask's state without requiring iteration through individual bits.

MethodSignatureDescriptionPerformance
lenlen(self) -> usizeCount oftruebits in the maskO(n) for most storage types
is_emptyis_empty(self) -> boolTests if no bits are setO(log n)
is_fullis_full(self) -> boolTests if all valid bits are setO(log n)

Implementation Details

The is_empty and is_full methods are implemented in terms of the index-finding operations:

flowchart TD
A["is_empty()"]
B["first_index().is_none()"]
C["is_full()"]
D["first_false_index().is_none()"]
E["Check if any bit is true"]
F["Check if any bit is false"]
G["len()"]
H["self.value.len()"]
I["Bitmap::len()"]

A --> B
B --> E
C --> D
D --> F
G --> H
H --> I

Sources: src/lib.rs(L149 - L164) 

Index Finding Operations

The cpumask provides efficient methods for locating set and unset bits, supporting both forward and backward traversal patterns commonly used in CPU scheduling algorithms.

True Bit Location Methods

MethodSignatureDescription
first_indexfirst_index(self) -> OptionFind first set bit
last_indexlast_index(self) -> OptionFind last set bit
next_indexnext_index(self, index: usize) -> OptionFind next set bit afterindex
prev_indexprev_index(self, index: usize) -> OptionFind previous set bit beforeindex

False Bit Location Methods

MethodSignatureDescription
first_false_indexfirst_false_index(self) -> OptionFind first unset bit
last_false_indexlast_false_index(self) -> OptionFind last unset bit
next_false_indexnext_false_index(self, index: usize) -> OptionFind next unset bit afterindex
prev_false_indexprev_false_index(self, index: usize) -> OptionFind previous unset bit beforeindex

Delegation to BitsImpl

All index-finding operations delegate to the corresponding methods on the underlying storage type, with false-bit methods using corrected implementations that respect the SIZE boundary:

flowchart TD
subgraph subGraph2["Storage Access"]
    Q["self.into_value()"]
    R["as Bits>::Store"]
end
subgraph subGraph1["False Bit Methods"]
    I["first_false_index()"]
    M["BitsImpl::corrected_first_false_index()"]
    J["last_false_index()"]
    N["BitsImpl::corrected_last_false_index()"]
    K["next_false_index(index)"]
    O["BitsImpl::corrected_next_false_index()"]
    L["prev_false_index(index)"]
    P["BitsImpl::Store::prev_false_index()"]
end
subgraph subGraph0["True Bit Methods"]
    A["first_index()"]
    E["BitsImpl::Store::first_index()"]
    B["last_index()"]
    F["BitsImpl::Store::last_index()"]
    C["next_index(index)"]
    G["BitsImpl::Store::next_index()"]
    D["prev_index(index)"]
    H["BitsImpl::Store::prev_index()"]
end

A --> E
B --> F
C --> G
D --> H
E --> Q
F --> Q
G --> Q
H --> Q
I --> M
J --> N
K --> O
L --> P
M --> Q
N --> Q
O --> Q
P --> Q
Q --> R

Sources: src/lib.rs(L183 - L228) 

Value Access Operations

These methods provide direct access to the underlying storage representation of the cpumask, enabling interoperability with external systems and efficient bulk operations.

MethodSignatureDescriptionUsage
into_valueinto_value(self) -> <BitsImpl as Bits>::StoreConsume and return backing storeMove semantics
as_valueas_value(&self) -> &<BitsImpl as Bits>::StoreReference to backing storeBorrowed access
as_bytesas_bytes(&self) -> &[u8]View as byte sliceSerialization

Storage Type Mapping

The actual storage type returned by into_value and referenced by as_value depends on the SIZE parameter:

flowchart TD
A["SIZE Parameter"]
B["Storage Selection"]
C["bool"]
D["u8"]
E["u16"]
F["u32"]
G["u64"]
H["u128"]
I["[u128; N]"]
J["into_value() -> bool"]
K["into_value() -> u8"]
L["into_value() -> u16"]
M["into_value() -> u32"]
N["into_value() -> u64"]
O["into_value() -> u128"]
P["into_value() -> [u128; N]"]

A --> B
B --> C
B --> D
B --> E
B --> F
B --> G
B --> H
B --> I
C --> J
D --> K
E --> L
F --> M
G --> N
H --> O
I --> P

Sources: src/lib.rs(L131 - L146) 

Performance Characteristics

The query operations exhibit different performance characteristics based on the underlying storage type and operation complexity:

Operation CategoryTime ComplexityNotes
get(index)O(1)Direct bit access
len()O(n)Requires bit counting
is_empty(),is_full()O(log n)Uses optimized bit scanning
Index findingO(log n) to O(n)Depends on bit density and hardware support
Value accessO(1)Direct storage access

The actual performance depends heavily on the target architecture's bit manipulation instructions and the density of set bits in the mask.

Sources: src/lib.rs(L148 - L234) 

Modification and Iteration

Relevant source files

This page documents the CpuMask methods and traits for modifying mask state after construction and for iterating over set CPU indices. For mask construction methods, see Construction and Conversion Methods. For read-only query operations, see Query and Inspection Operations. For bitwise operations and operator overloads, see Bitwise Operations and Traits.

Modification Operations

The CpuMask provides two primary methods for modifying the state of a mask after construction: individual bit manipulation via set and bulk inversion via invert.

Bit Manipulation

The set method allows modification of individual CPU bits within the mask. It takes an index and a boolean value, sets the bit at that position, and returns the previous value of that bit.

flowchart TD
A["CpuMask::set(index, value)"]
B["debug_assert!(index < SIZE)"]
C["self.value.set(index, value)"]
D["Returns previous bit value"]
E["Input: index: usize"]
F["Input: value: bool"]

A --> B
B --> C
C --> D
E --> A
F --> A

Sources: src/lib.rs(L177 - L180) 

The method includes a debug assertion to ensure the index is within the valid range (less than SIZE). The actual bit manipulation is delegated to the underlying Bitmap::set method from the bitmaps crate.

Bulk Inversion

The invert method provides a way to flip all bits in the mask simultaneously, converting true bits to false and vice versa.

flowchart TD
A["CpuMask::invert()"]
B["self.value.invert()"]
C["All bits flipped"]
D["Before: 101010"]
E["After: 010101"]

A --> B
B --> C
D --> A
E --> C

Sources: src/lib.rs(L232 - L234) 

This operation is useful for computing the complement of a CPU set, such as finding all CPUs that are not currently assigned to a particular task.

Iteration Interface

The CpuMask implements comprehensive iteration support through the IntoIterator trait and a custom Iter struct that provides both forward and backward iteration over set CPU indices.

Iterator Implementation Architecture

flowchart TD
A["&CpuMask"]
B["IntoIterator trait"]
C["Iter<'a, SIZE>"]
D["Iterator trait"]
E["DoubleEndedIterator trait"]
F["next() -> Option"]
G["next_back() -> Option"]
H["head: Option"]
I["tail: Option"]
J["data: &'a CpuMask"]

A --> B
B --> C
C --> D
C --> E
D --> F
E --> G
H --> C
I --> C
J --> C

Sources: src/lib.rs(L237 - L251)  src/lib.rs(L429 - L436)  src/lib.rs(L438 - L480)  src/lib.rs(L482 - L518) 

Iterator State Management

The Iter struct maintains iteration state using two optional indices:

FieldTypePurpose
headOptionTracks forward iteration position
tailOptionTracks backward iteration position
data&'a CpuMaskReference to the mask being iterated

The iterator is initialized with head set to None and tail set to Some(SIZE + 1), indicating that iteration can proceed in both directions from the extremes.

Forward Iteration Logic

flowchart TD
A["next() called"]
B["head == None?"]
C["result = data.first_index()"]
D["index >= SIZE?"]
E["result = None"]
F["result = data.next_index(index)"]
G["result found?"]
H["Check tail boundary"]
I["head = SIZE + 1"]
J["tail < index?"]
K["Stop iteration"]
L["head = index"]
M["Return None"]
N["Return Some(index)"]

A --> B
B --> C
B --> D
C --> G
D --> E
D --> F
E --> G
F --> G
G --> H
G --> I
H --> J
I --> M
J --> K
J --> L
K --> M
L --> N

Sources: src/lib.rs(L444 - L479) 

Backward Iteration Logic

The DoubleEndedIterator implementation provides next_back functionality with similar boundary checking but in reverse:

flowchart TD
A["next_back() called"]
B["tail == None?"]
C["result = None"]
D["index >= SIZE?"]
E["result = data.last_index()"]
F["result = data.prev_index(index)"]
G["result found?"]
H["Check head boundary"]
I["tail = None"]
J["head > index?"]
K["Stop iteration"]
L["tail = index"]
M["Return None"]
N["Return Some(index)"]

A --> B
B --> C
B --> D
C --> G
D --> E
D --> F
E --> G
F --> G
G --> H
G --> I
H --> J
I --> M
J --> K
J --> L
K --> M
L --> N

Sources: src/lib.rs(L486 - L517) 

Usage Patterns

Modification Examples

The modification operations enable dynamic CPU mask management:

OperationPurposeMethod Call
Enable CPUAdd CPU to active setmask.set(cpu_id, true)
Disable CPURemove CPU from active setmask.set(cpu_id, false)
Toggle all CPUsInvert entire maskmask.invert()

Iteration Examples

The iterator yields usize indices representing CPU numbers that are set to true in the mask:

// Iterate through all active CPUs
for cpu_id in &mask {
    // cpu_id is usize, represents an active CPU
}

// Collect to vector for further processing
let active_cpus: Vec<usize> = mask.into_iter().collect();

// Reverse iteration for priority-based processing
for cpu_id in mask.into_iter().rev() {
    // Process from highest to lowest CPU index
}

The implementation ensures that only indices with true bits are yielded, making it efficient for sparse CPU masks where only a few CPUs are active.

Sources: src/lib.rs(L419 - L427)  src/lib.rs(L31 - L32) 

Bitwise Operations and Traits

Relevant source files

This page documents the operator overloads, trait implementations, and set operations available on CpuMask instances. These operations enable intuitive mathematical set operations, comparisons, and conversions between CPU mask representations.

For information about basic construction and conversion methods, see Construction and Conversion Methods. For details about query operations and bit manipulation, see Query and Inspection Operations.

Bitwise Set Operations

The CpuMask type implements standard bitwise operators that correspond to mathematical set operations on CPU collections.

Binary Operators

The CpuMask struct implements four core binary bitwise operations:

OperatorTraitSet OperationDescription
&BitAndIntersectionCPUs present in both masks
|BitOrUnionCPUs present in either mask
^BitXorSymmetric DifferenceCPUs present in exactly one mask
!NotComplementInvert all CPU bits

Each operator returns a new CpuMask instance without modifying the operands.

flowchart TD
subgraph subGraph1["Implementation Details"]
    J["self.value.bitand(rhs.value)"]
    K["self.value.bitor(rhs.value)"]
    L["self.value.bitxor(rhs.value)"]
    M["self.value.not()"]
end
subgraph subGraph0["Bitwise Operations"]
    A["CpuMask"]
    B["& (BitAnd)"]
    C["| (BitOr)"]
    D["^ (BitXor)"]
    E["! (Not)"]
    F["Intersection"]
    G["Union"]
    H["Symmetric Difference"]
    I["Complement"]
end

A --> B
A --> C
A --> D
A --> E
B --> F
C --> G
D --> H
E --> I
F --> J
G --> K
H --> L
I --> M

Sources: src/lib.rs(L253 - L299) 

Assignment Operators

The CpuMask type also implements in-place assignment variants for efficiency:

OperatorTraitDescription
&=BitAndAssignIn-place intersection
|=BitOrAssignIn-place union
^=BitXorAssignIn-place symmetric difference

These operators modify the left operand directly, avoiding temporary allocations.

flowchart TD
subgraph subGraph0["Assignment Operations"]
    A["cpumask1 &= cpumask2"]
    B["BitAndAssign::bitand_assign"]
    C["cpumask1 |= cpumask2"]
    D["BitOrAssign::bitor_assign"]
    E["cpumask1 ^= cpumask2"]
    F["BitXorAssign::bitxor_assign"]
    G["self.value.bitand_assign(rhs.value)"]
    H["self.value.bitor_assign(rhs.value)"]
    I["self.value.bitxor_assign(rhs.value)"]
end

A --> B
B --> G
C --> D
D --> H
E --> F
F --> I

Sources: src/lib.rs(L301 - L326) 

Comparison and Ordering Traits

The CpuMask type implements a complete hierarchy of comparison traits, enabling sorting and ordering operations on CPU mask collections.

Trait Implementation Hierarchy

flowchart TD
subgraph subGraph1["Implementation Details"]
    F["self.value.as_value().partial_cmp(other.value.as_value())"]
    G["self.value.as_value().cmp(other.value.as_value())"]
end
subgraph subGraph0["Comparison Traits"]
    A["PartialEq"]
    B["Eq"]
    C["PartialOrd"]
    D["Ord"]
    E["CpuMask implements"]
end

A --> B
B --> C
C --> D
C --> F
D --> G
E --> A
E --> B
E --> C
E --> D

The comparison operations delegate to the underlying storage type's comparison methods, ensuring consistent ordering across different storage implementations.

Sources: src/lib.rs(L17)  src/lib.rs(L48 - L66) 

Utility Traits

Debug Formatting

The Debug trait provides a human-readable representation showing the indices of set CPU bits:


Sources: src/lib.rs(L25 - L36) 

Hash Implementation

The Hash trait enables use of CpuMask instances as keys in hash-based collections:

flowchart TD
A["Hash::hash"]
B["self.value.as_value().hash(state)"]
C["Delegates to underlying storage hash"]

A --> B
B --> C

The hash implementation requires that the underlying storage type implement Hash, which is satisfied by all primitive integer types used for storage.

Sources: src/lib.rs(L38 - L46) 

Derived Traits

The CpuMask struct automatically derives several fundamental traits:

TraitPurpose
CloneCreates deep copies of CPU masks
CopyEnables pass-by-value for smaller masks
DefaultCreates empty CPU masks
EqEnables exact equality comparisons
PartialEqEnables equality comparisons

Sources: src/lib.rs(L17) 

Conversion Traits

The library provides specialized From trait implementations for converting between CpuMask instances and u128 arrays for larger mask sizes.

Array Conversion Support

flowchart TD
subgraph subGraph1["From CpuMask to Array"]
    I["[u128; 2]"]
    J["[u128; 3]"]
    K["[u128; 4]"]
    L["[u128; 8]"]
end
subgraph subGraph0["From Array to CpuMask"]
    A["[u128; 2]"]
    B["CpuMask<256>"]
    C["[u128; 3]"]
    D["CpuMask<384>"]
    E["[u128; 4]"]
    F["CpuMask<512>"]
    G["[u128; 8]"]
    H["CpuMask<1024>"]
end

A --> B
B --> I
C --> D
D --> J
E --> F
F --> K
G --> H
H --> L

These implementations enable seamless conversion between raw array representations and type-safe CPU mask instances for masks requiring more than 128 bits of storage.

Sources: src/lib.rs(L328 - L410) 

Iterator Trait Implementation

The IntoIterator trait enables CpuMask instances to be used directly in for loops and with iterator methods.

Iterator Implementation Structure

flowchart TD
subgraph subGraph1["Internal State"]
    H["head: Option"]
    I["tail: Option"]
    J["data: &'a CpuMask"]
end
subgraph subGraph0["Iterator Pattern"]
    A["&CpuMask"]
    B["IntoIterator::into_iter"]
    C["Iter<'a, SIZE>"]
    D["Iterator::next"]
    E["DoubleEndedIterator::next_back"]
    F["Returns Option"]
    G["Returns Option"]
end

A --> B
B --> C
C --> D
C --> E
C --> H
C --> I
C --> J
D --> F
E --> G

The iterator yields the indices of set bits as usize values, enabling direct access to CPU indices without needing to manually scan the mask.

Sources: src/lib.rs(L237 - L251)  src/lib.rs(L412 - L518) 

Architecture and Design

Relevant source files

This document covers the internal implementation details, storage optimization strategy, performance characteristics, and architectural decisions of the cpumask library. It explains how the CpuMask struct integrates with the bitmaps crate to provide efficient CPU affinity management. For API usage patterns and practical examples, see Usage Guide and Examples. For complete API documentation, see API Reference.

Core Architecture

The cpumask library is built around a single primary type CpuMask<const SIZE: usize> that provides a type-safe, compile-time sized bitmap for CPU affinity management. The architecture leverages Rust's const generics and the bitmaps crate's automatic storage optimization to provide efficient memory usage across different CPU count scenarios.

Primary Components

flowchart TD
subgraph subGraph2["Concrete Storage Types"]
    E["bool (SIZE=1)"]
    F["u8 (SIZE≤8)"]
    G["u16 (SIZE≤16)"]
    H["u32 (SIZE≤32)"]
    I["u64 (SIZE≤64)"]
    J["u128 (SIZE≤128)"]
    K["[u128; N] (SIZE>128)"]
end
subgraph subGraph1["Storage Abstraction"]
    B["Bitmap"]
    C["BitsImpl"]
    D["Bits trait"]
end
subgraph subGraph0["User API Layer"]
    A["CpuMask"]
end
L["IntoIterator"]
M["BitOps traits"]
N["Hash, PartialOrd, Ord"]

A --> B
A --> L
A --> M
A --> N
B --> C
C --> D
D --> E
D --> F
D --> G
D --> H
D --> I
D --> J
D --> K

Core Architecture Design

The CpuMask struct wraps a Bitmap<SIZE> from the bitmaps crate, which automatically selects the most efficient storage type based on the compile-time SIZE parameter. The BitsImpl<SIZE> type alias resolves to the appropriate concrete storage type, and the Bits trait provides uniform operations across all storage variants.

Sources: src/lib.rs(L18 - L23)  src/lib.rs(L7)  src/lib.rs(L98 - L102) 

Type System Integration

flowchart TD
subgraph subGraph2["Operational Traits"]
    H["BitAnd, BitOr, BitXor"]
    I["Not"]
    J["BitAndAssign, BitOrAssign, BitXorAssign"]
    K["IntoIterator"]
end
subgraph subGraph1["Trait Bounds"]
    C["Default"]
    D["Clone, Copy"]
    E["Eq, PartialEq"]
    F["Hash"]
    G["PartialOrd, Ord"]
end
subgraph subGraph0["Const Generic System"]
    A["const SIZE: usize"]
    B["BitsImpl: Bits"]
end

A --> B
B --> C
B --> D
B --> E
B --> F
B --> G
B --> H
B --> I
B --> J
B --> K

Type System Design

The library extensively uses Rust's trait system to provide consistent behavior across different storage types. All traits are conditionally implemented based on the capabilities of the underlying storage type, ensuring type safety while maintaining performance.

Sources: src/lib.rs(L17)  src/lib.rs(L25 - L66)  src/lib.rs(L253 - L326) 

Storage Optimization Strategy

The most significant architectural feature is the automatic storage optimization that occurs at compile time based on the SIZE parameter. This optimization is handled entirely by the bitmaps crate's BitsImpl type system.

Storage Selection Logic

flowchart TD
A["Compile-time SIZE parameter"]
B["SIZE == 1?"]
C["bool storage1 bit"]
D["SIZE <= 8?"]
E["u8 storage8 bits"]
F["SIZE <= 16?"]
G["u16 storage16 bits"]
H["SIZE <= 32?"]
I["u32 storage32 bits"]
J["SIZE <= 64?"]
K["u64 storage64 bits"]
L["SIZE <= 128?"]
M["u128 storage128 bits"]
N["[u128; N] storageN = (SIZE + 127) / 128"]
O["Memory: 1 byte"]
P["Memory: 1 byte"]
Q["Memory: 2 bytes"]
R["Memory: 4 bytes"]
S["Memory: 8 bytes"]
T["Memory: 16 bytes"]
U["Memory: 16 * N bytes"]

A --> B
B --> C
B --> D
C --> O
D --> E
D --> F
E --> P
F --> G
F --> H
G --> Q
H --> I
H --> J
I --> R
J --> K
J --> L
K --> S
L --> M
L --> N
M --> T
N --> U

Storage Selection Algorithm

The storage selection is determined by the BitsImpl<SIZE> type alias from the bitmaps crate. This provides optimal memory usage by selecting the smallest storage type that can accommodate the required number of bits, with special handling for the single-bit case using bool.

Sources: src/lib.rs(L12 - L16)  src/lib.rs(L20) 

Array Storage for Large Sizes

For CPU masks exceeding 128 bits, the library uses arrays of u128 values. The implementation provides explicit conversion traits for common large sizes used in high-core-count systems.

SizeStorage TypeConversion Traits
256[u128; 2]From<[u128; 2]>,Into<[u128; 2]>
384[u128; 3]From<[u128; 3]>,Into<[u128; 3]>
512[u128; 4]From<[u128; 4]>,Into<[u128; 4]>
640[u128; 5]From<[u128; 5]>,Into<[u128; 5]>
768[u128; 6]From<[u128; 6]>,Into<[u128; 6]>
896[u128; 7]From<[u128; 7]>,Into<[u128; 7]>
1024[u128; 8]From<[u128; 8]>,Into<[u128; 8]>

Sources: src/lib.rs(L328 - L410) 

Integration with Bitmaps Crate

The cpumask library is designed as a domain-specific wrapper around the bitmaps crate, leveraging its efficient bitmap implementation while providing CPU-focused semantics and additional functionality.

Delegation Pattern

flowchart TD
subgraph subGraph1["Bitmap Delegation"]
    I["Bitmap::new()"]
    J["Bitmap::mask(SIZE)"]
    K["Bitmap::mask(bits)"]
    L["Store::get()"]
    M["Bitmap::set()"]
    N["Bitmap::len()"]
    O["Store::first_index()"]
    P["Bitmap::invert()"]
end
subgraph subGraph0["CpuMask Methods"]
    A["new()"]
    B["full()"]
    C["mask(bits)"]
    D["get(index)"]
    E["set(index, value)"]
    F["len()"]
    G["first_index()"]
    H["invert()"]
end

A --> I
B --> J
C --> K
D --> L
E --> M
F --> N
G --> O
H --> P

Method Delegation Architecture

Most CpuMask methods directly delegate to the underlying Bitmap<SIZE> implementation, providing a thin wrapper that maintains the CPU-specific semantics while leveraging the optimized bitmap operations.

Sources: src/lib.rs(L74 - L84)  src/lib.rs(L167 - L234) 

Storage Access Methods

The library provides multiple ways to access the underlying storage, enabling efficient integration with system APIs that expect raw bit representations:

flowchart TD
A["CpuMask"]
B["into_value()"]
C["as_value()"]
D["as_bytes()"]
E["from_value(data)"]
F["as Bits>::Store"]
G["& as Bits>::Store"]
H["&[u8]"]
I["CpuMask"]
J["Direct storage access"]
K["Borrowed storage access"]
L["Byte array representation"]
M["Construction from storage"]

A --> B
A --> C
A --> D
A --> E
B --> F
C --> G
D --> H
E --> I
F --> J
G --> K
H --> L
I --> M

Storage Access Patterns

These methods enable zero-copy conversions between CpuMask and its underlying storage representation, facilitating efficient interaction with system calls and external APIs that work with raw bit data.

Sources: src/lib.rs(L131 - L146) 

Performance Characteristics

The architectural design prioritizes performance through several key strategies:

Operation Complexity

Operation CategoryTime ComplexitySpace ComplexityImplementation
ConstructionO(1)O(1)Direct storage initialization
Single bit access (get,set)O(1)O(1)Direct bit manipulation
Bit counting (len)O(log n)O(1)Hardware population count
Index finding (first_index,last_index)O(log n)O(1)Hardware bit scan
IterationO(k)O(1)Where k = number of set bits
Bitwise operationsO(1)O(1)Single instruction for small sizes

Memory Layout Optimization

flowchart TD
subgraph subGraph1["Performance Impact"]
    E["Cache-friendly access"]
    F["Minimal memory overhead"]
    G["SIMD-compatible alignment"]
    H["Efficient copying"]
end
subgraph subGraph0["Memory Layout Examples"]
    A["CpuMask<4>Storage: u8Memory: 1 byteAlignment: 1"]
    B["CpuMask<16>Storage: u16Memory: 2 bytesAlignment: 2"]
    C["CpuMask<64>Storage: u64Memory: 8 bytesAlignment: 8"]
    D["CpuMask<256>Storage: [u128; 2]Memory: 32 bytesAlignment: 16"]
end

A --> E
B --> F
C --> G
D --> H

Memory Efficiency Design

The automatic storage selection ensures that CPU masks consume the minimum possible memory while maintaining optimal alignment for the target architecture. This reduces cache pressure and enables efficient copying and comparison operations.

Sources: src/lib.rs(L12 - L16) 

Iterator Implementation

The iterator design provides efficient traversal of set bits without allocating additional memory, supporting both forward and backward iteration.

Iterator State Management

stateDiagram-v2
[*] --> Initial
Initial --> Forward : "next()"
Initial --> Backward : "next_back()"
Forward --> ForwardActive : "found index"
Forward --> Exhausted : "no more indices"
Backward --> BackwardActive : "found index"
Backward --> Exhausted : "no more indices"
ForwardActive --> Forward : "next()"
ForwardActive --> Collision : "head >= tail"
BackwardActive --> Backward : "next_back()"
BackwardActive --> Collision : "head >= tail"
Collision --> Exhausted
Exhausted --> [*]

Iterator State Machine

The Iter<'a, SIZE> struct maintains head and tail pointers to support bidirectional iteration while detecting when the iterators meet to avoid double-yielding indices.

Sources: src/lib.rs(L428 - L518) 

Iterator Performance

The iterator implementation leverages the underlying bitmap's efficient index-finding operations:

  • first_index(): Hardware-accelerated bit scanning
  • next_index(index): Continues from previous position
  • prev_index(index): Reverse bit scanning
  • Memory usage: Fixed 24 bytes (two Option<usize> + reference)

Sources: src/lib.rs(L444 - L479)  src/lib.rs(L486 - L517) 

API Design Patterns

The library follows consistent design patterns that enhance usability and performance:

Constructor Patterns

flowchart TD
subgraph subGraph1["Use Cases"]
    G["Empty initialization"]
    H["All CPUs available"]
    I["Range-based masks"]
    J["Single CPU selection"]
    K["Integer conversion"]
    L["Type-safe conversion"]
end
subgraph subGraph0["Construction Methods"]
    A["new()"]
    B["full()"]
    C["mask(bits)"]
    D["one_shot(index)"]
    E["from_raw_bits(value)"]
    F["from_value(data)"]
end

A --> G
B --> H
C --> I
D --> J
E --> K
F --> L

Constructor Design Philosophy

Each constructor serves a specific use case common in CPU affinity management, from empty masks for incremental building to full masks for restriction-based workflows.

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

Error Handling Strategy

The library uses a combination of compile-time guarantees and runtime assertions to ensure correctness:

  • Compile-time: SIZE parameter ensures storage adequacy
  • Debug assertions: Index bounds checking in debug builds
  • Runtime panics: Explicit validation for user-provided values

Sources: src/lib.rs(L90)  src/lib.rs(L107)  src/lib.rs(L124)  src/lib.rs(L169)  src/lib.rs(L178) 

This architectural design provides a balance between performance, safety, and usability, making the cpumask library suitable for high-performance operating system kernels while maintaining Rust's safety guarantees.

Usage Guide and Examples

Relevant source files

This page provides practical examples and common usage patterns for the cpumask library in operating system contexts. It demonstrates how to effectively use the CpuMask<SIZE> struct for CPU affinity management, process scheduling, and system resource allocation.

For detailed API documentation, see API Reference. For implementation details and performance characteristics, see Architecture and Design.

Basic Usage Patterns

Creating CPU Masks

The most common way to start working with CPU masks is through the various construction methods provided by CpuMask<SIZE>:

flowchart TD
A["CpuMask::new()"]
B["Empty mask - all false"]
C["CpuMask::full()"]
D["Full mask - all true"]
E["CpuMask::mask(bits)"]
F["Range mask - first bits true"]
G["CpuMask::one_shot(index)"]
H["Single CPU mask"]
I["CpuMask::from_raw_bits(value)"]
J["From usize value"]
K["CpuMask::from_value(data)"]
L["From backing store type"]
M["Basic CPU set operations"]

A --> B
B --> M
C --> D
D --> M
E --> F
F --> M
G --> H
H --> M
I --> J
J --> M
K --> L
L --> M

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

Basic Manipulation Operations

The fundamental operations for working with individual CPU bits follow a consistent pattern:

OperationMethodDescriptionReturn Value
Test bitget(index)Check if CPU is in maskbool
Set bitset(index, value)Add/remove CPU from maskPreviousboolvalue
Count bitslen()Number of CPUs in maskusize
Test emptyis_empty()Check if no CPUs setbool
Test fullis_full()Check if all CPUs setbool

Sources: src/lib.rs(L148 - L179) 

Finding CPUs in Masks

The library provides efficient methods for locating specific CPUs within masks:

flowchart TD
A["CpuMask"]
B["first_index()"]
C["last_index()"]
D["next_index(current)"]
E["prev_index(current)"]
F["Inverse operations"]
G["first_false_index()"]
H["last_false_index()"]
I["next_false_index(current)"]
J["prev_false_index(current)"]
K["Option<usize>"]

A --> B
A --> C
A --> D
A --> E
B --> K
C --> K
D --> K
E --> K
F --> G
F --> H
F --> I
F --> J
G --> K
H --> K
I --> K
J --> K

Sources: src/lib.rs(L182 - L228) 

Set Operations and Logic

Bitwise Operations

CPU masks support standard bitwise operations for combining and manipulating sets of CPUs:

flowchart TD
A["mask1: CpuMask<N>"]
E["mask1 & mask2"]
B["mask2: CpuMask<N>"]
F["Intersection - CPUs in both"]
G["mask1 | mask2"]
H["Union - CPUs in either"]
I["mask1 ^ mask2"]
J["Symmetric difference - CPUs in one but not both"]
K["!mask1"]
L["Complement - inverse of mask"]
M["Assignment variants"]
N["&=, |=, ^="]
O["In-place operations"]

A --> E
A --> G
A --> I
A --> K
B --> E
B --> G
B --> I
E --> F
G --> H
I --> J
K --> L
M --> N
N --> O

Sources: src/lib.rs(L253 - L326) 

Practical Set Operations Example

Consider a scenario where you need to manage CPU allocation for different process types:

  1. System processes - CPUs 0-3 (reserved for kernel)
  2. User processes - CPUs 4-15 (general workload)
  3. Real-time processes - CPUs 12-15 (high-performance subset)

This demonstrates how set operations enable sophisticated CPU management policies.

Sources: src/lib.rs(L253 - L287) 

Iteration and Traversal

Forward and Backward Iteration

The CpuMask implements both Iterator and DoubleEndedIterator, enabling flexible traversal patterns:


Sources: src/lib.rs(L237 - L518) 

Iteration Use Cases

The iterator yields CPU indices where the bit is set to true, making it ideal for:

  • Process scheduling: Iterate through available CPUs for task assignment
  • Load balancing: Traverse CPU sets to find optimal placement
  • Resource monitoring: Check specific CPU states in sequence
  • Affinity enforcement: Validate process can run on assigned CPUs

Sources: src/lib.rs(L412 - L427) 

Operating System Scenarios

Process Scheduling Workflow

This diagram shows how CpuMask integrates into a typical process scheduler:

flowchart TD
A["Process creation"]
B["Determine CPU affinity policy"]
C["CpuMask::new() or policy-specific constructor"]
D["Store affinity in process control block"]
E["Scheduler activation"]
F["Load process affinity mask"]
G["available_cpus & process_affinity"]
H["Any CPUs available?"]
I["Select CPU from mask"]
J["Wait or migrate"]
K["Schedule process on selected CPU"]
L["Update available_cpus"]
M["CPU hotplug event"]
N["Update system CPU mask"]
O["Revalidate all process affinities"]
P["Migrate processes if needed"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H
H --> I
H --> J
I --> K
J --> L
L --> G
M --> N
N --> O
O --> P

Sources: src/lib.rs(L9 - L23) 

NUMA-Aware CPU Management

In NUMA systems, CPU masks help maintain memory locality:

flowchart TD
subgraph subGraph1["NUMA Node 1"]
    C["CPUs 8-15"]
    D["Local Memory"]
end
subgraph subGraph0["NUMA Node 0"]
    A["CPUs 0-7"]
    B["Local Memory"]
end
E["Process A"]
F["CpuMask::mask(8) - Node 0 only"]
G["Process B"]
H["CpuMask::from_raw_bits(0xFF00) - Node 1 only"]
I["System Process"]
J["CpuMask::full() - Any CPU"]
K["Load balancer decides"]

E --> F
F --> A
G --> H
H --> C
I --> J
J --> K
K --> A
K --> C

Sources: src/lib.rs(L86 - L94)  src/lib.rs(L106 - L119) 

Thread Affinity Management

For multi-threaded applications, CPU masks enable fine-grained control:

Thread TypeAffinity StrategyImplementation
Main threadSingle CPUCpuMask::one_shot(0)
Worker threadsSubset of CPUsCpuMask::mask(worker_count)
I/O threadsNon-main CPUs!CpuMask::one_shot(0)
RT threadsDedicated CPUsCpuMask::from_raw_bits(rt_mask)

Sources: src/lib.rs(L121 - L128)  src/lib.rs(L289 - L299) 

Advanced Usage Patterns

Dynamic CPU Set Management

Real systems require dynamic adjustment of CPU availability:


Sources: src/lib.rs(L177 - L180)  src/lib.rs(L301 - L326) 

Performance Optimization Strategies

The choice of SIZE parameter significantly impacts performance:

System SizeRecommended SIZEStorage TypeUse Case
Single coreSIZE = 1boolEmbedded systems
Small SMP (≤8)SIZE = 8u8IoT devices
Desktop/Server (≤32)SIZE = 32u32General purpose
Large server (≤128)SIZE = 128u128HPC systems
Massive systemsSIZE = 1024[u128; 8]Cloud infrastructure

The automatic storage selection in src/lib.rs(L12 - L16)  ensures optimal memory usage and performance characteristics for each size category.

Error Handling Patterns

While CpuMask operations are generally infallible, certain usage patterns require validation:

  • Bounds checking: Methods use debug_assert! for index validation
  • Size validation: from_raw_bits() asserts value fits in SIZE bits
  • Range validation: one_shot() panics if index exceeds SIZE

In production systems, wrap these operations with appropriate error handling based on your safety requirements.

Sources: src/lib.rs(L106 - L108)  src/lib.rs(L123 - L124)  src/lib.rs(L168 - L170) 

Development and Contribution

Relevant source files

This document provides comprehensive guidance for developing and contributing to the cpumask library. It covers the development environment setup, build processes, testing procedures, code quality standards, and the continuous integration pipeline that ensures the library maintains compatibility across multiple target platforms.

For information about the library's API and usage patterns, see API Reference and Usage Guide and Examples. For architectural details about the implementation, see Architecture and Design.

Development Environment Setup

The cpumask library requires a Rust nightly toolchain due to its no-std compatibility and advanced const generic features. The development environment must support cross-compilation for multiple target architectures.

Required Tools and Dependencies

flowchart TD
subgraph subGraph2["External Dependencies"]
    I["bitmaps v3.2.1"]
    J["no default features"]
end
subgraph subGraph1["Target Platforms"]
    E["x86_64-unknown-linux-gnu"]
    F["x86_64-unknown-none"]
    G["riscv64gc-unknown-none-elf"]
    H["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Development Environment"]
    A["rust-toolchain nightly"]
    B["rust-src component"]
    C["clippy component"]
    D["rustfmt component"]
end

A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
B --> I
I --> J

The toolchain configuration requires nightly Rust with specific components and target support as defined in .github/workflows/ci.yml(L15 - L19) 

Sources: .github/workflows/ci.yml(L11 - L19)  Cargo.toml(L14 - L15) 

Package Configuration

The crate is configured for multi-environment compatibility with the following key characteristics:

ConfigurationValuePurpose
Edition2021Modern Rust features
LicenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0Triple licensing for flexibility
Categoriesos, no-stdOperating system and embedded use
Dependenciesbitmaps 3.2.1 (no default features)Minimal dependency footprint

The no-std compatibility and minimal dependencies ensure the library can be used in kernel-space and embedded environments as specified in Cargo.toml(L12)  and Cargo.toml(L15) 

Sources: Cargo.toml(L1 - L15) 

Build System and Compilation

Multi-Target Build Process

flowchart TD
subgraph subGraph2["Validation Steps"]
    G["cargo build"]
    H["cargo clippy"]
    I["cargo test (linux only)"]
end
subgraph subGraph1["Target Validation"]
    C["x86_64-unknown-linux-gnu"]
    D["x86_64-unknown-none"]
    E["riscv64gc-unknown-none-elf"]
    F["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Build Matrix"]
    A["Source Code"]
    B["Cargo Build"]
end

A --> B
B --> C
B --> D
B --> E
B --> F
C --> G
C --> H
C --> I
D --> G
D --> H
E --> G
E --> H
F --> G
F --> H

The build system validates compilation across four distinct target architectures to ensure compatibility with hosted Linux environments, bare-metal x86_64 systems, RISC-V embedded systems, and ARM64 embedded systems.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L26 - L27) 

Local Development Commands

Standard development workflow commands:

# Build for default target
cargo build --all-features

# Build for specific target 
cargo build --target x86_64-unknown-none --all-features

# Run tests (only supported on hosted targets)
cargo test -- --nocapture

# Format check
cargo fmt --all -- --check

# Lint check
cargo clippy --all-features -- -A clippy::new_without_default

Sources: .github/workflows/ci.yml(L23 - L30) 

Quality Assurance Pipeline

Code Quality Checks

The CI pipeline enforces multiple code quality standards through automated checks:

flowchart TD
subgraph Documentation["Documentation"]
    D1["cargo doc --no-deps --all-features"]
    D2["Enforces missing-docs"]
    D3["Checks broken intra-doc links"]
end
subgraph subGraph3["Test Execution"]
    C1["cargo test --target x86_64-unknown-linux-gnu"]
    C2["Runs with --nocapture flag"]
    C3["Linux target only"]
end
subgraph subGraph2["Clippy Analysis"]
    B1["cargo clippy --target TARGET"]
    B2["Allows clippy::new_without_default"]
    B3["Fails on other warnings"]
end
subgraph subGraph1["Format Check"]
    A1["cargo fmt --all -- --check"]
    A2["Enforces consistent style"]
end
subgraph subGraph0["Quality Gates"]
    A["Code Format Check"]
    B["Clippy Linting"]
    C["Unit Testing"]
    D["Documentation Build"]
end

A --> A1
A --> A2
B --> B1
B --> B2
B --> B3
C --> C1
C --> C2
C --> C3
D --> D1
D --> D2
D --> D3

Sources: .github/workflows/ci.yml(L22 - L30)  .github/workflows/ci.yml(L40) 

Testing Strategy

Testing is limited to the x86_64-unknown-linux-gnu target due to the hosted environment requirements for test execution. The test suite runs with the --nocapture flag to provide detailed output for debugging as configured in .github/workflows/ci.yml(L29 - L30) 

Sources: .github/workflows/ci.yml(L28 - L30) 

Continuous Integration Workflow

CI Pipeline Architecture

flowchart TD
subgraph subGraph3["Execution Steps"]
    G["actions/checkout@v4"]
    H["dtolnay/rust-toolchain@nightly"]
    I["rustc --version --verbose"]
    J["cargo fmt --all -- --check"]
    K["cargo clippy per target"]
    L["cargo build per target"]
    M["cargo test (linux only)"]
end
subgraph subGraph2["Build Matrix"]
    E["rust-toolchain: nightly"]
    F["targets: 4 platforms"]
end
subgraph subGraph1["CI Job"]
    C["Matrix Strategy"]
    D["fail-fast: false"]
end
subgraph subGraph0["Trigger Events"]
    A["Push Events"]
    B["Pull Request Events"]
end

A --> C
B --> C
C --> D
C --> E
C --> F
E --> G
F --> G
G --> H
H --> I
I --> J
J --> K
K --> L
L --> M

The CI system uses a fail-fast disabled strategy to ensure all target platforms are validated even if one fails, providing comprehensive feedback on compatibility issues.

Sources: .github/workflows/ci.yml(L3)  .github/workflows/ci.yml(L8 - L12) 

Documentation Deployment

flowchart TD
subgraph subGraph2["Deployment Process"]
    G["JamesIves/github-pages-deploy-action@v4"]
    H["single-commit: true"]
    I["branch: gh-pages"]
    J["folder: target/doc"]
end
subgraph subGraph1["Quality Enforcement"]
    D["broken intra-doc links check"]
    E["missing-docs requirement"]
    F["default branch restriction"]
end
subgraph subGraph0["Documentation Pipeline"]
    A["cargo doc build"]
    B["RUSTDOCFLAGS validation"]
    C["GitHub Pages deployment"]
end

A --> B
B --> D
B --> E
C --> G
D --> F
E --> F
F --> C
G --> H
G --> I
G --> J

Documentation is automatically built and deployed to GitHub Pages, but only from the default branch to prevent documentation pollution from development branches.

Sources: .github/workflows/ci.yml(L32 - L55)  .github/workflows/ci.yml(L40) 

Contribution Guidelines

Repository Structure

The repository follows a minimal structure optimized for a single-crate library:

PathPurposeManaged By
src/lib.rsCore implementationDevelopers
Cargo.tomlPackage configurationMaintainers
.github/workflows/ci.ymlCI pipelineMaintainers
.gitignoreGit exclusionsMaintainers
target/Build artifactsGit ignored

Sources: .gitignore(L1 - L4) 

Development Workflow

  1. Environment Setup: Install nightly Rust with required components
  2. Local Testing: Verify code compiles for all targets
  3. Quality Checks: Run cargo fmt and cargo clippy
  4. Documentation: Ensure all public APIs are documented
  5. Submission: Create pull request with clear description

The CI system will automatically validate all changes against the full target matrix and quality standards.

Sources: .github/workflows/ci.yml(L13 - L30) 

License Compliance

The library uses triple licensing (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) as specified in Cargo.toml(L7)  Contributors must ensure their submissions are compatible with all three license options.

Sources: Cargo.toml(L7) 

Overview

Relevant source files

The axcpu repository provides a unified, multi-architecture CPU abstraction library that enables privileged instruction execution and low-level CPU state management across different processor architectures. This library serves as a hardware abstraction layer (HAL) for the ArceOS operating system project, offering consistent interfaces for CPU context switching, trap handling, memory management, and system initialization.

This document covers the overall architecture and capabilities of the axcpu library. For detailed information about specific architectures, see x86_64 Architecture, AArch64 Architecture, RISC-V Architecture, and LoongArch64 Architecture. For cross-architecture features and abstractions, see Cross-Architecture Features.

Library Architecture

The axcpu library implements a modular architecture that provides consistent abstractions across four supported CPU architectures through conditional compilation and feature flags.

Architecture Selection and Module Structure


Sources: src/lib.rs(L14 - L28)  Cargo.toml(L37 - L52) 

Core Abstractions and Data Structures

The library provides several key abstractions that are implemented consistently across all supported architectures:

flowchart TD
subgraph subGraph2["Architecture-Specific Implementations"]
    ARCH_IMPL["Per-architecture modulesx86_64, aarch64, riscv, loongarch64"]
end
subgraph subGraph1["Key Operations"]
    SWITCH["context_switch()Assembly routine for task switching"]
    SAVE["save_context()Save CPU state to memory"]
    RESTORE["restore_context()Restore CPU state from memory"]
    INIT_TRAP["init_trap()Initialize trap handling"]
    INIT_MMU["init_mmu()Initialize memory management"]
end
subgraph subGraph0["Core Data Structures"]
    TC["TaskContextMinimal CPU state for task switching"]
    TF["TrapFrameComplete CPU state for exception handling"]
    FP["FpState/ExtendedStateFloating-point and SIMD state"]
end

FP --> RESTORE
FP --> SAVE
INIT_MMU --> ARCH_IMPL
INIT_TRAP --> ARCH_IMPL
SWITCH --> ARCH_IMPL
TC --> SWITCH
TF --> RESTORE
TF --> SAVE

Sources: Architecture-specific context.rs files, init.rs files across all architectures

Feature Flags and Conditional Compilation

The library uses Cargo features to enable optional functionality and reduce code size when certain capabilities are not needed:

FeatureDescriptionAffected Components
fp-simdFloating-point and SIMD state managementFpState,ExtendedStatestructures
tlsThread-local storage supportThread pointer management inTaskContext
uspaceUser space supportSystem call handling, user/kernel context switching

Sources: Cargo.toml(L23 - L27) 

Architecture-Specific Dependencies

Each architecture requires specific external crates for low-level operations:


Sources: Cargo.toml(L29 - L52) 

Trap Handling Framework

The library implements a unified trap handling system using distributed slices that allows external code to register handlers for different types of CPU exceptions and interrupts.

Trap Handler Registration

flowchart TD
subgraph subGraph2["Runtime Dispatch"]
    HANDLE_TRAP["handle_trap! macroGeneric trap dispatcher"]
    HANDLE_SYSCALL["handle_syscall()System call dispatcher"]
end
subgraph subGraph1["Registration Mechanism"]
    LINKME["linkme::distributed_sliceCompile-time handler collection"]
    DEF_MACRO["def_trap_handler macroHandler slice definition"]
    REG_MACRO["register_trap_handler macroHandler registration"]
end
subgraph subGraph0["Trap Handler Types"]
    IRQ_SLICE["IRQ: [fn(usize) -> bool]Hardware interrupt handlers"]
    PF_SLICE["PAGE_FAULT: [fn(VirtAddr, PageFaultFlags, bool) -> bool]Page fault handlers"]
    SYSCALL_SLICE["SYSCALL: [fn(&TrapFrame, usize) -> isize]System call handlers"]
end

DEF_MACRO --> LINKME
IRQ_SLICE --> HANDLE_TRAP
LINKME --> IRQ_SLICE
LINKME --> PF_SLICE
LINKME --> SYSCALL_SLICE
PF_SLICE --> HANDLE_TRAP
REG_MACRO --> LINKME
SYSCALL_SLICE --> HANDLE_SYSCALL

Sources: src/trap.rs(L10 - L22)  src/trap.rs(L25 - L44) 

Integration with ArceOS Ecosystem

The axcpu library is designed as a foundational component of the ArceOS operating system, providing the low-level CPU abstractions needed by higher-level OS components:

  • Memory Management: Integrates with memory_addr and page_table_entry crates for address translation and page table management
  • Architecture Support: Provides consistent interfaces that higher-level ArceOS components can use without architecture-specific code
  • Modular Design: Uses feature flags to enable only needed functionality, supporting both full OS and embedded use cases
  • No-std Compatible: Designed for bare-metal and kernel environments without standard library dependencies

Sources: Cargo.toml(L14)  Cargo.toml(L19 - L20)  src/lib.rs(L1) 

Documentation and Target Platforms

The library supports multiple target platforms and provides comprehensive documentation:

  • Documented Targets: x86_64-unknown-none, aarch64-unknown-none-softfloat, riscv64gc-unknown-none-elf, loongarch64-unknown-none-softfloat
  • Documentation: Available at docs.rs with all features enabled for complete API coverage
  • License: Triple-licensed under GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0

Sources: Cargo.toml(L57 - L59)  Cargo.toml(L15)  Cargo.toml(L18) 

x86_64 Architecture

Relevant source files

Purpose and Scope

This document provides an overview of the x86_64 architecture support in axcpu, which represents the most comprehensive and mature architecture implementation in the codebase. The x86_64 module provides CPU context management, trap handling, system call support, and hardware initialization for Intel/AMD 64-bit processors.

This page covers the overall module organization and key data structures. For detailed information about specific aspects, see:

For user space support across all architectures, see User Space Support.

Module Organization

The x86_64 architecture support is organized into several specialized modules, each handling different aspects of CPU management:

x86_64 Module Structure


Sources: src/x86_64/mod.rs(L1 - L21) 

Core Data Structures

The x86_64 implementation exports several key data structures that represent different aspects of CPU state:

Exported Types and Their Purposes

flowchart TD
subgraph subGraph2["External Dependencies"]
    X86_64_CRATE["x86_64::structures::tss"]
end
subgraph subGraph1["Hardware Tables"]
    TASK_CONTEXT["TaskContextMinimal context for task switching"]
    EXTENDED_STATE["ExtendedStateFPU and SIMD state"]
    TRAP_FRAME["TrapFrameComplete CPU state during exceptions"]
    FXSAVE_AREA["FxsaveAreax86 FXSAVE format"]
    GDT_STRUCT["GdtStructGlobal descriptor table"]
    IDT_STRUCT["IdtStructInterrupt descriptor table"]
    TSS["TaskStateSegmentTask state segment"]
end
subgraph subGraph0["Context Management"]
    TASK_CONTEXT["TaskContextMinimal context for task switching"]
    EXTENDED_STATE["ExtendedStateFPU and SIMD state"]
    TRAP_FRAME["TrapFrameComplete CPU state during exceptions"]
    FXSAVE_AREA["FxsaveAreax86 FXSAVE format"]
    GDT_STRUCT["GdtStructGlobal descriptor table"]
end

GDT_STRUCT --> TSS
TASK_CONTEXT --> EXTENDED_STATE
TRAP_FRAME --> FXSAVE_AREA
TSS --> X86_64_CRATE

Sources: src/x86_64/mod.rs(L17 - L20) 

Data StructurePurposeModule Source
TaskContextStores callee-saved registers and stack pointer for task switchingcontext
TrapFrameCaptures complete CPU state during exceptions and interruptscontext
ExtendedStateManages FPU and SIMD register statecontext
FxsaveAreax86-specific FXSAVE/FXRSTOR data formatcontext
GdtStructGlobal descriptor table managementgdt
IdtStructInterrupt descriptor table managementidt
TaskStateSegmentHardware task state segment (from x86_64 crate)External

Feature-Based Compilation

The x86_64 module uses conditional compilation to enable different capabilities based on build configuration:

Conditional Module Loading

flowchart TD
subgraph subGraph2["Compilation Conditions"]
    TARGET_NONE["target_os = \none\"]
    USPACE_FEAT["feature = \uspace\"]
end
subgraph subGraph1["Conditional Modules"]
    TRAP_MOD["trap.rs"]
    SYSCALL_MOD["syscall.rs"]
    USPACE_MOD["uspace.rs"]
end
subgraph subGraph0["Base Modules"]
    BASE_MODS["context, gdt, idt, asm, init"]
end

BASE_MODS --> SYSCALL_MOD
BASE_MODS --> TRAP_MOD
BASE_MODS --> USPACE_MOD
TARGET_NONE --> TRAP_MOD
USPACE_FEAT --> SYSCALL_MOD
USPACE_FEAT --> USPACE_MOD

Sources: src/x86_64/mod.rs(L8 - L15) 

The conditional compilation enables:

  • Trap handling (target_os = "none"): Exception and interrupt processing for bare-metal environments
  • User space support (feature = "uspace"): System call handling and user-kernel transitions
  • System call interface (feature = "uspace"): Dedicated syscall entry points and parameter handling

Key Components Overview

The x86_64 architecture support provides several essential capabilities:

Hardware Abstraction Layer

  • Context Management: Efficient task switching using minimal register sets in TaskContext
  • Exception Handling: Complete trap frame capture and restoration for all exception types
  • Memory Management: Integration with x86_64 paging hardware and TLB management
  • FPU/SIMD Support: Extended state preservation for floating-point and vector operations

System Integration

  • Descriptor Tables: GDT and IDT management for privilege level transitions
  • Task State Segment: Hardware task switching support (though typically unused in modern kernels)
  • System Calls: Fast system call entry points using SYSCALL/SYSRET instructions
  • User Space: Complete user-kernel isolation with separate address spaces

Assembly Interface

The asm module provides low-level operations that cannot be expressed in safe Rust, including:

  • Context switching routines
  • Trap entry/exit sequences
  • CPU state manipulation
  • Hardware feature detection and configuration

This architecture implementation serves as the foundation for kernel development on x86_64 platforms, providing all necessary abstractions for task management, memory protection, and hardware interaction.

Sources: src/x86_64/mod.rs(L1 - L21) 

x86_64 Context Management

Relevant source files

This document covers the x86_64 CPU context management implementation in axcpu, focusing on the core data structures and mechanisms used for task context switching, exception handling, and state preservation. The implementation provides both kernel-level task switching and user space context management capabilities.

For x86_64 trap and exception handling mechanisms, see x86_64 Trap and Exception Handling. For system call implementation details, see x86_64 System Calls.

Core Context Data Structures

The x86_64 context management system uses several key data structures to manage CPU state across different scenarios:

flowchart TD
subgraph subGraph2["Hardware Integration"]
    CPU["x86_64 CPU"]
    Stack["Kernel Stack"]
    Registers["CPU Registers"]
end
subgraph subGraph0["Core Context Types"]
    TaskContext["TaskContextMain task switching context"]
    TrapFrame["TrapFrameException/interrupt context"]
    ContextSwitchFrame["ContextSwitchFrameInternal context switch frame"]
    ExtendedState["ExtendedStateFP/SIMD state container"]
    FxsaveArea["FxsaveArea512-byte FXSAVE region"]
end

ContextSwitchFrame --> Registers
ExtendedState --> CPU
ExtendedState --> FxsaveArea
TaskContext --> ContextSwitchFrame
TaskContext --> ExtendedState
TaskContext --> Stack
TrapFrame --> Registers

Sources: src/x86_64/context.rs(L1 - L291) 

TaskContext Structure

The TaskContext struct represents the complete saved hardware state of a task, containing all necessary information for context switching:

FieldTypePurposeFeature Gate
kstack_topVirtAddrTop of kernel stackAlways
rspu64Stack pointer after register savesAlways
fs_baseusizeThread Local Storage baseAlways
gs_baseusizeUser space GS base registeruspace
ext_stateExtendedStateFP/SIMD statefp-simd
cr3PhysAddrPage table rootuspace

Sources: src/x86_64/context.rs(L166 - L183) 

TrapFrame Structure

The TrapFrame captures the complete CPU register state when a trap (interrupt or exception) occurs, containing all general-purpose registers plus trap-specific information:

flowchart TD
subgraph subGraph1["Syscall Interface"]
    ARG0["arg0() -> rdi"]
    ARG1["arg1() -> rsi"]
    ARG2["arg2() -> rdx"]
    ARG3["arg3() -> r10"]
    ARG4["arg4() -> r8"]
    ARG5["arg5() -> r9"]
    USER_CHECK["is_user() -> cs & 3 == 3"]
end
subgraph subGraph0["TrapFrame Layout"]
    GPR["General Purpose Registersrax, rbx, rcx, rdx, rbp, rsi, rdir8, r9, r10, r11, r12, r13, r14, r15"]
    TRAP_INFO["Trap Informationvector, error_code"]
    CPU_PUSHED["CPU-Pushed Fieldsrip, cs, rflags, rsp, ss"]
end

CPU_PUSHED --> USER_CHECK
GPR --> ARG0
GPR --> ARG1
GPR --> ARG2
GPR --> ARG3
GPR --> ARG4
GPR --> ARG5

Sources: src/x86_64/context.rs(L4 - L72) 

Context Switching Mechanism

The context switching process involves saving the current task's state and restoring the next task's state through a coordinated sequence of operations:

flowchart TD
subgraph subGraph1["Feature Gates"]
    FP_GATE["fp-simd feature"]
    TLS_GATE["tls feature"]
    USPACE_GATE["uspace feature"]
end
subgraph subGraph0["Context Switch Flow"]
    START["switch_to() called"]
    SAVE_FP["Save FP/SIMD stateext_state.save()"]
    RESTORE_FP["Restore FP/SIMD statenext_ctx.ext_state.restore()"]
    SAVE_TLS["Save current TLSread_thread_pointer()"]
    RESTORE_TLS["Restore next TLSwrite_thread_pointer()"]
    SAVE_GS["Save GS baserdmsr(IA32_KERNEL_GSBASE)"]
    RESTORE_GS["Restore GS basewrmsr(IA32_KERNEL_GSBASE)"]
    UPDATE_TSS["Update TSS RSP0write_tss_rsp0()"]
    SWITCH_PT["Switch page tablewrite_user_page_table()"]
    ASM_SWITCH["Assembly context switchcontext_switch()"]
    END["Context switch complete"]
end

ASM_SWITCH --> END
RESTORE_FP --> FP_GATE
RESTORE_FP --> SAVE_TLS
RESTORE_GS --> UPDATE_TSS
RESTORE_GS --> USPACE_GATE
RESTORE_TLS --> SAVE_GS
RESTORE_TLS --> TLS_GATE
SAVE_FP --> FP_GATE
SAVE_FP --> RESTORE_FP
SAVE_GS --> RESTORE_GS
SAVE_GS --> USPACE_GATE
SAVE_TLS --> RESTORE_TLS
SAVE_TLS --> TLS_GATE
START --> SAVE_FP
SWITCH_PT --> ASM_SWITCH
SWITCH_PT --> USPACE_GATE
UPDATE_TSS --> SWITCH_PT
UPDATE_TSS --> USPACE_GATE

Sources: src/x86_64/context.rs(L242 - L265) 

Assembly Context Switch Implementation

The low-level context switching is implemented in assembly using a naked function that saves and restores callee-saved registers:

flowchart TD
subgraph subGraph0["context_switch Assembly"]
    PUSH["Push callee-saved registersrbp, rbx, r12-r15"]
    SAVE_RSP["Save current RSPmov [rdi], rsp"]
    LOAD_RSP["Load next RSPmov rsp, [rsi]"]
    POP["Pop callee-saved registersr15-r12, rbx, rbp"]
    RET["Return to new taskret"]
end

LOAD_RSP --> POP
POP --> RET
PUSH --> SAVE_RSP
SAVE_RSP --> LOAD_RSP

Sources: src/x86_64/context.rs(L268 - L290) 

Extended State Management

The x86_64 architecture provides extensive floating-point and SIMD capabilities that require specialized state management:

FxsaveArea Structure

The FxsaveArea represents the 512-byte memory region used by the FXSAVE/FXRSTOR instructions:

FieldTypePurpose
fcwu16FPU Control Word
fswu16FPU Status Word
ftwu16FPU Tag Word
fopu16FPU Opcode
fipu64FPU Instruction Pointer
fdpu64FPU Data Pointer
mxcsru32MXCSR Register
mxcsr_masku32MXCSR Mask
st[u64; 16]ST0-ST7 FPU registers
xmm[u64; 32]XMM0-XMM15 registers

Sources: src/x86_64/context.rs(L86 - L107) 

ExtendedState Operations

The ExtendedState provides methods for saving and restoring FP/SIMD state:

flowchart TD
subgraph subGraph2["Hardware Interface"]
    CPU_FPU["CPU FPU/SIMD Units"]
    FXSAVE_AREA["512-byte aligned memory"]
end
subgraph subGraph1["Default Values"]
    FCW["fcw = 0x37fStandard FPU control"]
    FTW["ftw = 0xffffAll tags empty"]
    MXCSR["mxcsr = 0x1f80Standard SIMD control"]
end
subgraph subGraph0["ExtendedState Operations"]
    SAVE["save()_fxsave64()"]
    RESTORE["restore()_fxrstor64()"]
    DEFAULT["default()Initialize with standard values"]
end

DEFAULT --> FCW
DEFAULT --> FTW
DEFAULT --> MXCSR
RESTORE --> CPU_FPU
RESTORE --> FXSAVE_AREA
SAVE --> CPU_FPU
SAVE --> FXSAVE_AREA

Sources: src/x86_64/context.rs(L115 - L137) 

Task Context Initialization

The task context initialization process sets up a new task for execution:

flowchart TD
subgraph subGraph2["Stack Layout"]
    STACK_TOP["Stack top (16-byte aligned)"]
    PADDING["8-byte padding"]
    SWITCH_FRAME["ContextSwitchFramerip = entry point"]
end
subgraph subGraph1["Default Values"]
    KSTACK["kstack_top = 0"]
    RSP["rsp = 0"]
    FS_BASE["fs_base = 0"]
    CR3["cr3 = kernel page table"]
    EXT_STATE["ext_state = default"]
    GS_BASE["gs_base = 0"]
end
subgraph subGraph0["Task Initialization"]
    NEW["new()Create empty context"]
    INIT["init()Setup for execution"]
    SETUP_STACK["Setup kernel stack16-byte alignment"]
    CREATE_FRAME["Create ContextSwitchFrameSet entry point"]
    SET_TLS["Set TLS basefs_base = tls_area"]
end

CREATE_FRAME --> SET_TLS
INIT --> SETUP_STACK
NEW --> CR3
NEW --> EXT_STATE
NEW --> FS_BASE
NEW --> GS_BASE
NEW --> KSTACK
NEW --> RSP
PADDING --> SWITCH_FRAME
SETUP_STACK --> CREATE_FRAME
SETUP_STACK --> STACK_TOP
STACK_TOP --> PADDING

Sources: src/x86_64/context.rs(L185 - L227) 

User Space Context Management

When the uspace feature is enabled, the context management system provides additional support for user space processes:

User Space Fields

FieldPurposeUsage
gs_baseUser space GS base registerSaved/restored via MSR operations
cr3Page table rootUpdated during context switch

Page Table Management

The context switching process includes page table switching when transitioning between user space tasks:

flowchart TD
subgraph subGraph1["TSS Update"]
    UPDATE_TSS["write_tss_rsp0(next_ctx.kstack_top)"]
    KERNEL_STACK["Update kernel stack for syscalls"]
end
subgraph subGraph0["Page Table Switch Logic"]
    CHECK["Compare next_ctx.cr3 with current cr3"]
    SWITCH["write_user_page_table(next_ctx.cr3)"]
    FLUSH["TLB automatically flushed"]
    SKIP["Skip page table switch"]
end

CHECK --> SKIP
CHECK --> SWITCH
SWITCH --> FLUSH
SWITCH --> UPDATE_TSS
UPDATE_TSS --> KERNEL_STACK

Sources: src/x86_64/context.rs(L253 - L263) 

System Call Argument Extraction

The TrapFrame provides convenient methods for extracting system call arguments following the x86_64 calling convention:

MethodRegisterPurpose
arg0()rdiFirst argument
arg1()rsiSecond argument
arg2()rdxThird argument
arg3()r10Fourth argument (note: r10, not rcx)
arg4()r8Fifth argument
arg5()r9Sixth argument
is_user()cs & 3Check if trap originated from user space

Sources: src/x86_64/context.rs(L37 - L71) 

x86_64 Trap and Exception Handling

Relevant source files

This document covers the x86_64-specific trap and exception handling implementation in the axcpu library. It focuses on how hardware exceptions, interrupts, and system calls are processed and dispatched to appropriate handlers on the x86_64 architecture.

For broader context management including TrapFrame structure details, see x86_64 Context Management. For system call implementation specifics, see x86_64 System Calls. For cross-architecture trap handling abstractions, see Core Trap Handling Framework.

Exception Dispatch Architecture

The x86_64 trap handling system centers around the x86_trap_handler function, which serves as the main dispatcher for all hardware exceptions and interrupts. This handler examines the trap vector number to determine the appropriate response.

flowchart TD
subgraph subGraph2["Exception Handlers"]
    PAGE_FAULT["handle_page_fault()"]
    BREAKPOINT["Breakpoint Debug"]
    GPF["General Protection Fault Panic"]
    SYSCALL["x86_syscall_handler()"]
    IRQ["IRQ Handler via handle_trap!"]
    UNKNOWN["Unhandled Exception Panic"]
end
subgraph subGraph1["Main Dispatch Logic"]
    TRAP_HANDLER["x86_trap_handler()"]
    VECTOR_MATCH["Match tf.vector"]
end
subgraph subGraph0["Hardware Exception Entry"]
    HW_TRAP["Hardware Exception/Interrupt"]
    ASM_ENTRY["trap.S Assembly Entry Point"]
    SAVE_CONTEXT["Save CPU State to TrapFrame"]
end

ASM_ENTRY --> SAVE_CONTEXT
HW_TRAP --> ASM_ENTRY
SAVE_CONTEXT --> TRAP_HANDLER
TRAP_HANDLER --> VECTOR_MATCH
VECTOR_MATCH --> BREAKPOINT
VECTOR_MATCH --> GPF
VECTOR_MATCH --> IRQ
VECTOR_MATCH --> PAGE_FAULT
VECTOR_MATCH --> SYSCALL
VECTOR_MATCH --> UNKNOWN

The dispatch mechanism uses a simple match statement on the trap vector stored in the TrapFrame. Each vector corresponds to a specific x86_64 exception or interrupt type defined by the hardware architecture.

Sources: src/x86_64/trap.rs(L33 - L59) 

Vector Classification and Constants

The trap handler categorizes exceptions and interrupts using predefined vector constants that correspond to x86_64 architectural definitions:

Vector TypeRange/ValuePurpose
PAGE_FAULT_VECTOR14Memory access violations
BREAKPOINT_VECTOR3Debug breakpoints
GENERAL_PROTECTION_FAULT_VECTOR13Protection violations
LEGACY_SYSCALL_VECTOR0x80System calls (int 0x80)
IRQ_VECTOR_STARTtoIRQ_VECTOR_END0x20-0xFFHardware interrupts

Sources: src/x86_64/trap.rs(L10 - L13)  src/x86_64/trap.rs(L34 - L47) 

Page Fault Handling

Page fault processing involves extracting the fault address from the CR2 control register and converting the hardware error code into portable flags for the broader trap handling framework.

flowchart TD
subgraph subGraph3["Dispatch Decision"]
    HANDLE_TRAP["handle_trap!(PAGE_FAULT, ...)"]
    SUCCESS["Handler processes fault"]
    PANIC["Unhandled page fault panic"]
end
subgraph subGraph2["Flag Mapping"]
    WRITE_FLAG["PageFaultFlags::WRITE"]
    READ_FLAG["PageFaultFlags::READ"]
    USER_FLAG["PageFaultFlags::USER"]
    EXEC_FLAG["PageFaultFlags::EXECUTE"]
end
subgraph subGraph1["Error Code Analysis"]
    WRITE_BIT["CAUSED_BY_WRITE bit"]
    USER_BIT["USER_MODE bit"]
    EXEC_BIT["INSTRUCTION_FETCH bit"]
    RESERVED_CHECK["Check reserved bits"]
end
subgraph subGraph0["Page Fault Processing"]
    PF_ENTRY["handle_page_fault(tf)"]
    ERR_CONVERT["err_code_to_flags(tf.error_code)"]
    CR2_READ["Read fault address from CR2"]
    FLAG_CHECK["Convert to PageFaultFlags"]
end
read_FLAG["read_FLAG"]

CR2_READ --> HANDLE_TRAP
ERR_CONVERT --> FLAG_CHECK
EXEC_BIT --> EXEC_FLAG
EXEC_FLAG --> HANDLE_TRAP
FLAG_CHECK --> EXEC_BIT
FLAG_CHECK --> RESERVED_CHECK
FLAG_CHECK --> USER_BIT
FLAG_CHECK --> WRITE_BIT
HANDLE_TRAP --> PANIC
HANDLE_TRAP --> SUCCESS
PF_ENTRY --> CR2_READ
PF_ENTRY --> ERR_CONVERT
USER_BIT --> USER_FLAG
USER_FLAG --> HANDLE_TRAP
WRITE_BIT --> READ_FLAG
WRITE_BIT --> WRITE_FLAG
WRITE_FLAG --> HANDLE_TRAP
read_FLAG --> HANDLE_TRAP

The err_code_to_flags function translates x86_64 page fault error codes into architecture-neutral PageFaultFlags. This abstraction allows the same page fault logic to work across different architectures.

Sources: src/x86_64/trap.rs(L15 - L30)  src/x86_64/trap.rs(L69 - L92) 

Interrupt Request (IRQ) Handling

Hardware interrupts occupy vector numbers 0x20 through 0xFF and are handled through the unified trap framework. The handler identifies IRQ vectors and delegates processing to registered handlers.

flowchart TD
subgraph subGraph1["IRQ Vector Space"]
    VEC_20["0x20: Timer"]
    VEC_21["0x21: Keyboard"]
    VEC_22["0x22: Cascade"]
    VEC_DOTS["..."]
    VEC_FF["0xFF: Spurious"]
end
subgraph subGraph0["IRQ Processing Flow"]
    IRQ_DETECT["Vector in IRQ_VECTOR_START..=IRQ_VECTOR_END"]
    IRQ_EXTRACT["Extract IRQ number from tf.vector"]
    HANDLE_TRAP_IRQ["handle_trap!(IRQ, tf.vector as _)"]
end

IRQ_DETECT --> IRQ_EXTRACT
IRQ_EXTRACT --> HANDLE_TRAP_IRQ
VEC_DOTS --> IRQ_DETECT
VEC_FF --> IRQ_DETECT

The IRQ handling delegates to the cross-architecture trap framework using the handle_trap! macro, which allows different system components to register interrupt handlers.

Sources: src/x86_64/trap.rs(L45 - L47) 

Assembly Integration

The trap handling system integrates with assembly code through the trap.S file, which contains the low-level exception entry points and state saving routines.


The assembly code is included at compile time and provides the hardware-level interface between x86_64 exception mechanisms and the Rust handler functions.

Sources: src/x86_64/trap.rs(L7) 

System Call Integration

When the uspace feature is enabled, the trap handler supports legacy system calls via the int 0x80 instruction. This provides compatibility with traditional Unix system call interfaces.


System call handling is feature-gated and delegates to the dedicated system call module for processing.

Sources: src/x86_64/trap.rs(L9 - L10)  src/x86_64/trap.rs(L44) 

Error Handling and Diagnostics

The trap handler implements comprehensive error reporting for unhandled exceptions and invalid page faults. Critical errors result in detailed panic messages that include register state and fault information.

Unhandled Exception Reporting

For unknown or unsupported exception vectors, the handler provides detailed diagnostic information including vector number, mnemonic name, error code, and complete register state dump.

Page Fault Error Reporting

Page fault panics include comprehensive fault analysis showing whether the fault occurred in user or kernel mode, the faulting instruction address, the virtual address that caused the fault, and the decoded access flags.

Sources: src/x86_64/trap.rs(L20 - L29)  src/x86_64/trap.rs(L48 - L57)  src/x86_64/trap.rs(L61 - L67) 

Integration with Cross-Architecture Framework

The x86_64 trap handler integrates with the broader axcpu trap handling framework through the handle_trap! macro and standardized flag types like PageFaultFlags. This design allows architecture-specific trap handling while maintaining consistent interfaces for higher-level system components.

Sources: src/x86_64/trap.rs(L5)  src/x86_64/trap.rs(L19)  src/x86_64/trap.rs(L46) 

x86_64 System Calls

Relevant source files

This document covers the x86_64 system call implementation in the axcpu library, including the low-level assembly entry point, MSR configuration, and user-kernel transition mechanisms. The system call interface enables user space programs to request kernel services through the syscall instruction.

For general trap and exception handling beyond system calls, see x86_64 Trap and Exception Handling. For user space context management across architectures, see User Space Support.

System Call Architecture Overview

The x86_64 system call implementation consists of three main components: MSR configuration during initialization, the assembly entry point that handles the low-level transition, and the Rust handler that processes the actual system call.

flowchart TD
subgraph subGraph2["Context Management"]
    SWAPGS["swapgs"]
    STACK_SWITCH["Stack Switch"]
    TRAPFRAME["TrapFrame Construction"]
    RESTORE["Context Restore"]
end
subgraph subGraph0["System Call Flow"]
    USER["User Space Process"]
    SYSCALL_INSTR["syscall instruction"]
    ENTRY["syscall_entry (Assembly)"]
    HANDLER["x86_syscall_handler (Rust)"]
    GENERIC["handle_syscall (Generic)"]
    SYSRET["sysretq instruction"]
end
subgraph Configuration["Configuration"]
    INIT["init_syscall()"]
    LSTAR["LSTAR MSR"]
    STAR["STAR MSR"]
    SFMASK["SFMASK MSR"]
    EFER["EFER MSR"]
end

ENTRY --> HANDLER
ENTRY --> STACK_SWITCH
ENTRY --> SWAPGS
ENTRY --> TRAPFRAME
GENERIC --> HANDLER
HANDLER --> GENERIC
HANDLER --> SYSRET
INIT --> EFER
INIT --> LSTAR
INIT --> SFMASK
INIT --> STAR
SYSCALL_INSTR --> ENTRY
SYSRET --> RESTORE
SYSRET --> USER
USER --> SYSCALL_INSTR

Sources: src/x86_64/syscall.rs(L1 - L49)  src/x86_64/syscall.S(L1 - L56) 

System Call Initialization

The init_syscall function configures the x86_64 Fast System Call mechanism by programming several Model Specific Registers (MSRs).

MSRPurposeConfiguration
LSTARLong Syscall Target AddressPoints tosyscall_entryassembly function
STARSyscall Target AddressContains segment selectors for user/kernel code/data
SFMASKSyscall Flag MaskMasks specific RFLAGS during syscall execution
EFERExtended Feature EnableEnables System Call Extensions (SCE bit)
flowchart TD
subgraph subGraph3["MSR Configuration in init_syscall()"]
    INIT_FUNC["init_syscall()"]
    subgraph subGraph2["Flag Masking"]
        TF["TRAP_FLAG"]
        IF["INTERRUPT_FLAG"]
        DF["DIRECTION_FLAG"]
        IOPL["IOPL_LOW | IOPL_HIGH"]
        AC["ALIGNMENT_CHECK"]
        NT["NESTED_TASK"]
    end
    subgraph subGraph1["GDT Selectors"]
        UCODE["UCODE64_SELECTOR"]
        UDATA["UDATA_SELECTOR"]
        KCODE["KCODE64_SELECTOR"]
        KDATA["KDATA_SELECTOR"]
    end
    subgraph subGraph0["MSR Setup"]
        LSTAR_SETUP["LStar::write(syscall_entry)"]
        STAR_SETUP["Star::write(selectors)"]
        SFMASK_SETUP["SFMask::write(flags)"]
        EFER_SETUP["Efer::update(SCE)"]
        KERNELGS_SETUP["KernelGsBase::write(0)"]
    end
end

INIT_FUNC --> EFER_SETUP
INIT_FUNC --> KERNELGS_SETUP
INIT_FUNC --> LSTAR_SETUP
INIT_FUNC --> SFMASK_SETUP
INIT_FUNC --> STAR_SETUP
SFMASK_SETUP --> AC
SFMASK_SETUP --> DF
SFMASK_SETUP --> IF
SFMASK_SETUP --> IOPL
SFMASK_SETUP --> NT
SFMASK_SETUP --> TF
STAR_SETUP --> KCODE
STAR_SETUP --> KDATA
STAR_SETUP --> UCODE
STAR_SETUP --> UDATA

The SFMASK register masks flags with value 0x47700, ensuring that potentially dangerous flags like interrupts and trap flags are cleared during system call execution.

Sources: src/x86_64/syscall.rs(L22 - L48) 

Assembly Entry Point

The syscall_entry function in assembly handles the low-level transition from user space to kernel space. It performs critical operations including GS base swapping, stack switching, and trap frame construction.

sequenceDiagram
    participant UserSpace as "User Space"
    participant CPUHardware as "CPU Hardware"
    participant KernelSpace as "Kernel Space"

    UserSpace ->> CPUHardware: syscall instruction
    CPUHardware ->> KernelSpace: Jump to syscall_entry
    Note over KernelSpace: swapgs (switch to kernel GS)
    KernelSpace ->> KernelSpace: Save user RSP to percpu area
    KernelSpace ->> KernelSpace: Load kernel stack from TSS
    Note over KernelSpace: Build TrapFrame on stack
    KernelSpace ->> KernelSpace: Push user RSP, RFLAGS, RIP
    KernelSpace ->> KernelSpace: Push all general purpose registers
    KernelSpace ->> KernelSpace: Call x86_syscall_handler(trapframe)
    KernelSpace ->> KernelSpace: Pop all general purpose registers
    KernelSpace ->> KernelSpace: Restore user RSP, RFLAGS, RIP
    Note over KernelSpace: swapgs (switch to user GS)
    KernelSpace ->> CPUHardware: sysretq instruction
    CPUHardware ->> UserSpace: Return to user space

Key Assembly Operations

The entry point performs these critical steps:

  1. GS Base Switching: swapgs switches between user and kernel GS base registers
  2. Stack Management: Saves user RSP and switches to kernel stack from TSS
  3. Register Preservation: Pushes all general-purpose registers to build a TrapFrame
  4. Handler Invocation: Calls x86_syscall_handler with the trap frame pointer
  5. Context Restoration: Restores all registers and user context
  6. Return: Uses sysretq to return to user space

Sources: src/x86_64/syscall.S(L3 - L55) 

System Call Handler

The Rust-based system call handler processes the actual system call request after the assembly entry point has set up the execution context.

flowchart TD
subgraph subGraph2["Per-CPU Data"]
    USER_RSP["USER_RSP_OFFSET"]
    TSS_STACK["TSS privilege_stack_table"]
end
subgraph subGraph1["Data Flow"]
    TRAPFRAME_IN["TrapFrame (Input)"]
    SYSCALL_NUM["tf.rax (System Call Number)"]
    RESULT["Return Value"]
    TRAPFRAME_OUT["tf.rax (Updated)"]
end
subgraph subGraph0["Handler Flow"]
    ASM_ENTRY["syscall_entry (Assembly)"]
    RUST_HANDLER["x86_syscall_handler"]
    GENERIC_HANDLER["crate::trap::handle_syscall"]
    RETURN["Return to Assembly"]
end

ASM_ENTRY --> RUST_HANDLER
ASM_ENTRY --> TSS_STACK
ASM_ENTRY --> USER_RSP
GENERIC_HANDLER --> RESULT
GENERIC_HANDLER --> RUST_HANDLER
RESULT --> TRAPFRAME_OUT
RUST_HANDLER --> GENERIC_HANDLER
RUST_HANDLER --> RETURN
SYSCALL_NUM --> GENERIC_HANDLER
TRAPFRAME_IN --> RUST_HANDLER

The handler function is minimal but critical:

#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
pub(super) fn x86_syscall_handler(tf: &mut TrapFrame) {
    tf.rax = crate::trap::handle_syscall(tf, tf.rax as usize) as u64;
}
}

It extracts the system call number from rax, delegates to the generic system call handler, and stores the return value back in rax for return to user space.

Sources: src/x86_64/syscall.rs(L17 - L20) 

Per-CPU State Management

The system call implementation uses per-CPU variables to manage state during the user-kernel transition:

VariablePurposeUsage
USER_RSP_OFFSETStores user stack pointerSaved during entry, restored during exit
TSS.privilege_stack_tableKernel stack pointerLoaded from TSS during stack switch
flowchart TD
subgraph subGraph1["Assembly Operations"]
    SAVE_USER_RSP["mov gs:[USER_RSP_OFFSET], rsp"]
    LOAD_KERNEL_RSP["mov rsp, gs:[TSS + rsp0_offset]"]
    RESTORE_USER_RSP["mov rsp, [rsp - 2 * 8]"]
end
subgraph subGraph0["Per-CPU Memory Layout"]
    PERCPU_BASE["Per-CPU Base Address"]
    USER_RSP_VAR["USER_RSP_OFFSET"]
    TSS_VAR["TSS Structure"]
    TSS_RSP0["privilege_stack_table[0]"]
end

PERCPU_BASE --> TSS_VAR
PERCPU_BASE --> USER_RSP_VAR
SAVE_USER_RSP --> RESTORE_USER_RSP
TSS_RSP0 --> LOAD_KERNEL_RSP
TSS_VAR --> TSS_RSP0
USER_RSP_VAR --> SAVE_USER_RSP

The per-CPU storage ensures that system calls work correctly in multi-CPU environments where each CPU needs its own temporary storage for user state.

Sources: src/x86_64/syscall.rs(L9 - L10)  src/x86_64/syscall.S(L5 - L6)  src/x86_64/syscall.S(L52) 

Integration with Trap Framework

The x86_64 system call implementation integrates with the broader trap handling framework through the handle_syscall function, which provides architecture-independent system call processing.

flowchart TD
subgraph subGraph2["Return Path"]
    RESULT_VAL["Return Value"]
    RAX_UPDATE["tf.rax = result"]
    SYSRET["sysretq"]
end
subgraph subGraph1["Generic Trap Framework"]
    HANDLE_SYSCALL["crate::trap::handle_syscall"]
    SYSCALL_DISPATCH["System Call Dispatch"]
    SYSCALL_IMPL["Actual System Call Implementation"]
end
subgraph subGraph0["Architecture-Specific Layer"]
    SYSCALL_ENTRY["syscall_entry.S"]
    X86_HANDLER["x86_syscall_handler"]
    TRAPFRAME["TrapFrame"]
end

HANDLE_SYSCALL --> SYSCALL_DISPATCH
RAX_UPDATE --> SYSRET
RESULT_VAL --> RAX_UPDATE
SYSCALL_DISPATCH --> SYSCALL_IMPL
SYSCALL_ENTRY --> X86_HANDLER
SYSCALL_IMPL --> RESULT_VAL
TRAPFRAME --> HANDLE_SYSCALL
X86_HANDLER --> TRAPFRAME

This layered approach allows the generic trap framework to handle system call logic while the x86_64-specific code manages the low-level hardware interface and calling conventions.

Sources: src/x86_64/syscall.rs(L18 - L19) 

x86_64 System Initialization

Relevant source files

This document covers the x86_64 CPU initialization procedures during system bootstrap, including the setup of critical system tables and per-CPU data structures. The initialization process establishes the Global Descriptor Table (GDT), Interrupt Descriptor Table (IDT), and prepares the CPU for trap handling and task switching.

For x86_64 trap and exception handling mechanisms, see x86_64 Trap and Exception Handling. For x86_64 system call implementation details, see x86_64 System Calls.

Initialization Overview

The x86_64 system initialization follows a structured sequence that prepares the CPU for operation in both kernel and user modes. The process is coordinated through the init module and involves setting up fundamental x86_64 system tables.

flowchart TD
start["System Boot"]
init_percpu["init_percpu()"]
init_trap["init_trap()"]
init_gdt["init_gdt()"]
init_idt["init_idt()"]
init_syscall["init_syscall()"]
gdt_setup["GDT Table Setup"]
load_gdt["lgdt instruction"]
load_tss["load_tss()"]
idt_setup["IDT Table Setup"]
load_idt["lidt instruction"]
msr_setup["MSR Configuration"]
ready["CPU Ready"]

gdt_setup --> load_gdt
idt_setup --> load_idt
init_gdt --> gdt_setup
init_idt --> idt_setup
init_percpu --> init_trap
init_syscall --> msr_setup
init_trap --> init_gdt
init_trap --> init_idt
init_trap --> init_syscall
load_gdt --> load_tss
load_idt --> ready
load_tss --> ready
msr_setup --> ready
start --> init_percpu

Initialization Sequence Flow

Sources: src/x86_64/init.rs(L15 - L38) 

Per-CPU Data Structure Initialization

The init_percpu() function establishes per-CPU data structures required for multi-core operation. This initialization must occur before trap handling setup.

FunctionPurposeDependencies
percpu::init()Initialize percpu frameworkNone
percpu::init_percpu_reg()Set CPU-specific registerCPU ID

The function takes a cpu_id parameter to configure CPU-specific storage and must be called on each CPU core before init_trap().

flowchart TD
percpu_init["percpu::init()"]
percpu_reg["percpu::init_percpu_reg(cpu_id)"]
tss_ready["TSS per-CPU ready"]
gdt_ready["GDT per-CPU ready"]

percpu_init --> percpu_reg
percpu_reg --> gdt_ready
percpu_reg --> tss_ready

Per-CPU Initialization Dependencies

Sources: src/x86_64/init.rs(L15 - L18) 

Global Descriptor Table (GDT) Initialization

The GDT setup establishes memory segmentation for x86_64 operation, including kernel and user code/data segments plus the Task State Segment (TSS).

GDT Structure

The GdtStruct contains predefined segment selectors for different privilege levels and operating modes:

SelectorIndexPurposePrivilege
KCODE32_SELECTOR1Kernel 32-bit codeRing 0
KCODE64_SELECTOR2Kernel 64-bit codeRing 0
KDATA_SELECTOR3Kernel dataRing 0
UCODE32_SELECTOR4User 32-bit codeRing 3
UDATA_SELECTOR5User dataRing 3
UCODE64_SELECTOR6User 64-bit codeRing 3
TSS_SELECTOR7Task State SegmentRing 0

GDT Initialization Process

flowchart TD
tss_percpu["TSS per-CPU instance"]
gdt_new["GdtStruct::new(tss)"]
gdt_table["table[1-8] segment descriptors"]
gdt_load["gdt.load()"]
lgdt["lgdt instruction"]
cs_set["CS::set_reg(KCODE64_SELECTOR)"]
load_tss_call["gdt.load_tss()"]
ltr["load_tss(TSS_SELECTOR)"]

cs_set --> load_tss_call
gdt_load --> lgdt
gdt_new --> gdt_table
gdt_table --> gdt_load
lgdt --> cs_set
load_tss_call --> ltr
tss_percpu --> gdt_new

GDT Setup and Loading Process

The TSS is configured as a per-CPU structure using the percpu crate, allowing each CPU core to maintain its own stack pointers and state information.

Sources: src/x86_64/gdt.rs(L104 - L111)  src/x86_64/gdt.rs(L41 - L55)  src/x86_64/gdt.rs(L73 - L90) 

Interrupt Descriptor Table (IDT) Initialization

The IDT setup configures interrupt and exception handlers for the x86_64 architecture, mapping 256 possible interrupt vectors to their respective handler functions.

IDT Structure and Handler Mapping

The IdtStruct populates all 256 interrupt vectors from an external trap_handler_table:

flowchart TD
trap_handler_table["trap_handler_table[256]"]
idt_new["IdtStruct::new()"]
entry_loop["for i in 0..256"]
set_handler["entries[i].set_handler_fn()"]
privilege_check["i == 0x3 || i == 0x80?"]
ring3["set_privilege_level(Ring3)"]
ring0["Default Ring0"]
idt_load["idt.load()"]
lidt["lidt instruction"]

entry_loop --> set_handler
idt_load --> lidt
idt_new --> entry_loop
privilege_check --> ring0
privilege_check --> ring3
ring0 --> idt_load
ring3 --> idt_load
set_handler --> privilege_check
trap_handler_table --> idt_new

IDT Initialization and Handler Assignment

Special Privilege Level Handling

Two interrupt vectors receive special Ring 3 (user mode) access privileges:

  • Vector 0x3: Breakpoint exception for user-space debugging
  • Vector 0x80: Legacy system call interface

Sources: src/x86_64/idt.rs(L22 - L46)  src/x86_64/idt.rs(L77 - L81) 

System Call Initialization (Optional)

When the uspace feature is enabled, init_syscall() configures Model-Specific Registers (MSRs) for fast system call handling using the syscall/sysret instructions.


Conditional System Call Initialization

The system call initialization is conditionally compiled and only executed when user space support is required.

Sources: src/x86_64/init.rs(L6 - L7)  src/x86_64/init.rs(L36 - L37) 

Task State Segment (TSS) Management

The TSS provides crucial stack switching capabilities for privilege level transitions, particularly important for user space support.

TSS Per-CPU Organization

flowchart TD
percpu_tss["#[percpu::def_percpu] TSS"]
cpu0_tss["CPU 0 TSS instance"]
cpu1_tss["CPU 1 TSS instance"]
cpun_tss["CPU N TSS instance"]
rsp0_0["privilege_stack_table[0]"]
rsp0_1["privilege_stack_table[0]"]
rsp0_n["privilege_stack_table[0]"]

cpu0_tss --> rsp0_0
cpu1_tss --> rsp0_1
cpun_tss --> rsp0_n
percpu_tss --> cpu0_tss
percpu_tss --> cpu1_tss
percpu_tss --> cpun_tss

Per-CPU TSS Instance Management

The TSS includes utility functions for managing the Ring 0 stack pointer (RSP0) used during privilege level transitions:

  • read_tss_rsp0(): Retrieves current RSP0 value
  • write_tss_rsp0(): Updates RSP0 for kernel stack switching

Sources: src/x86_64/gdt.rs(L10 - L12)  src/x86_64/gdt.rs(L114 - L129) 

Complete Initialization Function

The init_trap() function orchestrates the complete x86_64 system initialization sequence:

#![allow(unused)]
fn main() {
pub fn init_trap() {
    init_gdt();
    init_idt();
    #[cfg(feature = "uspace")]
    init_syscall();
}
}

This function must be called after init_percpu() to ensure proper per-CPU data structure availability. The initialization establishes a fully functional x86_64 CPU state capable of handling interrupts, exceptions, and system calls.

Sources: src/x86_64/init.rs(L33 - L38) 

AArch64 Architecture

Relevant source files

Purpose and Scope

This document provides a comprehensive overview of AArch64 (ARM64) architecture support within the axcpu library. The AArch64 implementation provides CPU abstraction capabilities including context management, trap handling, and system initialization for ARM 64-bit processors.

For detailed information about specific AArch64 subsystems, see AArch64 Context Management, AArch64 Trap and Exception Handling, and AArch64 System Initialization. For information about cross-architecture features that work with AArch64, see Cross-Architecture Features.

Module Organization

The AArch64 architecture support is organized into several focused modules that handle different aspects of CPU management and abstraction.

AArch64 Module Structure


The module structure follows a consistent pattern with other architectures in axcpu, providing specialized implementations for AArch64's unique characteristics while maintaining a unified interface.

Sources: src/aarch64/mod.rs(L1 - L13) 

Core Data Structures

The AArch64 implementation centers around three primary data structures that encapsulate different aspects of CPU state management.

Context Management Abstractions

flowchart TD
subgraph subGraph3["FpState Contents"]
    FP_VREGS["V0-V31Vector registers"]
    FP_FPCR["FPCRFloating-point control"]
    FP_FPSR["FPSRFloating-point status"]
end
subgraph subGraph2["TrapFrame Contents"]
    TF_REGS["R0-R30General purpose registers"]
    TF_SP_USER["SP_EL0User stack pointer"]
    TF_ELR["ELR_EL1Exception link register"]
    TF_SPSR["SPSR_EL1Saved program status"]
end
subgraph subGraph1["TaskContext Contents"]
    TC_REGS["R19-R29Callee-saved registers"]
    TC_SP["SPStack pointer"]
    TC_TPIDR["TPIDR_EL0Thread pointer"]
    TC_FP_STATE["FpStateFloating-point context"]
end
subgraph subGraph0["AArch64 Context Types"]
    TC["TaskContext"]
    TF["TrapFrame"]
    FP["FpState"]
end

FP --> FP_FPCR
FP --> FP_FPSR
FP --> FP_VREGS
TC --> TC_FP_STATE
TC --> TC_REGS
TC --> TC_SP
TC --> TC_TPIDR
TC_FP_STATE --> FP
TF --> TF_ELR
TF --> TF_REGS
TF --> TF_SPSR
TF --> TF_SP_USER

These structures provide the foundation for AArch64's context switching, exception handling, and floating-point state management capabilities.

Sources: src/aarch64/mod.rs(L12) 

Feature-Based Compilation

The AArch64 implementation uses conditional compilation to provide different capabilities based on the target environment and enabled features.

ConditionModulePurpose
target_os = "none"trapBare-metal trap handling
feature = "uspace"uspaceUser space support and system calls
Defaultcontext,asm,initCore functionality always available

Conditional Module Integration


This modular design allows the AArch64 implementation to be tailored for different deployment scenarios, from bare-metal embedded systems to full operating system kernels with user space support.

Sources: src/aarch64/mod.rs(L6 - L10) 

Architecture Integration

The AArch64 implementation integrates with the broader axcpu framework through standardized interfaces and common abstractions.

Cross-Architecture Compatibility

flowchart TD
subgraph subGraph2["External Dependencies"]
    AARCH64_CPU["aarch64-cpu crateArchitecture-specific types"]
    MEMORY_ADDR["memory_addrAddress abstractions"]
    PAGE_TABLE["page_table_entryPage table support"]
end
subgraph subGraph1["AArch64 Implementation"]
    AARCH64_CONTEXT["AArch64 ContextTaskContext, TrapFrame, FpState"]
    AARCH64_ASM["AArch64 AssemblyLow-level operations"]
    AARCH64_INIT["AArch64 InitSystem startup"]
    AARCH64_TRAP["AArch64 TrapException handlers"]
end
subgraph subGraph0["axcpu Framework"]
    CORE_TRAITS["Core TraitsCommon interfaces"]
    MEMORY_MGMT["Memory ManagementPage tables, MMU"]
    TRAP_FRAMEWORK["Trap FrameworkUnified exception handling"]
end

AARCH64_ASM --> AARCH64_CPU
AARCH64_CONTEXT --> AARCH64_CPU
AARCH64_INIT --> MEMORY_ADDR
AARCH64_INIT --> PAGE_TABLE
CORE_TRAITS --> AARCH64_CONTEXT
MEMORY_MGMT --> AARCH64_INIT
TRAP_FRAMEWORK --> AARCH64_TRAP

The AArch64 implementation follows the established patterns used by other architectures in axcpu, ensuring consistent behavior and interfaces across different processor architectures while leveraging AArch64-specific features and optimizations.

Sources: src/aarch64/mod.rs(L1 - L13) 

AArch64 Context Management

Relevant source files

This document covers CPU context management for the AArch64 architecture within the axcpu library. It details the data structures, mechanisms, and assembly implementations used for task switching, exception handling, and state preservation on ARM64 systems.

The scope includes the TaskContext structure for task switching, TrapFrame for exception handling, and FpState for floating-point operations. For information about how these contexts are used during exception processing, see AArch64 Trap and Exception Handling. For system initialization and setup procedures, see AArch64 System Initialization.

Context Data Structures

The AArch64 context management revolves around three primary data structures, each serving different aspects of CPU state management.

Context Structure Overview

flowchart TD
subgraph subGraph3["FpState Fields"]
    FP_REGS["regs[32]: u128V0-V31 SIMD Registers"]
    FP_CTRL["fpcr/fpsr: u32Control/Status Registers"]
end
subgraph subGraph2["TrapFrame Fields"]
    TF_REGS["r[31]: u64General Purpose Registers"]
    TF_USP["usp: u64User Stack Pointer"]
    TF_ELR["elr: u64Exception Link Register"]
    TF_SPSR["spsr: u64Saved Process Status"]
end
subgraph subGraph1["TaskContext Fields"]
    TC_SP["sp: u64Stack Pointer"]
    TC_TPIDR["tpidr_el0: u64Thread Local Storage"]
    TC_REGS["r19-r29, lr: u64Callee-saved Registers"]
    TC_TTBR["ttbr0_el1: PhysAddrPage Table Root"]
    TC_FP["fp_state: FpStateFP/SIMD State"]
end
subgraph subGraph0["AArch64 Context Management"]
    TaskContext["TaskContextTask Switching Context"]
    TrapFrame["TrapFrameException Context"]
    FpState["FpStateFP/SIMD Context"]
end

FpState --> FP_CTRL
FpState --> FP_REGS
TC_FP --> FpState
TaskContext --> TC_FP
TaskContext --> TC_REGS
TaskContext --> TC_SP
TaskContext --> TC_TPIDR
TaskContext --> TC_TTBR
TrapFrame --> TF_ELR
TrapFrame --> TF_REGS
TrapFrame --> TF_SPSR
TrapFrame --> TF_USP

Sources: src/aarch64/context.rs(L8 - L124) 

TaskContext Structure

The TaskContext structure maintains the minimal set of registers needed for task switching, focusing on callee-saved registers and system state.

FieldTypePurpose
spu64Stack pointer register
tpidr_el0u64Thread pointer for TLS
r19-r29u64Callee-saved general registers
lru64Link register (r30)
ttbr0_el1PhysAddrUser page table root (uspace feature)
fp_stateFpStateFloating-point state (fp-simd feature)

The structure is conditionally compiled based on enabled features, with ttbr0_el1 only included with the uspace feature and fp_state only with the fp-simd feature.

Sources: src/aarch64/context.rs(L104 - L124) 

TrapFrame Structure

The TrapFrame captures the complete CPU state during exceptions and system calls, preserving all general-purpose registers and processor state.

flowchart TD
subgraph subGraph0["TrapFrame Memory Layout"]
    R0_30["r[0-30]General Registers248 bytes"]
    USP["uspUser Stack Pointer8 bytes"]
    ELR["elrException Link Register8 bytes"]
    SPSR["spsrSaved Process Status8 bytes"]
end

ELR --> SPSR
USP --> ELR

The TrapFrame provides accessor methods for system call arguments through arg0() through arg5() methods, which map to registers r[0] through r[5] respectively.

Sources: src/aarch64/context.rs(L8 - L63) 

FpState Structure

The FpState structure manages floating-point and SIMD register state, with 16-byte alignment required for proper SIMD operations.

flowchart TD
subgraph Operations["Operations"]
    SAVE["save()CPU → Memory"]
    RESTORE["restore()Memory → CPU"]
end
subgraph subGraph0["FpState Structure"]
    REGS["regs[32]: u128V0-V31 SIMD/FP Registers512 bytes total"]
    FPCR["fpcr: u32Floating-Point Control Register"]
    FPSR["fpsr: u32Floating-Point Status Register"]
end

FPCR --> SAVE
FPSR --> SAVE
REGS --> SAVE
RESTORE --> FPCR
RESTORE --> FPSR
RESTORE --> REGS
SAVE --> RESTORE

Sources: src/aarch64/context.rs(L66 - L88) 

Context Switching Mechanism

Task switching on AArch64 involves saving the current task's callee-saved registers and restoring the next task's context, with optional handling of floating-point state and page tables.

Context Switch Flow

sequenceDiagram
    participant CurrentTaskContext as "Current TaskContext"
    participant NextTaskContext as "Next TaskContext"
    participant AArch64CPU as "AArch64 CPU"
    participant MemoryManagementUnit as "Memory Management Unit"

    CurrentTaskContext ->> CurrentTaskContext: Check fp-simd feature
    alt fp-simd enabled
        CurrentTaskContext ->> AArch64CPU: Save FP/SIMD state
        NextTaskContext ->> AArch64CPU: Restore FP/SIMD state
    end
    CurrentTaskContext ->> CurrentTaskContext: Check uspace feature
    alt uspace enabled and ttbr0_el1 differs
        NextTaskContext ->> MemoryManagementUnit: Update page table root
        MemoryManagementUnit ->> MemoryManagementUnit: Flush TLB
    end
    CurrentTaskContext ->> AArch64CPU: context_switch(current, next)
    Note over AArch64CPU: Assembly context switch
    AArch64CPU ->> CurrentTaskContext: Save callee-saved registers
    AArch64CPU ->> AArch64CPU: Restore next task registers
    AArch64CPU ->> NextTaskContext: Load context to CPU

The switch_to method orchestrates the complete context switch:

  1. FP/SIMD State: If fp-simd feature is enabled, saves current FP state and restores next task's FP state
  2. Page Table Switching: If uspace feature is enabled and page tables differ, updates ttbr0_el1 and flushes TLB
  3. Register Context: Calls assembly context_switch function to handle callee-saved registers

Sources: src/aarch64/context.rs(L160 - L172) 

Assembly Context Switch Implementation

The low-level context switching is implemented in naked assembly within the context_switch function:

flowchart TD
subgraph subGraph0["context_switch Assembly Flow"]
    SAVE_START["Save Current Contextstp x29,x30 [x0,12*8]"]
    SAVE_REGS["Save Callee-Savedr19-r28 to memory"]
    SAVE_SP_TLS["Save SP and TLSmov x19,sp; mrs x20,tpidr_el0"]
    RESTORE_SP_TLS["Restore SP and TLSmov sp,x19; msr tpidr_el0,x20"]
    RESTORE_REGS["Restore Callee-Savedr19-r28 from memory"]
    RESTORE_END["Restore Frameldp x29,x30 [x1,12*8]"]
    RET["Returnret"]
end

RESTORE_END --> RET
RESTORE_REGS --> RESTORE_END
RESTORE_SP_TLS --> RESTORE_REGS
SAVE_REGS --> SAVE_SP_TLS
SAVE_SP_TLS --> RESTORE_SP_TLS
SAVE_START --> SAVE_REGS

The assembly implementation uses paired load/store instructions (stp/ldp) for efficiency, handling registers in pairs and preserving the AArch64 calling convention.

Sources: src/aarch64/context.rs(L175 - L203) 

Feature-Conditional Context Management

The AArch64 context management includes several optional features that extend the base functionality.

Feature Integration Overview

flowchart TD
subgraph subGraph2["Feature Operations"]
    SET_PT["set_page_table_root()Update ttbr0_el1"]
    SWITCH_PT["Page table switchingin switch_to()"]
    FP_SAVE["fp_state.save()CPU → Memory"]
    FP_RESTORE["fp_state.restore()Memory → CPU"]
    TLS_INIT["TLS initializationin init()"]
end
subgraph subGraph1["Optional Features"]
    USPACE["uspace Featurettbr0_el1: PhysAddr"]
    FPSIMD["fp-simd Featurefp_state: FpState"]
    TLS["tls Supporttpidr_el0 management"]
end
subgraph subGraph0["Base Context"]
    BASE["TaskContext Basesp, tpidr_el0, r19-r29, lr"]
end

BASE --> FPSIMD
BASE --> TLS
BASE --> USPACE
FPSIMD --> FP_RESTORE
FPSIMD --> FP_SAVE
TLS --> TLS_INIT
USPACE --> SET_PT
USPACE --> SWITCH_PT

User Space Support

When the uspace feature is enabled, TaskContext includes the ttbr0_el1 field for managing user-space page tables. The set_page_table_root method allows updating the page table root, and context switching automatically handles page table updates and TLB flushes when switching between tasks with different address spaces.

Floating-Point State Management

The fp-simd feature enables comprehensive floating-point and SIMD state management through the FpState structure. The assembly implementations fpstate_save and fpstate_restore handle all 32 vector registers (V0-V31) plus control registers using quad-word load/store instructions.

Sources: src/aarch64/context.rs(L77 - L88)  src/aarch64/context.rs(L120 - L123)  src/aarch64/context.rs(L147 - L154)  src/aarch64/context.rs(L206 - L267) 

Task Initialization

New tasks are initialized through the TaskContext::init method, which sets up the minimal execution environment:

flowchart TD
subgraph subGraph1["TaskContext Setup"]
    SET_SP["self.sp = kstack_top"]
    SET_LR["self.lr = entry"]
    SET_TLS["self.tpidr_el0 = tls_area"]
end
subgraph subGraph0["Task Initialization Parameters"]
    ENTRY["entry: usizeTask entry point"]
    KSTACK["kstack_top: VirtAddrKernel stack top"]
    TLS_AREA["tls_area: VirtAddrThread-local storage"]
end

ENTRY --> SET_LR
KSTACK --> SET_SP
TLS_AREA --> SET_TLS

The initialization sets the stack pointer to the kernel stack top, the link register to the task entry point, and the thread pointer for thread-local storage support.

Sources: src/aarch64/context.rs(L140 - L145) 

AArch64 Trap and Exception Handling

Relevant source files

This document covers the AArch64 (ARM64) trap and exception handling implementation in the axcpu library. It details the exception classification system, handler dispatch mechanism, and integration with the cross-architecture trap handling framework. For AArch64 context management including TrapFrame structure, see AArch64 Context Management. For system initialization and exception vector setup, see AArch64 System Initialization.

Exception Classification System

The AArch64 trap handler uses a two-dimensional classification system to categorize exceptions based on their type and source context.

Exception Types

The system defines four primary exception types in the TrapKind enumeration:

Exception TypeValueDescription
Synchronous0Synchronous exceptions requiring immediate handling
Irq1Standard interrupt requests
Fiq2Fast interrupt requests (higher priority)
SError3System error exceptions

Exception Sources

The TrapSource enumeration categorizes the execution context from which exceptions originate:

Source TypeValueDescription
CurrentSpEl00Current stack pointer, Exception Level 0
CurrentSpElx1Current stack pointer, Exception Level 1+
LowerAArch642Lower exception level, AArch64 state
LowerAArch323Lower exception level, AArch32 state

Sources: src/aarch64/trap.rs(L9 - L27) 

Exception Handler Architecture

Exception Handler Dispatch Flow

flowchart TD
ASM_ENTRY["trap.S Assembly Entry Points"]
DISPATCH["Exception Dispatch Logic"]
SYNC_CHECK["Exception Type?"]
SYNC_HANDLER["handle_sync_exception()"]
IRQ_HANDLER["handle_irq_exception()"]
INVALID_HANDLER["invalid_exception()"]
ESR_READ["Read ESR_EL1 Register"]
EC_DECODE["Decode Exception Class"]
SYSCALL["handle_syscall()"]
INSTR_ABORT["handle_instruction_abort()"]
DATA_ABORT["handle_data_abort()"]
BRK_HANDLER["Debug Break Handler"]
PANIC["Panic with Details"]
TRAP_MACRO["handle_trap!(IRQ, 0)"]
PAGE_FAULT_CHECK["Translation/Permission Fault?"]
PAGE_FAULT_CHECK2["Translation/Permission Fault?"]
PAGE_FAULT_HANDLER["handle_trap!(PAGE_FAULT, ...)"]
ABORT_PANIC["Panic with Fault Details"]

ASM_ENTRY --> DISPATCH
DATA_ABORT --> PAGE_FAULT_CHECK2
DISPATCH --> SYNC_CHECK
EC_DECODE --> BRK_HANDLER
EC_DECODE --> DATA_ABORT
EC_DECODE --> INSTR_ABORT
EC_DECODE --> PANIC
EC_DECODE --> SYSCALL
ESR_READ --> EC_DECODE
INSTR_ABORT --> PAGE_FAULT_CHECK
IRQ_HANDLER --> TRAP_MACRO
PAGE_FAULT_CHECK --> ABORT_PANIC
PAGE_FAULT_CHECK --> PAGE_FAULT_HANDLER
PAGE_FAULT_CHECK2 --> ABORT_PANIC
PAGE_FAULT_CHECK2 --> PAGE_FAULT_HANDLER
SYNC_CHECK --> INVALID_HANDLER
SYNC_CHECK --> IRQ_HANDLER
SYNC_CHECK --> SYNC_HANDLER
SYNC_HANDLER --> ESR_READ

Sources: src/aarch64/trap.rs(L29 - L121) 

Core Handler Functions

The trap handling system implements several specialized handler functions:

Synchronous Exception Handler

The handle_sync_exception function serves as the primary dispatcher for synchronous exceptions, using the ESR_EL1 register to determine the specific exception class:

flowchart TD
ESR_READ["ESR_EL1.extract()"]
EC_MATCH["Match Exception Class"]
SYSCALL_IMPL["tf.r[0] = handle_syscall(tf, tf.r[8])"]
INSTR_LOWER["handle_instruction_abort(tf, iss, true)"]
INSTR_CURRENT["handle_instruction_abort(tf, iss, false)"]
DATA_LOWER["handle_data_abort(tf, iss, true)"]
DATA_CURRENT["handle_data_abort(tf, iss, false)"]
BRK_ADVANCE["tf.elr += 4"]
UNHANDLED_PANIC["panic!(unhandled exception)"]

EC_MATCH --> BRK_ADVANCE
EC_MATCH --> DATA_CURRENT
EC_MATCH --> DATA_LOWER
EC_MATCH --> INSTR_CURRENT
EC_MATCH --> INSTR_LOWER
EC_MATCH --> SYSCALL_IMPL
EC_MATCH --> UNHANDLED_PANIC
ESR_READ --> EC_MATCH

Sources: src/aarch64/trap.rs(L94 - L121) 

IRQ Exception Handler

The handle_irq_exception function provides a simple interface to the cross-architecture interrupt handling system through the handle_trap! macro:

Sources: src/aarch64/trap.rs(L37 - L40) 

Invalid Exception Handler

The invalid_exception function handles unexpected exception types or sources by panicking with detailed context information including the TrapFrame, exception kind, and source classification:

Sources: src/aarch64/trap.rs(L29 - L35) 

Memory Fault Handling

The AArch64 implementation provides specialized handling for instruction and data aborts, which correspond to page faults in other architectures.

Page Fault Classification

Both instruction and data aborts use a common pattern for classifying page faults:

Fault TypeISS BitsHandledDescription
Translation Fault0b0100YesMissing page table entry
Permission Fault0b1100YesAccess permission violation
Other FaultsVariousNoUnhandled fault types

Access Flag Generation

The system generates PageFaultFlags based on the fault characteristics:

Instruction Abort Flags

  • Always includes PageFaultFlags::EXECUTE
  • Adds PageFaultFlags::USER for EL0 (user) faults

Data Abort Flags

  • Uses ESR_EL1 ISS WnR bit (Write not Read) to determine access type
  • Considers CM bit (Cache Maintenance) to exclude cache operations from write classification
  • Adds PageFaultFlags::USER for EL0 (user) faults
flowchart TD
DATA_ABORT["Data Abort Exception"]
ISS_DECODE["Decode ISS Bits from ESR_EL1"]
WNR_CHECK["WnR Bit Set?"]
CM_CHECK["CM Bit Set?"]
EL_CHECK["Exception from EL0?"]
WRITE_FLAG["PageFaultFlags::WRITE"]
READ_FLAG["PageFaultFlags::read"]
OVERRIDE_READ["Override to READ (cache maintenance)"]
USER_FLAG["Add PageFaultFlags::USER"]
ACCESS_FLAGS["Combined Access Flags"]
read_FLAG["PageFaultFlags::read"]
DFSC_CHECK["DFSC bits = 0100 or 1100?"]
PAGE_FAULT_TRAP["handle_trap!(PAGE_FAULT, vaddr, flags, is_user)"]
UNHANDLED_PANIC["panic!(unhandled data abort)"]

ACCESS_FLAGS --> DFSC_CHECK
CM_CHECK --> OVERRIDE_READ
DATA_ABORT --> ISS_DECODE
DFSC_CHECK --> PAGE_FAULT_TRAP
DFSC_CHECK --> UNHANDLED_PANIC
EL_CHECK --> USER_FLAG
ISS_DECODE --> CM_CHECK
ISS_DECODE --> EL_CHECK
ISS_DECODE --> WNR_CHECK
OVERRIDE_READ --> ACCESS_FLAGS
USER_FLAG --> ACCESS_FLAGS
WNR_CHECK --> READ_FLAG
WNR_CHECK --> WRITE_FLAG
WRITE_FLAG --> ACCESS_FLAGS
read_FLAG --> ACCESS_FLAGS

Sources: src/aarch64/trap.rs(L42 - L92) 

System Call Integration

When the uspace feature is enabled, the trap handler supports AArch64 system calls through the SVC (Supervisor Call) instruction.

System Call Convention

  • System call number: Passed in register x8 (tf.r[8])
  • Return value: Stored in register x0 (tf.r[0])
  • Handler: Delegates to cross-architecture crate::trap::handle_syscall

Sources: src/aarch64/trap.rs(L99 - L102) 

Integration with Cross-Architecture Framework

The AArch64 trap handler integrates with the broader axcpu trap handling framework through several mechanisms:

Trap Handler Macros

The implementation uses the handle_trap! macro for consistent integration:

  • handle_trap!(IRQ, 0) for interrupt processing
  • handle_trap!(PAGE_FAULT, vaddr, access_flags, is_user) for memory faults

Assembly Integration

The Rust handlers are called from assembly code included via:

core::arch::global_asm!(include_str!("trap.S"));

Register Access

The system uses the aarch64_cpu crate to access AArch64 system registers:

  • ESR_EL1: Exception Syndrome Register for fault classification
  • FAR_EL1: Fault Address Register for memory fault addresses

Sources: src/aarch64/trap.rs(L1 - L7) 

AArch64 System Initialization

Relevant source files

This document covers the AArch64 system initialization procedures provided by the axcpu library. The initialization process includes exception level transitions, Memory Management Unit (MMU) configuration, and trap handler setup. These functions are primarily used during system bootstrap to establish a proper execution environment at EL1.

For detailed information about AArch64 trap and exception handling mechanisms, see AArch64 Trap and Exception Handling. For AArch64 context management including TaskContext and TrapFrame structures, see AArch64 Context Management.

Exception Level Transition

AArch64 systems support multiple exception levels (EL0-EL3), with EL3 being the highest privilege level and EL0 being user space. The switch_to_el1() function handles the transition from higher exception levels (EL2 or EL3) down to EL1, which is the typical kernel execution level.

Exception Level Transition Flow

flowchart TD
START["switch_to_el1()"]
CHECK_EL["CurrentEL.read(CurrentEL::EL)"]
EL3_CHECK["current_el == 3?"]
EL2_PLUS["current_el >= 2?"]
EL3_CONFIG["Configure EL3→EL2 transitionSCR_EL3.write()SPSR_EL3.write()ELR_EL3.set()"]
EL2_CONFIG["Configure EL2→EL1 transitionCNTHCTL_EL2.modify()CNTVOFF_EL2.set()HCR_EL2.write()SPSR_EL2.write()SP_EL1.set()ELR_EL2.set()"]
ERET["aarch64_cpu::asm::eret()"]
DONE["Return (already in EL1)"]

CHECK_EL --> EL2_PLUS
EL2_CONFIG --> ERET
EL2_PLUS --> DONE
EL2_PLUS --> EL3_CHECK
EL3_CHECK --> EL2_CONFIG
EL3_CHECK --> EL3_CONFIG
EL3_CONFIG --> EL2_CONFIG
ERET --> DONE
START --> CHECK_EL

Sources: src/aarch64/init.rs(L15 - L52) 

The transition process configures multiple system registers:

RegisterPurposeConfiguration
SCR_EL3Secure Configuration RegisterNon-secure world, HVC enabled, AArch64
SPSR_EL3/EL2Saved Program Status RegisterEL1h mode, interrupts masked
ELR_EL3/EL2Exception Link RegisterReturn address from LR
HCR_EL2Hypervisor Configuration RegisterEL1 in AArch64 mode
CNTHCTL_EL2Counter-timer Hypervisor ControlEnable EL1 timer access

MMU Initialization

The Memory Management Unit initialization is handled by the init_mmu() function, which configures address translation and enables caching. This function sets up a dual page table configuration using both TTBR0_EL1 and TTBR1_EL1.

MMU Configuration Sequence

flowchart TD
INIT_MMU["init_mmu(root_paddr)"]
SET_MAIR["MAIR_EL1.set(MemAttr::MAIR_VALUE)"]
CONFIG_TCR["Configure TCR_EL1- Page size: 4KB- VA size: 48 bits- PA size: 48 bits"]
SET_TTBR["Set page table baseTTBR0_EL1.set(root_paddr)TTBR1_EL1.set(root_paddr)"]
FLUSH_TLB["crate::asm::flush_tlb(None)"]
ENABLE_MMU["SCTLR_EL1.modify()Enable MMU + I-cache + D-cache"]
ISB["barrier::isb(barrier::SY)"]

CONFIG_TCR --> ISB
ENABLE_MMU --> ISB
FLUSH_TLB --> ENABLE_MMU
INIT_MMU --> SET_MAIR
ISB --> SET_TTBR
SET_MAIR --> CONFIG_TCR
SET_TTBR --> FLUSH_TLB

Sources: src/aarch64/init.rs(L54 - L95) 

Memory Attribute Configuration

The MMU initialization configures memory attributes through several system registers:

RegisterConfigurationPurpose
MAIR_EL1Memory attribute encodingDefines memory types (Normal, Device, etc.)
TCR_EL1Translation controlPage size, address ranges, cacheability
TTBR0_EL1Translation table base 0User space page table root
TTBR1_EL1Translation table base 1Kernel space page table root
SCTLR_EL1System controlMMU enable, cache enable

The function configures both translation table base registers to use the same physical address, enabling a unified address space configuration at initialization time.

Trap Handler Initialization

The init_trap() function sets up the exception handling infrastructure by configuring the exception vector table base address and initializing user space memory protection.

Trap Initialization Components

flowchart TD
subgraph subGraph1["User Space Protection"]
    TTBR0_ZERO["TTBR0_EL1 = 0Blocks low address access"]
    USER_ISOLATION["User space isolationPrevents unauthorized access"]
end
subgraph subGraph0["Exception Vector Setup"]
    VBAR_EL1["VBAR_EL1 registerPoints to exception_vector_base"]
    VECTOR_TABLE["Exception vector tableSynchronous, IRQ, FIQ, SError"]
end
INIT_TRAP["init_trap()"]
GET_VECTOR["extern exception_vector_base()"]
SET_VBAR["crate::asm::write_exception_vector_base()"]
CLEAR_TTBR0["crate::asm::write_user_page_table(0)"]

CLEAR_TTBR0 --> TTBR0_ZERO
GET_VECTOR --> SET_VBAR
INIT_TRAP --> GET_VECTOR
SET_VBAR --> CLEAR_TTBR0
SET_VBAR --> VBAR_EL1
TTBR0_ZERO --> USER_ISOLATION
VBAR_EL1 --> VECTOR_TABLE

Sources: src/aarch64/init.rs(L97 - L109) 

The function performs two critical operations:

  1. Vector Base Setup: Points VBAR_EL1 to the exception_vector_base symbol, which contains the exception handlers
  2. User Space Protection: Sets TTBR0_EL1 to 0, effectively disabling user space address translation until a proper user page table is loaded

System Initialization Sequence

The complete AArch64 system initialization follows a specific sequence to ensure proper system state transitions and hardware configuration.

Complete Initialization Flow

flowchart TD
subgraph subGraph2["Exception Handling Setup"]
    SET_VBAR_OP["Set VBAR_EL1 to exception_vector_base"]
    PROTECT_USER["Zero TTBR0_EL1 for protection"]
end
subgraph subGraph1["Memory Management Setup"]
    MMU_REGS["Configure MMU registersMAIR_EL1, TCR_EL1, TTBR0/1_EL1"]
    ENABLE_MMU_OP["Enable SCTLR_EL1.M + C + I"]
end
subgraph subGraph0["Exception Level Transition"]
    EL_REGS["Configure EL transition registersSCR_EL3, SPSR_EL3, HCR_EL2, etc."]
    ERET_OP["Execute ERET instruction"]
end
BOOT["System Boot(EL2/EL3)"]
SWITCH["switch_to_el1()"]
EL1_MODE["Running in EL1"]
INIT_MMU_STEP["init_mmu(root_paddr)"]
MMU_ENABLED["MMU + Caches Enabled"]
INIT_TRAP_STEP["init_trap()"]
SYSTEM_READY["System Ready"]

BOOT --> SWITCH
EL1_MODE --> INIT_MMU_STEP
EL_REGS --> ERET_OP
ENABLE_MMU_OP --> MMU_ENABLED
ERET_OP --> EL1_MODE
INIT_MMU_STEP --> MMU_REGS
INIT_TRAP_STEP --> SET_VBAR_OP
MMU_ENABLED --> INIT_TRAP_STEP
MMU_REGS --> ENABLE_MMU_OP
PROTECT_USER --> SYSTEM_READY
SET_VBAR_OP --> PROTECT_USER
SWITCH --> EL_REGS

Sources: src/aarch64/init.rs(L1 - L110) 

Safety Considerations

All initialization functions are marked as unsafe due to their privileged operations:

  • switch_to_el1(): Changes CPU execution mode and privilege level
  • init_mmu(): Modifies address translation configuration, which can affect memory access behavior
  • Assembly operations: Direct register manipulation and barrier instructions require careful sequencing

The initialization sequence must be performed in the correct order, as MMU configuration depends on being in the proper exception level, and trap handling setup assumes MMU functionality is available.

RISC-V Architecture

Relevant source files

Purpose and Scope

This document covers the RISC-V architecture support within the axcpu library, focusing on the module organization, key data structures, and integration patterns specific to RISC-V processors. The RISC-V implementation follows the common multi-architecture abstraction pattern established by axcpu, providing CPU context management, trap handling, and system initialization capabilities.

For detailed coverage of RISC-V context management operations, see RISC-V Context Management. For RISC-V trap and exception handling mechanisms, see RISC-V Trap and Exception Handling. For RISC-V system initialization procedures, see RISC-V System Initialization.

Module Organization

The RISC-V architecture support is organized under the src/riscv/ directory, following the standard architecture module pattern used throughout axcpu. The main module file orchestrates the various components and provides the public API.

RISC-V Module Structure


The mod.rs file serves as the central orchestrator, defining the module hierarchy and exporting the primary data structures. The macro system provides low-level assembly abstractions shared across the RISC-V modules.

Sources: src/riscv/mod.rs(L1 - L14) 

Core Data Structures

The RISC-V architecture implementation defines three fundamental data structures that encapsulate different aspects of CPU state management: GeneralRegisters, TaskContext, and TrapFrame.

RISC-V Context Management Hierarchy

flowchart TD
subgraph FEATURES["Optional Features"]
    FP["fp-simdFloating point state"]
    TLS["tlsThread-local storage"]
    USPACE["uspaceUser space transitions"]
end
subgraph OPERATIONS["Context Operations"]
    SWITCH["context_switch()Assembly routine"]
    SAVE["Save context state"]
    RESTORE["Restore context state"]
    TRAP_ENTRY["Trap entry/exit"]
end
subgraph CONTEXT_MOD["context.rs Module"]
    GR["GeneralRegistersBasic register set"]
    TC["TaskContextTask switching state"]
    TF["TrapFrameComplete trap state"]
end

GR --> TC
GR --> TF
SWITCH --> RESTORE
SWITCH --> SAVE
TC --> SWITCH
TC --> TLS
TC --> USPACE
TF --> FP
TF --> TRAP_ENTRY
TF --> USPACE

The GeneralRegisters structure provides the foundation for both task switching and trap handling contexts. The TaskContext builds upon this for efficient task switching, while TrapFrame extends it with additional state needed for complete exception handling.

Sources: src/riscv/mod.rs(L13) 

Architecture Integration

The RISC-V implementation integrates with the broader axcpu framework through standardized interfaces and conditional compilation features. This allows RISC-V-specific optimizations while maintaining compatibility with the cross-architecture abstractions.

RISC-V Feature Integration

flowchart TD
subgraph MODULES["Module Implementation"]
    USPACE_M["uspace.rsUser transitionsKernel-user boundary"]
    INIT_M["init.rsSystem setupTrap vectorsMMU configuration"]
    ASM_M["asm.rsAssembly operationsRegister accessControl flow"]
end
subgraph OPTIONAL["Optional Features"]
    USPACE_F["uspace FeatureUser space supportSystem callsSATP management"]
    FP_F["fp-simd FeatureFuture FP supportVector extensions"]
    TLS_F["tls FeatureTP register managementThread-local storage"]
end
subgraph CORE["Core RISC-V Support"]
    BASE["Base ImplementationTaskContextTrapFrameBasic Operations"]
end

BASE --> ASM_M
BASE --> FP_F
BASE --> INIT_M
BASE --> TLS_F
BASE --> USPACE_F
USPACE_F --> USPACE_M

The modular design allows RISC-V support to be configured with only the necessary features for a given use case, while providing extension points for future capabilities like floating-point and vector processing.

Sources: src/riscv/mod.rs(L10 - L11) 

System Initialization and Runtime

The RISC-V architecture support provides comprehensive system initialization capabilities through the init module, complemented by low-level assembly operations in the asm module. These components work together to establish the runtime environment and provide the foundation for higher-level abstractions.

The trap handling system integrates with the RISC-V privilege architecture, managing transitions between different privilege levels and handling various exception types. This forms the basis for kernel-user space transitions and system call implementations when the uspace feature is enabled.

Sources: src/riscv/mod.rs(L7 - L8) 

RISC-V Context Management

Relevant source files

Purpose and Scope

This document covers the RISC-V architecture's CPU context management implementation within the axcpu library. It focuses on the data structures and mechanisms used to manage CPU state during task switching and exception handling, including register preservation, memory management integration, and thread-local storage support.

For RISC-V trap and exception handling mechanisms, see RISC-V Trap and Exception Handling. For system initialization procedures, see RISC-V System Initialization.

Core Context Structures

The RISC-V context management is built around three primary data structures that represent different levels of CPU state preservation.

Context Structure Hierarchy

flowchart TD
subgraph subGraph2["RISC-V Hardware Registers"]
    SEPC["SEPCSupervisor Exception PC"]
    SSTATUS["SSTATUSSupervisor Status"]
    SATP["SATPAddress Translation"]
    TP_REG["TPThread Pointer"]
end
subgraph subGraph1["Usage Contexts"]
    EXCEPTION["Exception/Trap HandlingFull state preservation"]
    SWITCHING["Task SwitchingMinimal state preservation"]
    SYSCALL["System Call InterfaceArgument extraction"]
end
subgraph subGraph0["RISC-V Context Management"]
    GR["GeneralRegistersAll 31 GP registersra, sp, gp, tp, t0-t6, s0-s11, a0-a7"]
    TF["TrapFrameComplete exception stateregs + sepc + sstatus"]
    TC["TaskContextMinimal switching stateCallee-saved + sp + tp + satp"]
end

GR --> TF
TC --> SATP
TC --> SWITCHING
TC --> TP_REG
TF --> EXCEPTION
TF --> SEPC
TF --> SSTATUS
TF --> SYSCALL

Sources: src/riscv/context.rs(L8 - L123) 

GeneralRegisters Structure

The GeneralRegisters structure represents the complete RISC-V general-purpose register set, containing all 31 registers as defined by the RISC-V specification.

Register GroupRegistersPurpose
Return AddressraFunction return address
Stack PointerspCurrent stack pointer
Global PointergpGlobal data pointer (user traps only)
Thread PointertpThread-local storage (user traps only)
Temporariest0-t6Temporary registers
Saved Registerss0-s11Callee-saved registers
Argumentsa0-a7Function arguments and return values

Sources: src/riscv/context.rs(L8 - L40) 

TrapFrame Structure

The TrapFrame extends GeneralRegisters with supervisor-mode control and status registers required for complete exception context preservation.

flowchart TD
subgraph subGraph1["System Call Interface"]
    ARG0["arg0() -> a0"]
    ARG1["arg1() -> a1"]
    ARG2["arg2() -> a2"]
    ARG3["arg3() -> a3"]
    ARG4["arg4() -> a4"]
    ARG5["arg5() -> a5"]
end
subgraph subGraph0["TrapFrame Layout"]
    REGS["regs: GeneralRegistersComplete register file"]
    SEPC["sepc: usizeException return address"]
    SSTATUS["sstatus: usizeProcessor status"]
end

REGS --> ARG0
REGS --> ARG1
REGS --> ARG2
REGS --> ARG3
REGS --> ARG4
REGS --> ARG5

The TrapFrame provides accessor methods for system call arguments through the arg0() through arg5() methods, which extract values from the appropriate argument registers (a0-a5).

Sources: src/riscv/context.rs(L44 - L84) 

TaskContext Structure

The TaskContext represents the minimal CPU state required for task switching, containing only callee-saved registers and system-specific state.

FieldPurposeAvailability
raReturn address for context switchAlways
spStack pointerAlways
s0-s11Callee-saved registersAlways
tpThread pointer for TLSAlways
satpPage table rootuspacefeature only

Sources: src/riscv/context.rs(L100 - L123) 

Context Switching Mechanism

The RISC-V context switching implementation uses a combination of Rust methods and naked assembly functions to efficiently preserve and restore task state.

Context Switch Flow

flowchart TD
subgraph subGraph1["context_switch() Assembly"]
    SAVE_REGS["Save callee-saved registersSTR ra,sp,s0-s11 -> current"]
    RESTORE_REGS["Restore callee-saved registersLDR s11-s0,sp,ra <- next"]
    RETURN["ret"]
end
subgraph TaskContext::switch_to()["TaskContext::switch_to()"]
    START["switch_to(&self, next_ctx: &Self)"]
    TLS_CHECK["TLS featureenabled?"]
    TLS_SAVE["Save current TPLoad next TP"]
    USPACE_CHECK["USPACE featureenabled?"]
    SATP_CMP["Current SATP !=Next SATP?"]
    SATP_SWITCH["Switch page tableFlush TLB"]
    ASM_SWITCH["context_switch(self, next_ctx)"]
end

ASM_SWITCH --> SAVE_REGS
RESTORE_REGS --> RETURN
SATP_CMP --> ASM_SWITCH
SATP_CMP --> SATP_SWITCH
SATP_SWITCH --> ASM_SWITCH
SAVE_REGS --> RESTORE_REGS
START --> TLS_CHECK
TLS_CHECK --> TLS_SAVE
TLS_CHECK --> USPACE_CHECK
TLS_SAVE --> USPACE_CHECK
USPACE_CHECK --> ASM_SWITCH
USPACE_CHECK --> SATP_CMP

Sources: src/riscv/context.rs(L162 - L177)  src/riscv/context.rs(L181 - L219) 

Assembly Context Switch Implementation

The context_switch function is implemented as a naked assembly function that directly manipulates the task context structures:

  • Save Phase: Uses STR macro to store callee-saved registers from CPU to current task's context
  • Restore Phase: Uses LDR macro to load callee-saved registers from next task's context to CPU
  • Register Order: Processes registers in reverse order during restore to maintain stack discipline

The assembly implementation assumes that the STR and LDR macros are defined to handle register storage and loading operations specific to the RISC-V architecture.

Sources: src/riscv/context.rs(L181 - L219) 

Memory Management Integration

The RISC-V context management integrates closely with the memory management system through the satp (Supervisor Address Translation and Protection) register.

Page Table Root Management

flowchart TD
subgraph subGraph1["SATP Management"]
    CURRENT_SATP["Current Task SATP"]
    NEXT_SATP["Next Task SATP"]
    COMPARE["SATP valuesdifferent?"]
    WRITE_SATP["write_user_page_table()"]
    FLUSH_TLB["flush_tlb(None)"]
end
subgraph subGraph0["User Space Context Management"]
    NEW_TASK["TaskContext::new()"]
    KERNEL_SATP["read_kernel_page_table()"]
    SET_ROOT["set_page_table_root(satp)"]
    SWITCH["Context Switch"]
end

COMPARE --> SWITCH
COMPARE --> WRITE_SATP
CURRENT_SATP --> COMPARE
NEW_TASK --> KERNEL_SATP
NEXT_SATP --> COMPARE
SET_ROOT --> CURRENT_SATP
SWITCH --> COMPARE
WRITE_SATP --> FLUSH_TLB

Key Operations:

  • Initialization: New tasks start with the kernel page table root
  • Page Table Switching: Only occurs when SATP values differ between tasks
  • TLB Management: Complete TLB flush after page table switches
  • Conditional Compilation: Available only with uspace feature

Sources: src/riscv/context.rs(L120 - L121)  src/riscv/context.rs(L154 - L156)  src/riscv/context.rs(L168 - L172) 

Thread Local Storage Support

The RISC-V implementation provides thread-local storage support through the tp (thread pointer) register, available when the tls feature is enabled.

TLS Context Management

OperationMethodRegisterPurpose
Initializationinit()SettpfieldConfigure TLS area for new tasks
Context Switchswitch_to()Save/restoretpMaintain per-task TLS state
Register AccessASM functionsRead/write TPLow-level TLS manipulation

The TLS implementation ensures that each task maintains its own thread-local storage area by:

  1. Saving Current State: Reading the current tp register value before switching
  2. Restoring Next State: Writing the next task's tp value to the register
  3. Initialization Support: Setting the TLS area address during task creation

Sources: src/riscv/context.rs(L118)  src/riscv/context.rs(L146)  src/riscv/context.rs(L164 - L167) 

TaskContext Lifecycle

The TaskContext follows a specific lifecycle pattern for task creation and management:

flowchart TD
subgraph subGraph1["Runtime Operations"]
    SWITCH["switch_to(next_ctx)"]
    SET_PT["set_page_table_root()"]
    ACTIVE["Task Execution"]
end
subgraph subGraph0["Task Creation Flow"]
    NEW["TaskContext::new()"]
    INIT["init(entry, kstack_top, tls_area)"]
    READY["Ready for Switching"]
end

ACTIVE --> SWITCH
INIT --> READY
NEW --> INIT
READY --> SET_PT
READY --> SWITCH
SET_PT --> SWITCH
SWITCH --> ACTIVE

Initialization Parameters:

  • entry: Task entry point address (stored in ra)
  • kstack_top: Kernel stack top (stored in sp)
  • tls_area: Thread-local storage area (stored in tp)

Sources: src/riscv/context.rs(L133 - L147) 

RISC-V Trap and Exception Handling

Relevant source files

Purpose and Scope

This page covers the RISC-V trap and exception handling implementation in axcpu, including both the assembly-level trap entry/exit mechanisms and the Rust-based trap dispatch logic. The system handles supervisor-mode and user-mode traps, including page faults, system calls, interrupts, and debugging exceptions.

For information about RISC-V context management and register state, see 4.1. For system initialization and trap vector setup, see 4.3. For cross-architecture trap handling abstractions, see 6.2.

Trap Handling Architecture

The RISC-V trap handling system operates in two phases: assembly-level trap entry/exit for performance-critical register save/restore operations, and Rust-based trap dispatch for high-level exception handling logic.

Trap Flow Diagram

flowchart TD
HW["Hardware Exception/Interrupt"]
TV["trap_vector_base"]
SS["sscratch == 0?"]
STE[".Ltrap_entry_s(Supervisor trap)"]
UTE[".Ltrap_entry_u(User trap)"]
SRS["SAVE_REGS 0"]
URS["SAVE_REGS 1"]
RTH["riscv_trap_handler(tf, false)"]
RTH2["riscv_trap_handler(tf, true)"]
SC["scause analysis"]
PF["Page Faulthandle_page_fault()"]
BP["Breakpointhandle_breakpoint()"]
SYS["System Callhandle_syscall()"]
IRQ["Interrupthandle_trap!(IRQ)"]
SRR["RESTORE_REGS 0"]
URR["RESTORE_REGS 1"]
SRET["sret"]
SRET2["sret"]

BP --> SRR
HW --> TV
IRQ --> SRR
PF --> SRR
RTH --> SC
RTH2 --> SC
SC --> BP
SC --> IRQ
SC --> PF
SC --> SYS
SRR --> SRET
SRS --> RTH
SS --> STE
SS --> UTE
STE --> SRS
SYS --> URR
TV --> SS
URR --> SRET2
URS --> RTH2
UTE --> URS

Sources: src/riscv/trap.S(L45 - L69)  src/riscv/trap.rs(L36 - L71) 

Assembly Trap Entry Mechanism

The trap entry mechanism uses the sscratch register to distinguish between supervisor-mode and user-mode traps, implementing different register save/restore strategies for each privilege level.

Register Save/Restore Strategy

Trap Sourcesscratch ValueEntry PointRegister Handling
Supervisor Mode0.Ltrap_entry_sBasic register save, no privilege switch
User ModeNon-zero (supervisor SP).Ltrap_entry_uFull context switch including GP/TP registers

Trap Vector Implementation

flowchart TD
TV["trap_vector_base"]
SSW["csrrw sp, sscratch, sp"]
BZ["bnez sp, .Ltrap_entry_u"]
CSR["csrr sp, sscratch"]
UE[".Ltrap_entry_u"]
SE[".Ltrap_entry_s"]
SR0["SAVE_REGS 0Basic save"]
SR1["SAVE_REGS 1Context switch"]
CH["call riscv_trap_handler(tf, false)"]
CH2["call riscv_trap_handler(tf, true)"]

BZ --> CSR
BZ --> UE
CSR --> SE
SE --> SR0
SR0 --> CH
SR1 --> CH2
SSW --> BZ
TV --> SSW
UE --> SR1

Sources: src/riscv/trap.S(L45 - L69) 

SAVE_REGS Macro Behavior

The SAVE_REGS macro implements different register handling strategies based on the trap source:

Supervisor Mode (from_user=0):

  • Saves all general-purpose registers to trap frame
  • Preserves sepc, sstatus, and sscratch
  • No privilege-level register switching

User Mode (from_user=1):

  • Saves user GP and TP registers to trap frame
  • Loads supervisor GP and TP from trap frame offsets 2 and 3
  • Switches to supervisor register context

Sources: src/riscv/trap.S(L1 - L20) 

Rust Trap Handler Dispatch

The riscv_trap_handler function serves as the main dispatch point for all RISC-V traps, analyzing the scause register to determine trap type and invoking appropriate handlers.

Trap Classification and Dispatch

flowchart TD
RTH["riscv_trap_handler(tf, from_user)"]
SCR["scause::read()"]
TTC["scause.cause().try_into::()"]
EXC["Exception Branch"]
INT["Interrupt Branch"]
UNK["Unknown trap"]
UEC["UserEnvCall(System Call)"]
LPF["LoadPageFault"]
SPF["StorePageFault"]
IPF["InstructionPageFault"]
BKP["Breakpoint"]
HIRQ["handle_trap!(IRQ)"]
HSC["handle_syscall(tf, tf.regs.a7)"]
HPF1["handle_page_fault(tf, READ, from_user)"]
HPF2["handle_page_fault(tf, WRITE, from_user)"]
HPF3["handle_page_fault(tf, EXECUTE, from_user)"]
HBK["handle_breakpoint(&mut tf.sepc)"]
PAN["panic!(Unknown trap)"]

BKP --> HBK
EXC --> BKP
EXC --> IPF
EXC --> LPF
EXC --> SPF
EXC --> UEC
INT --> HIRQ
IPF --> HPF3
LPF --> HPF1
RTH --> SCR
SCR --> TTC
SPF --> HPF2
TTC --> EXC
TTC --> INT
TTC --> UNK
UEC --> HSC
UNK --> PAN

Sources: src/riscv/trap.rs(L36 - L71) 

Specific Trap Type Handlers

Page Fault Handling

The handle_page_fault function processes memory access violations by extracting the fault address from stval and determining access permissions:

flowchart TD
HPF["handle_page_fault(tf, access_flags, is_user)"]
USR["is_user?"]
UFLG["access_flags |= USER"]
VADDR["vaddr = va!(stval::read())"]
HTM["handle_trap!(PAGE_FAULT, vaddr, access_flags, is_user)"]
SUC["Success?"]
RET["Return"]
PAN["panic!(Unhandled Page Fault)"]

HPF --> USR
HTM --> SUC
SUC --> PAN
SUC --> RET
UFLG --> VADDR
USR --> UFLG
USR --> VADDR
VADDR --> HTM

Page Fault Types:

  • LoadPageFault: Read access violation (PageFaultFlags::READ)
  • StorePageFault: Write access violation (PageFaultFlags::WRITE)
  • InstructionPageFault: Execute access violation (PageFaultFlags::EXECUTE)

Sources: src/riscv/trap.rs(L19 - L34)  src/riscv/trap.rs(L46 - L54) 

System Call Handling

System calls are handled through the UserEnvCall exception when the uspace feature is enabled:

tf.regs.a0 = crate::trap::handle_syscall(tf, tf.regs.a7) as usize;
tf.sepc += 4;

The system call number is passed in register a7, and the return value is stored in a0. The program counter (sepc) is incremented by 4 to skip the ecall instruction.

Sources: src/riscv/trap.rs(L42 - L45) 

Breakpoint Handling

Breakpoint exceptions increment the program counter by 2 bytes to skip the compressed ebreak instruction:

#![allow(unused)]
fn main() {
fn handle_breakpoint(sepc: &mut usize) {
    debug!("Exception(Breakpoint) @ {sepc:#x} ");
    *sepc += 2
}
}

Sources: src/riscv/trap.rs(L14 - L17)  src/riscv/trap.rs(L55) 

Interrupt Handling

Hardware interrupts are dispatched to the common trap handling framework using the handle_trap! macro with the IRQ trap type and scause.bits() as the interrupt number.

Sources: src/riscv/trap.rs(L56 - L58) 

Integration with Common Framework

The RISC-V trap handler integrates with axcpu's cross-architecture trap handling framework through several mechanisms:

Trap Framework Integration

flowchart TD
subgraph subGraph1["Common Framework"]
    HTM["handle_trap! macro"]
    HSC["crate::trap::handle_syscall"]
    PFF["PageFaultFlags"]
end
subgraph subGraph0["RISC-V Specific"]
    RTH["riscv_trap_handler"]
    HPF["handle_page_fault"]
    HBP["handle_breakpoint"]
end

HPF --> HTM
HPF --> PFF
RTH --> HSC
RTH --> HTM

Framework Components Used:

  • PageFaultFlags: Common page fault flag definitions
  • handle_trap! macro: Architecture-agnostic trap dispatch
  • crate::trap::handle_syscall: Common system call interface

Sources: src/riscv/trap.rs(L1 - L6)  src/riscv/trap.rs(L24)  src/riscv/trap.rs(L43)  src/riscv/trap.rs(L57) 

Assembly Integration

The trap handling system integrates assembly code through the global_asm! macro, including architecture-specific macros and the trap frame size constant:

core::arch::global_asm!(
    include_asm_macros!(),
    include_str!("trap.S"),
    trapframe_size = const core::mem::size_of::<TrapFrame>(),
);

This approach ensures type safety by using the Rust TrapFrame size directly in assembly code, preventing layout mismatches between Rust and assembly implementations.

Sources: src/riscv/trap.rs(L8 - L12) 

RISC-V System Initialization

Relevant source files

Purpose and Scope

This document covers the RISC-V-specific system initialization procedures implemented in the axcpu library. It focuses on the bootstrap sequence for setting up CPU state and trap handling on RISC-V platforms during system startup.

For RISC-V context management structures and switching mechanisms, see RISC-V Context Management. For detailed trap handling implementation after initialization, see RISC-V Trap and Exception Handling. For initialization procedures on other architectures, see x86_64 System Initialization and AArch64 System Initialization.

System Initialization Overview

The RISC-V system initialization is implemented as a minimal bootstrap procedure that configures essential CPU state for trap handling. Unlike other architectures that may require complex descriptor table setup, RISC-V initialization focuses primarily on trap vector configuration.

flowchart TD
subgraph subGraph1["External Dependencies"]
    ASM_MODULE["asm module"]
    TRAP_ASSEMBLY["trap.S"]
end
subgraph subGraph0["RISC-V System Bootstrap"]
    BOOT["System Boot"]
    INIT_TRAP["init_trap()"]
    VECTOR_BASE["trap_vector_base()"]
    WRITE_VECTOR["write_trap_vector_base()"]
    READY["CPU Ready for Operation"]
end

BOOT --> INIT_TRAP
INIT_TRAP --> ASM_MODULE
INIT_TRAP --> VECTOR_BASE
INIT_TRAP --> WRITE_VECTOR
VECTOR_BASE --> TRAP_ASSEMBLY
VECTOR_BASE --> WRITE_VECTOR
WRITE_VECTOR --> ASM_MODULE
WRITE_VECTOR --> READY

RISC-V Initialization Flow

Sources: src/riscv/init.rs(L1 - L14) 

Trap Vector Initialization

The core initialization function init_trap() establishes the trap vector base address that the RISC-V CPU uses to locate exception handlers. This is a critical step that must be completed before the system can handle any interrupts or exceptions.

flowchart TD
subgraph subGraph1["External Symbols"]
    TRAP_VECTOR["trap_vector_base()"]
    ASM_WRITE["asm::write_trap_vector_base()"]
end
subgraph subGraph0["init_trap() Implementation"]
    FUNC_START["init_trap()"]
    GET_ADDR["Get trap_vector_base address"]
    CAST_ADDR["Cast to usize"]
    WRITE_CSR["write_trap_vector_base()"]
    COMPLETE["Initialization Complete"]
end

CAST_ADDR --> WRITE_CSR
FUNC_START --> GET_ADDR
GET_ADDR --> CAST_ADDR
GET_ADDR --> TRAP_VECTOR
WRITE_CSR --> ASM_WRITE
WRITE_CSR --> COMPLETE

Trap Vector Setup Process

The implementation follows this sequence:

  1. Vector Address Resolution: The trap_vector_base symbol is resolved as an external C function pointer, representing the base address of the trap vector table
  2. Address Conversion: The function pointer is cast to usize for use as a memory address
  3. CSR Configuration: The address is written to the appropriate RISC-V Control and Status Register (CSR) via write_trap_vector_base()

Sources: src/riscv/init.rs(L6 - L13) 

Integration with Assembly Layer

The initialization depends on two key external components that bridge to the assembly layer:

ComponentTypePurpose
trap_vector_baseExternal C functionProvides base address of trap vector table
write_trap_vector_base()Assembly functionWrites address to RISC-V trap vector CSR
flowchart TD
subgraph subGraph2["Hardware Layer"]
    TVEC_CSR["RISC-V tvec CSR"]
    TRAP_TABLE["Trap Vector Table"]
end
subgraph subGraph1["Assembly Interface"]
    TRAP_BASE["trap_vector_base symbol"]
    WRITE_CSR["write_trap_vector_base()"]
end
subgraph subGraph0["Rust Layer"]
    INIT_TRAP["init_trap()"]
end

INIT_TRAP --> TRAP_BASE
INIT_TRAP --> WRITE_CSR
TRAP_BASE --> TRAP_TABLE
TVEC_CSR --> TRAP_TABLE
WRITE_CSR --> TVEC_CSR

Assembly Interface Integration

The unsafe blocks in the implementation reflect the direct hardware manipulation required for system initialization, where incorrect trap vector configuration could compromise system stability.

Sources: src/riscv/init.rs(L7 - L12) 

Relationship to Trap Handling Architecture

The initialization establishes the foundation for the RISC-V trap handling system. The configured trap vector base points to assembly routines that will save CPU state into TrapFrame structures and dispatch to Rust handlers.

flowchart TD
subgraph subGraph1["Data Structures"]
    TRAP_FRAME["TrapFrame"]
    TASK_CONTEXT["TaskContext"]
end
subgraph subGraph0["System Lifecycle"]
    INIT["init_trap()"]
    VECTOR_SET["Trap Vector Configured"]
    TRAP_OCCUR["Trap/Exception Occurs"]
    VECTOR_JUMP["Hardware jumps to trap_vector_base"]
    SAVE_CONTEXT["Save to TrapFrame"]
    RUST_HANDLER["Rust trap handler"]
end

INIT --> VECTOR_SET
RUST_HANDLER --> TASK_CONTEXT
SAVE_CONTEXT --> RUST_HANDLER
SAVE_CONTEXT --> TRAP_FRAME
TRAP_OCCUR --> VECTOR_JUMP
VECTOR_JUMP --> SAVE_CONTEXT
VECTOR_SET --> TRAP_OCCUR

Initialization to Runtime Flow

The initialization creates the linkage between hardware trap events and the software trap handling framework implemented in the broader RISC-V module.

Sources: src/riscv/init.rs(L1 - L14) 

Architectural Simplicity

The RISC-V initialization implementation demonstrates the architectural simplicity compared to other platforms. The entire initialization consists of a single function with minimal setup requirements, reflecting RISC-V's streamlined approach to system configuration.

This contrasts with x86_64 initialization which requires GDT and IDT setup, or AArch64 initialization which involves exception level transitions and MMU configuration. The RISC-V approach focuses on the essential trap vector configuration needed for basic system operation.

Sources: src/riscv/init.rs(L1 - L14) 

LoongArch64 Architecture

Relevant source files

Purpose and Scope

This document covers the LoongArch64 architecture support within the axcpu multi-architecture CPU abstraction library. LoongArch64 is a RISC instruction set architecture developed by Loongson Technology, and this module provides comprehensive CPU context management, trap handling, and system initialization capabilities for LoongArch64-based systems.

For detailed information about specific aspects of LoongArch64 support, see LoongArch64 Context Management, LoongArch64 Assembly Operations, and LoongArch64 System Initialization. For comparative information about other supported architectures, see x86_64 Architecture, AArch64 Architecture, and RISC-V Architecture.

LoongArch64 Module Organization

The LoongArch64 architecture support follows the standard axcpu architecture pattern, with a well-organized module structure that separates concerns between low-level assembly operations, context management, trap handling, and system initialization.

Module Structure Overview


Sources: src/loongarch64/mod.rs(L1 - L14) 

Core Data Structures

The LoongArch64 module exports four key data structures that form the foundation of CPU state management:

StructurePurposeModule
FpuStateFloating-point unit register statecontext
GeneralRegistersGeneral-purpose register statecontext
TaskContextMinimal context for task switchingcontext
TrapFrameComplete CPU state for exception handlingcontext

These structures provide the necessary abstractions for context switching, exception handling, and floating-point state management specific to the LoongArch64 architecture.

Sources: src/loongarch64/mod.rs(L13) 

LoongArch64 Feature Support

The LoongArch64 implementation supports both core functionality and optional features that enhance system capabilities.

Feature Matrix


Sources: src/loongarch64/mod.rs(L10 - L11) 

Architecture Integration

The LoongArch64 module integrates seamlessly with the broader axcpu framework, following established patterns while accommodating LoongArch64-specific requirements.

Integration with axcpu Framework

flowchart TD
subgraph subGraph2["External Dependencies"]
    MEMORY_ADDR["memory_addr crateAddress Abstractions"]
    PAGE_TABLE["page_table_entry cratePage Table Support"]
    LOONGARCH_CRATES["LoongArch64 Support CratesArchitecture-Specific Types"]
end
subgraph subGraph1["LoongArch64 Implementation"]
    LA_MOD["loongarch64::modArchitecture Module"]
    LA_CONTEXT["loongarch64::contextTaskContext, TrapFrame"]
    LA_TRAP["loongarch64::trapException Handlers"]
    LA_ASM["loongarch64::asmAssembly Operations"]
    LA_INIT["loongarch64::initSystem Bootstrap"]
end
subgraph subGraph0["axcpu Core Framework"]
    CORE_LIB["src/lib.rsCore Library"]
    CORE_TRAP["src/trap.rsUnified Trap Framework"]
end

CORE_LIB --> LA_MOD
CORE_TRAP --> LA_TRAP
LA_ASM --> LOONGARCH_CRATES
LA_CONTEXT --> MEMORY_ADDR
LA_INIT --> PAGE_TABLE
LA_MOD --> LA_ASM
LA_MOD --> LA_CONTEXT
LA_MOD --> LA_INIT
LA_MOD --> LA_TRAP

Sources: src/loongarch64/mod.rs(L1 - L14) 

LoongArch64-Specific Characteristics

LoongArch64 brings several unique characteristics to the axcpu framework that distinguish it from other supported architectures:

Register Architecture

  • General Purpose Registers: 32 general-purpose registers (R0-R31)
  • Floating Point Registers: 32 floating-point registers (F0-F31)
  • Control Registers: Including ERA (Exception Return Address), PRMD (Privilege Mode), and others
  • Thread Pointer: TP register for thread-local storage support

Memory Management

  • Page Table Structure: Multi-level page tables with LoongArch64-specific layout
  • TLB Management: Translation Lookaside Buffer with architecture-specific invalidation
  • MMU Configuration: Memory Management Unit setup with LoongArch64 control registers

Exception Handling

  • Exception Types: Synchronous exceptions, interrupts, and system calls
  • Exception Levels: Multiple privilege levels with controlled transitions
  • Vector Table: LoongArch64-specific exception vector organization

The LoongArch64 implementation maintains compatibility with the unified axcpu interface while providing full access to architecture-specific features through specialized modules and assembly operations.

Sources: src/loongarch64/mod.rs(L1 - L14) 

LoongArch64 Context Management

Relevant source files

This document covers the CPU context management system for the LoongArch64 architecture within the axcpu library. It details the data structures and mechanisms used to save, restore, and switch between different execution contexts including general-purpose registers, floating-point state, and task switching contexts.

For LoongArch64 assembly operations and low-level register manipulation, see LoongArch64 Assembly Operations. For system initialization including MMU and TLB setup, see LoongArch64 System Initialization.

Context Management Overview

The LoongArch64 context management system provides three primary context types, each serving different purposes in the execution lifecycle:

flowchart TD
subgraph subGraph2["Register Categories"]
    GPR["General Purpose Registers$r0-$r31"]
    FPR["Floating Point Registers$f0-$f31"]
    CSR["Control/Status RegistersPRMD, ERA, FCSR"]
end
subgraph subGraph1["Usage Scenarios"]
    TRAP["Exception/Interrupt Handling"]
    SWITCH["Task Switching"]
    SYSCALL["System Call Processing"]
end
subgraph subGraph0["LoongArch64 Context Types"]
    GR["GeneralRegisters"]
    TF["TrapFrame"]
    TC["TaskContext"]
    FPU["FpuState"]
end

FPU --> FPR
FPU --> TC
GR --> GPR
TC --> GR
TC --> SWITCH
TF --> CSR
TF --> GR
TF --> SYSCALL
TF --> TRAP

Sources: src/loongarch64/context.rs(L6 - L43)  src/loongarch64/context.rs(L72 - L82)  src/loongarch64/context.rs(L116 - L145) 

General Register Management

The GeneralRegisters structure captures all LoongArch64 general-purpose registers following the architecture's register naming convention:

Register CategoryRegistersPurpose
Zero Registerzero($r0)Always contains zero
Return Addressra($r1)Function return address
Stack/Threadsp($r3),tp($r2)Stack pointer, thread pointer
Argumentsa0-a7($r4-$r11)Function arguments and return values
Temporariest0-t8($r12-$r20)Temporary registers
Saveds0-s8($r23-$r31)Callee-saved registers
Frame Pointerfp($r22)Frame pointer
Reservedu0($r21)Reserved for user applications

The register layout in GeneralRegisters matches the LoongArch64 ABI specification, ensuring compatibility with compiler-generated code and system call conventions.

Sources: src/loongarch64/context.rs(L6 - L43) 

Floating-Point State Management

FpuState Structure

The FpuState structure manages LoongArch64 floating-point context:

flowchart TD
subgraph subGraph2["Assembly Macros"]
    SAVE_FP["SAVE_FP"]
    SAVE_FCC_M["SAVE_FCC"]
    SAVE_FCSR["SAVE_FCSR"]
    RESTORE_FP["RESTORE_FP"]
    RESTORE_FCC_M["RESTORE_FCC"]
    RESTORE_FCSR["RESTORE_FCSR"]
end
subgraph subGraph1["FPU Operations"]
    SAVE_OP["save()"]
    RESTORE_OP["restore()"]
    SAVE_ASM["save_fp_registers()"]
    RESTORE_ASM["restore_fp_registers()"]
end
subgraph subGraph0["FpuState Components"]
    FP_REGS["fp: [u64; 32]Floating-point registers f0-f31"]
    FCC["fcc: [u8; 8]Floating-point condition codes"]
    FCSR["fcsr: u32Control and status register"]
end

FCC --> SAVE_OP
FCSR --> SAVE_OP
FP_REGS --> SAVE_OP
RESTORE_ASM --> RESTORE_FCC_M
RESTORE_ASM --> RESTORE_FCSR
RESTORE_ASM --> RESTORE_FP
RESTORE_OP --> RESTORE_ASM
SAVE_ASM --> SAVE_FCC_M
SAVE_ASM --> SAVE_FCSR
SAVE_ASM --> SAVE_FP
SAVE_OP --> SAVE_ASM

FPU State Operations

The floating-point state management is conditional on the fp-simd feature flag. When enabled, the FpuState structure provides save() and restore() methods that delegate to assembly routines for efficient register transfers.

The assembly implementations use offset calculations to access structure fields directly, ensuring optimal performance during context switches.

Sources: src/loongarch64/context.rs(L45 - L70)  src/loongarch64/context.rs(L197 - L229) 

Trap Frame Context

TrapFrame Structure

The TrapFrame captures the complete CPU state when exceptions, interrupts, or system calls occur:

flowchart TD
subgraph subGraph2["Exception Context"]
    EXCEPTION["Hardware Exception"]
    INTERRUPT["Interrupt"]
    SYSCALL["System Call"]
end
subgraph subGraph1["System Call Interface"]
    ARG0["arg0() -> a0"]
    ARG1["arg1() -> a1"]
    ARG2["arg2() -> a2"]
    ARG3["arg3() -> a3"]
    ARG4["arg4() -> a4"]
    ARG5["arg5() -> a5"]
end
subgraph subGraph0["TrapFrame Layout"]
    REGS["regs: GeneralRegistersAll general-purpose registers"]
    PRMD["prmd: usizePre-exception Mode Information"]
    ERA["era: usizeException Return Address"]
end

EXCEPTION --> ERA
EXCEPTION --> PRMD
INTERRUPT --> ERA
INTERRUPT --> PRMD
REGS --> ARG0
REGS --> ARG1
REGS --> ARG2
REGS --> ARG3
REGS --> ARG4
REGS --> ARG5
SYSCALL --> ARG0

System Call Argument Access

The TrapFrame provides convenience methods for accessing system call arguments through registers a0 through a5. These methods cast the register values appropriately for syscall parameter passing conventions.

Sources: src/loongarch64/context.rs(L72 - L114) 

Task Context and Switching

TaskContext Structure

The TaskContext represents the minimal state required for task switching:

FieldTypePurposeFeature Flag
rausizeReturn addressAlways
spusizeStack pointerAlways
s[usize; 10]Saved registers $r22-$r31Always
tpusizeThread pointerAlways
pgdlusizePage table rootuspace
fpuFpuStateFPU statefp-simd

Context Switch Implementation

The task switching process involves multiple phases coordinated between Rust and assembly code:


Context Switch Assembly Implementation

The low-level context switch uses the STD and LDD assembly macros to efficiently save and restore the minimal register set required for task switching. The assembly routine saves callee-saved registers from the current context and restores them for the next context.

Sources: src/loongarch64/context.rs(L116 - L195)  src/loongarch64/context.rs(L231 - L266) 

Feature-Conditional Context Management

The LoongArch64 context management system supports several optional features that extend the basic context switching capabilities:

Thread-Local Storage Support

When the tls feature is enabled, the context switch saves and restores the thread pointer (tp) register, enabling proper thread-local storage functionality across task switches.

User Space Support

The uspace feature adds user page table management to task contexts. During context switches, the system checks if the page table root (pgdl) has changed and updates the hardware page table register accordingly, followed by a TLB flush.

Floating-Point SIMD Support

With the fp-simd feature, the task context includes complete FPU state management. During context switches, the current task's FPU state is saved and the next task's FPU state is restored, ensuring floating-point computations remain isolated between tasks.

Sources: src/loongarch64/context.rs(L175 - L194) 

LoongArch64 Assembly Operations

Relevant source files

This document covers the low-level assembly operations and macros provided by the LoongArch64 architecture implementation in axcpu. It focuses on the assembly-level primitives used for CPU state management, register manipulation, and hardware control operations.

For information about LoongArch64 context structures and high-level context switching, see LoongArch64 Context Management. For system initialization procedures, see LoongArch64 System Initialization.

Control and Status Register (CSR) Operations

The LoongArch64 implementation defines a comprehensive set of Control and Status Register (CSR) constants and operations. These registers control fundamental CPU behaviors including exception handling, memory management, and system configuration.

CSR Definitions

The core CSR registers are defined as assembly constants for direct hardware access:

RegisterValuePurpose
LA_CSR_PRMD0x1Previous Mode Data - saves processor state
LA_CSR_EUEN0x2Extended Unit Enable - controls extensions
LA_CSR_ERA0x6Exception Return Address
LA_CSR_PGDL0x19Page table base when VA[47] = 0
LA_CSR_PGDH0x1aPage table base when VA[47] = 1
LA_CSR_PGD0x1bGeneral page table base
LA_CSR_TLBRENTRY0x88TLB refill exception entry
LA_CSR_DMW0/DMW10x180/0x181Direct Mapped Windows
flowchart TD
subgraph CSR_SYSTEM["LoongArch64 CSR System"]
    subgraph KSAVE_REGS["Kernel Save Registers"]
        KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"]
        KSAVE_TEMP["KSAVE_TEMPTemporary Storage"]
        KSAVE_R21["KSAVE_R21Register 21 Save"]
        KSAVE_TP["KSAVE_TPThread Pointer Save"]
        EUEN["LA_CSR_EUENExtended Unit Enable"]
        TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"]
        PGDL["LA_CSR_PGDLUser Page Table"]
        PRMD["LA_CSR_PRMDPrevious Mode Data"]
    end
    subgraph EXTENSION_CSRS["Extensions"]
        subgraph TLB_CSRS["TLB Management"]
            KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"]
            EUEN["LA_CSR_EUENExtended Unit Enable"]
            DMW0["LA_CSR_DMW0Direct Map Window 0"]
            DMW1["LA_CSR_DMW1Direct Map Window 1"]
            TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"]
            TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"]
            TLBRERA["LA_CSR_TLBRERATLB Refill ERA"]
            PGDL["LA_CSR_PGDLUser Page Table"]
            PGDH["LA_CSR_PGDHKernel Page Table"]
            PWCL["LA_CSR_PWCLPage Walk Lower"]
            PRMD["LA_CSR_PRMDPrevious Mode Data"]
            ERA["LA_CSR_ERAException Return Address"]
            EENTRY["Exception Entry Base"]
        end
    end
    subgraph MEMORY_CSRS["Memory Management"]
        KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"]
        EUEN["LA_CSR_EUENExtended Unit Enable"]
        DMW0["LA_CSR_DMW0Direct Map Window 0"]
        DMW1["LA_CSR_DMW1Direct Map Window 1"]
        TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"]
        TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"]
        TLBRERA["LA_CSR_TLBRERATLB Refill ERA"]
        PGDL["LA_CSR_PGDLUser Page Table"]
        PGDH["LA_CSR_PGDHKernel Page Table"]
        PWCL["LA_CSR_PWCLPage Walk Lower"]
        PWCH["LA_CSR_PWCHPage Walk Higher"]
        PRMD["LA_CSR_PRMDPrevious Mode Data"]
        ERA["LA_CSR_ERAException Return Address"]
        EENTRY["Exception Entry Base"]
    end
    subgraph EXCEPTION_CSRS["Exception Control"]
        KSAVE_KSP["KSAVE_KSPKernel Stack Pointer"]
        EUEN["LA_CSR_EUENExtended Unit Enable"]
        DMW0["LA_CSR_DMW0Direct Map Window 0"]
        DMW1["LA_CSR_DMW1Direct Map Window 1"]
        TLBRENTRY["LA_CSR_TLBRENTRYTLB Refill Entry"]
        TLBRBADV["LA_CSR_TLBRBADVTLB Bad Address"]
        TLBRERA["LA_CSR_TLBRERATLB Refill ERA"]
        PGDL["LA_CSR_PGDLUser Page Table"]
        PGDH["LA_CSR_PGDHKernel Page Table"]
        PWCL["LA_CSR_PWCLPage Walk Lower"]
        PRMD["LA_CSR_PRMDPrevious Mode Data"]
        ERA["LA_CSR_ERAException Return Address"]
        EENTRY["Exception Entry Base"]
    end
end

Sources: src/loongarch64/macros.rs(L7 - L29) 

Register Access Macros

The implementation provides optimized macros for common register operations:

  • STD rd, rj, off - Store doubleword with scaled offset
  • LDD rd, rj, off - Load doubleword with scaled offset

These macros automatically scale the offset by 8 bytes for 64-bit operations, simplifying stack and structure access patterns.

Sources: src/loongarch64/macros.rs(L31 - L36) 

General Purpose Register Operations

Register Save/Restore Framework

The LoongArch64 implementation provides a systematic approach to saving and restoring general purpose registers using parameterized macros:

flowchart TD
subgraph GPR_OPERATIONS["General Purpose Register Operations"]
    subgraph CONCRETE_MACROS["Concrete Operations"]
        PUSH_GENERAL["PUSH_GENERAL_REGSSave All GPRs"]
        POP_GENERAL["POP_GENERAL_REGSRestore All GPRs"]
    end
    subgraph REGISTER_GROUPS["Register Groups"]
        ARGS["Argument Registersa0-a7"]
        TEMPS["Temporary Registerst0-t8"]
        SAVED["Saved Registerss0-s8"]
        SPECIAL["Special Registersra, fp, sp"]
    end
    subgraph MACRO_FRAMEWORK["Macro Framework"]
        PUSH_POP["PUSH_POP_GENERAL_REGSParameterized Operation"]
        STD_OP["STD OperationStore Doubleword"]
        LDD_OP["LDD OperationLoad Doubleword"]
    end
end

ARGS --> PUSH_GENERAL
POP_GENERAL --> PUSH_POP
PUSH_GENERAL --> PUSH_POP
PUSH_POP --> LDD_OP
PUSH_POP --> STD_OP
SAVED --> PUSH_GENERAL
SPECIAL --> PUSH_GENERAL
TEMPS --> PUSH_GENERAL

The PUSH_POP_GENERAL_REGS macro takes an operation parameter (STD for save, LDD for restore) and systematically processes all general purpose registers in a standardized order. This ensures consistent register handling across all context switching operations.

Sources: src/loongarch64/macros.rs(L38 - L74) 

Register Layout and Ordering

The register save/restore operations follow the LoongArch64 ABI conventions:

OffsetRegisterPurpose
1$raReturn address
4-11$a0-$a7Function arguments
12-20$t0-$t8Temporary registers
22$fpFrame pointer
23-31$s0-$s8Saved registers

Sources: src/loongarch64/macros.rs(L39 - L66) 

Floating Point Operations

When the fp-simd feature is enabled, the LoongArch64 implementation provides comprehensive floating point state management through specialized assembly macros.

Floating Point Condition Code (FCC) Management

The FCC registers (fcc0-fcc7) store comparison results and require special handling due to their packed storage format:

flowchart TD
subgraph FCC_OPERATIONS["Floating Point Condition Code Operations"]
    FCC_UNPACK["Unpack bit fieldsbstrpick.d instructions"]
    FCC_WRITE["Write fcc0-fcc7movgr2cf instructions"]
    FCC_PACK["Pack into single registerbstrins.d instructions"]
    FCC_STORE["Store to memoryst.d instruction"]
    subgraph BIT_LAYOUT["64-bit FCC Layout"]
        FCC0_BITS["fcc0: bits 7:0"]
        FCC1_BITS["fcc1: bits 15:8"]
        FCC2_BITS["fcc2: bits 23:16"]
        FCC3_BITS["fcc3: bits 31:24"]
        FCC4_BITS["fcc4: bits 39:32"]
        FCC5_BITS["fcc5: bits 47:40"]
        FCC6_BITS["fcc6: bits 55:48"]
        FCC7_BITS["fcc7: bits 63:56"]
        FCC_LOAD["Load from memoryld.d instruction"]
        FCC_READ["Read fcc0-fcc7movcf2gr instructions"]
    end
    subgraph FCC_RESTORE["RESTORE_FCC Macro Flow"]
        subgraph FCC_SAVE["SAVE_FCC Macro Flow"]
            FCC0_BITS["fcc0: bits 7:0"]
            FCC_LOAD["Load from memoryld.d instruction"]
            FCC_UNPACK["Unpack bit fieldsbstrpick.d instructions"]
            FCC_WRITE["Write fcc0-fcc7movgr2cf instructions"]
            FCC_READ["Read fcc0-fcc7movcf2gr instructions"]
            FCC_PACK["Pack into single registerbstrins.d instructions"]
            FCC_STORE["Store to memoryst.d instruction"]
        end
    end
end

FCC_LOAD --> FCC_UNPACK
FCC_PACK --> FCC_STORE
FCC_READ --> FCC_PACK
FCC_UNPACK --> FCC_WRITE

The implementation uses bit manipulation instructions (bstrins.d, bstrpick.d) to efficiently pack and unpack the 8 condition code registers into a single 64-bit value for storage.

Sources: src/loongarch64/macros.rs(L87 - L125) 

Floating Point Control and Status Register (FCSR)

The FCSR management is simpler than FCC handling since it's a single 32-bit register:

  • SAVE_FCSR - Uses movfcsr2gr to read FCSR and st.w to store
  • RESTORE_FCSR - Uses ld.w to load and movgr2fcsr to write back

Sources: src/loongarch64/macros.rs(L127 - L135) 

Floating Point Register Operations

The floating point registers ($f0-$f31) are handled through a parameterized macro system similar to general purpose registers:

flowchart TD
subgraph FP_REG_OPS["Floating Point Register Operations"]
    subgraph FP_REGISTERS["32 Floating Point Registers"]
        F0_F7["f0-f7Temporary registers"]
        F8_F15["f8-f15Temporary registers"]
        F16_F23["f16-f23Saved registers"]
        F24_F31["f24-f31Saved registers"]
    end
    subgraph FP_CONCRETE["Concrete Operations"]
        SAVE_FP["SAVE_FP MacroUses fst.d operation"]
        RESTORE_FP["RESTORE_FP MacroUses fld.d operation"]
    end
    subgraph FP_MACRO["PUSH_POP_FLOAT_REGS Macro"]
        FP_PARAM["Takes operation and base registerfst.d or fld.d"]
        FP_LOOP["Iterates f0 through f31Sequential 8-byte offsets"]
    end
end

F0_F7 --> FP_LOOP
F16_F23 --> FP_LOOP
F24_F31 --> FP_LOOP
F8_F15 --> FP_LOOP
FP_PARAM --> RESTORE_FP
FP_PARAM --> SAVE_FP

Sources: src/loongarch64/macros.rs(L138 - L179) 

Memory Management Operations

The LoongArch64 architecture provides sophisticated memory management capabilities through dedicated wrapper functions that interact with hardware registers and TLB operations.

Page Table Management

LoongArch64 uses separate page table base registers for user and kernel address spaces:

FunctionRegisterAddress Space
read_user_page_table()PGDLVA[47] = 0 (user space)
read_kernel_page_table()PGDHVA[47] = 1 (kernel space)
write_user_page_table()PGDLUser space page table root
write_kernel_page_table()PGDHKernel space page table root
flowchart TD
subgraph PAGE_TABLE_OPS["Page Table Operations"]
    subgraph ADDRESS_SPACES["Virtual Address Spaces"]
        USER_SPACE["User SpaceVA[47] = 0"]
        KERNEL_SPACE["Kernel SpaceVA[47] = 1"]
    end
    subgraph HARDWARE_REGS["Hardware Registers"]
        PGDL_REG["PGDL RegisterUser Space Page Table"]
        PGDH_REG["PGDH RegisterKernel Space Page Table"]
    end
    subgraph WRITE_OPS["Write Operations"]
        WRITE_USER["write_user_page_table()pgdl::set_base()"]
        WRITE_KERNEL["write_kernel_page_table()pgdh::set_base()"]
    end
    subgraph READ_OPS["Read Operations"]
        READ_USER["read_user_page_table()pgdl::read().base()"]
        READ_KERNEL["read_kernel_page_table()pgdh::read().base()"]
    end
end

PGDH_REG --> KERNEL_SPACE
PGDL_REG --> USER_SPACE
READ_KERNEL --> PGDH_REG
READ_USER --> PGDL_REG
WRITE_KERNEL --> PGDH_REG
WRITE_USER --> PGDL_REG

Sources: src/loongarch64/asm.rs(L41 - L79) 

TLB Management

The Translation Lookaside Buffer (TLB) management uses the invtlb instruction with specific operation codes:

  • invtlb 0x00 - Clear all TLB entries (full flush)
  • invtlb 0x05 - Clear specific VA with ASID=0 (single page flush)

The implementation includes proper memory barriers (dbar 0) to ensure ordering of memory operations around TLB invalidation.

Sources: src/loongarch64/asm.rs(L81 - L111) 

Page Walk Controller Configuration

The write_pwc() function configures the hardware page walker through the PWCL and PWCH registers, which control page table traversal parameters for lower and upper address spaces respectively.

Sources: src/loongarch64/asm.rs(L130 - L150) 

Exception and Interrupt Handling

Interrupt Control

The LoongArch64 interrupt system is controlled through the Current Mode register (CRMD):

FunctionOperationHardware Effect
enable_irqs()crmd::set_ie(true)Sets IE bit in CRMD
disable_irqs()crmd::set_ie(false)Clears IE bit in CRMD
irqs_enabled()crmd::read().ie()Reads IE bit status
flowchart TD
subgraph IRQ_CONTROL["Interrupt Control System"]
    subgraph HARDWARE_STATE["Hardware State"]
        CRMD_REG["CRMD RegisterCurrent Mode Data"]
        IE_BIT["IE BitInterrupt Enable"]
        CPU_STATE["CPU Execution StateRunning/Idle"]
    end
    subgraph POWER_MANAGEMENT["Power Management"]
        WAIT_IRQS["wait_for_irqs()loongArch64::asm::idle()"]
        HALT_CPU["halt()disable_irqs() + idle()"]
    end
    subgraph IRQ_FUNCTIONS["IRQ Control Functions"]
        ENABLE_IRQS["enable_irqs()crmd::set_ie(true)"]
        DISABLE_IRQS["disable_irqs()crmd::set_ie(false)"]
        CHECK_IRQS["irqs_enabled()crmd::read().ie()"]
    end
end

CHECK_IRQS --> IE_BIT
DISABLE_IRQS --> IE_BIT
ENABLE_IRQS --> IE_BIT
HALT_CPU --> CPU_STATE
HALT_CPU --> DISABLE_IRQS
IE_BIT --> CRMD_REG
WAIT_IRQS --> CPU_STATE

Sources: src/loongarch64/asm.rs(L8 - L39) 

Exception Entry Configuration

The write_exception_entry_base() function configures the exception handling entry point by setting both the Exception Entry Base Address register (EENTRY) and the Exception Configuration register (ECFG):

  • Sets ECFG.VS = 0 for unified exception entry
  • Sets EENTRY to the provided exception handler address

Sources: src/loongarch64/asm.rs(L113 - L128) 

Thread Local Storage Support

LoongArch64 implements Thread Local Storage (TLS) through the thread pointer register ($tp):

  • read_thread_pointer() - Reads current $tp value using move instruction
  • write_thread_pointer() - Updates $tp register for TLS base address

The implementation also provides kernel stack pointer management through a custom CSR (KSAVE_KSP) when the uspace feature is enabled, supporting user-space context switching.

Sources: src/loongarch64/asm.rs(L152 - L199) 

Extension Support

Floating Point Extensions

The LoongArch64 implementation supports multiple floating point and SIMD extensions:

  • enable_fp() - Enables floating-point instructions by setting EUEN.FPE
  • enable_lsx() - Enables LSX (LoongArch SIMD eXtension) by setting EUEN.SXE

These functions control the Extended Unit Enable register (EUEN) to selectively enable hardware extensions based on system requirements.

Sources: src/loongarch64/asm.rs(L174 - L187) 

LoongArch64 System Initialization

Relevant source files

This document covers the LoongArch64 system initialization routines in the axcpu library, focusing on the bootstrapping procedures for CPU state, memory management unit (MMU), and trap handling. The initialization process establishes the fundamental hardware configurations required for the CPU to operate in the kernel environment.

This page specifically covers system-level hardware initialization. For LoongArch64 CPU context management and task switching, see LoongArch64 Context Management. For low-level assembly operations and register manipulation, see LoongArch64 Assembly Operations.

Initialization Overview

The LoongArch64 system initialization process consists of two primary components: MMU/TLB configuration and trap handling setup. These initialization routines are implemented in the init_mmu and init_trap functions, which must be called during system bootstrap to establish a properly configured execution environment.

flowchart TD
subgraph subGraph2["Trap Initialization"]
    EXCEPTION_VECTOR["Exception Vector Setup"]
    HANDLER_BASE["Handler Base Address"]
end
subgraph subGraph1["MMU Initialization"]
    TLB_CONFIG["TLB Configuration"]
    PAGE_TABLE["Page Table Setup"]
    TRANSLATION["Enable Translation"]
end
subgraph subGraph0["System Bootstrap Sequence"]
    START["System Boot"]
    INIT_MMU["init_mmu()"]
    INIT_TRAP["init_trap()"]
    READY["System Ready"]
end

INIT_MMU --> PAGE_TABLE
INIT_MMU --> READY
INIT_MMU --> TLB_CONFIG
INIT_MMU --> TRANSLATION
INIT_TRAP --> EXCEPTION_VECTOR
INIT_TRAP --> HANDLER_BASE
INIT_TRAP --> READY
START --> INIT_MMU
START --> INIT_TRAP

Sources: src/loongarch64/init.rs(L1 - L49) 

MMU and TLB Initialization

The init_mmu function performs comprehensive memory management unit initialization, configuring the Translation Lookaside Buffer (TLB), page table walking parameters, and enabling virtual memory translation.

TLB Configuration Process

The MMU initialization begins with TLB configuration, setting consistent 4KB page sizes across multiple TLB-related registers and establishing the TLB refill exception handler address.

flowchart TD
subgraph subGraph1["Handler Setup"]
    HANDLE_TLB["handle_tlb_refill(extern assembly function)"]
    PHYS_ADDR["Convert to physical addressusing phys_virt_offset"]
end
subgraph subGraph0["TLB Register Configuration"]
    PS_4K["PS_4K = 0x0c(4KB Page Size)"]
    TLBIDX["tlbidx::set_ps()"]
    STLBPS["stlbps::set_ps()"]
    TLBREHI["tlbrehi::set_ps()"]
    TLBRENTRY["tlbrentry::set_tlbrentry()"]
end

HANDLE_TLB --> PHYS_ADDR
PHYS_ADDR --> TLBRENTRY
PS_4K --> STLBPS
PS_4K --> TLBIDX
PS_4K --> TLBREHI

Sources: src/loongarch64/init.rs(L19 - L25) 

Page Table Walking Configuration

Following TLB setup, the function configures page table walking parameters and establishes both kernel and user page table root addresses.

Configuration StepFunction CallPurpose
PWC Registerswrite_pwc(PWCL_VALUE, PWCH_VALUE)Set page walking control values
Kernel Page Tablewrite_kernel_page_table(root_paddr)Configure kernel space translation
User Page Tablewrite_user_page_table(pa!(0))Initialize user space (initially zero)
TLB Flushflush_tlb(None)Clear existing TLB entries

Sources: src/loongarch64/init.rs(L28 - L33) 

Translation Mode Activation

The final step enables mapped address translation mode through the Current Mode (CRMD) register, activating virtual memory management.

flowchart TD
subgraph subGraph0["Translation Activation"]
    CRMD_REG["CRMD Register"]
    PG_BIT["PG Bit = true"]
    TRANSLATION_ON["Virtual Memory Active"]
end

CRMD_REG --> PG_BIT
PG_BIT --> TRANSLATION_ON

Sources: src/loongarch64/init.rs(L35 - L36) 

Trap and Exception Initialization

The init_trap function establishes the exception handling infrastructure by configuring the exception vector base address, which serves as the entry point for all hardware exceptions and interrupts.

Exception Vector Configuration

The trap initialization process involves setting up the base address for exception handling routines, linking the hardware exception mechanism to the software handlers.

flowchart TD
subgraph subGraph0["Exception Vector Setup"]
    EXCEPTION_ENTRY["exception_entry_base(extern assembly function)"]
    ADDR_CALC["Get function address(exception_entry_base as usize)"]
    WRITE_BASE["write_exception_entry_base()(assembly call)"]
    VECTOR_SET["Exception Vector Configured"]
end

ADDR_CALC --> WRITE_BASE
EXCEPTION_ENTRY --> ADDR_CALC
WRITE_BASE --> VECTOR_SET

Sources: src/loongarch64/init.rs(L42 - L48) 

Assembly Integration Points

Both initialization functions rely heavily on external assembly routines and low-level register operations that interface directly with LoongArch64 hardware.

External Assembly Functions

FunctionPurposeUsed By
handle_tlb_refillTLB refill exception handlerinit_mmu
exception_entry_baseException vector entry pointinit_trap

Assembly Module Integration

The initialization routines call several functions from the crate::asm module:

  • write_pwc() - Configure page walking control registers
  • write_kernel_page_table() - Set kernel page table root
  • write_user_page_table() - Set user page table root
  • flush_tlb() - Invalidate TLB entries
  • write_exception_entry_base() - Set exception vector base

Sources: src/loongarch64/init.rs(L29 - L47) 

Register and Hardware References

The initialization code directly manipulates several LoongArch64-specific control registers through the loongArch64::register module:

  • CRMD - Current Mode register for translation control
  • STLBPS - Shared TLB Page Size register
  • TLBIDX - TLB Index register
  • TLBREHI - TLB Refill Entry High register
  • TLBRENTRY - TLB Refill Exception Entry Base Address register

These registers are documented in the LoongArch Architecture Reference Manual, with specific links provided in the source code comments.

Sources: src/loongarch64/init.rs(L3 - L13) 

Cross-Architecture Features

Relevant source files

Purpose and Scope

This document covers features and abstractions in the axcpu library that work uniformly across all supported architectures (x86_64, AArch64, RISC-V, and LoongArch64). These cross-architecture features provide unified interfaces and common functionality that abstract away architecture-specific implementation details.

For architecture-specific implementations, see the dedicated architecture pages: x86_64 Architecture, AArch64 Architecture, RISC-V Architecture, and LoongArch64 Architecture. For user space specific functionality, see User Space Support.

Unified Trap Handling Framework

The axcpu library provides a distributed trap handling system that allows external code to register handlers for various types of traps and exceptions. This system works consistently across all supported architectures through the linkme crate's distributed slice mechanism.

Trap Handler Registration

The core trap handling framework defines three types of distributed handler slices:

flowchart TD
subgraph subGraph2["Handler Invocation"]
    HANDLE_TRAP["handle_trap! macroDispatches to first handler"]
    HANDLE_SYSCALL["handle_syscall functionDirect syscall dispatch"]
end
subgraph subGraph1["Registration Mechanism"]
    LINKME["linkme::distributed_sliceCompile-time collection"]
    DEF_TRAP["def_trap_handler macroCreates handler slices"]
    REG_TRAP["register_trap_handler macroRegisters individual handlers"]
end
subgraph subGraph0["Trap Handler Types"]
    IRQ["IRQ[fn(usize) -> bool]Hardware interrupts"]
    PAGE_FAULT["PAGE_FAULT[fn(VirtAddr, PageFaultFlags, bool) -> bool]Memory access violations"]
    SYSCALL["SYSCALL[fn(&TrapFrame, usize) -> isize]System calls (uspace feature)"]
end

DEF_TRAP --> IRQ
DEF_TRAP --> PAGE_FAULT
DEF_TRAP --> SYSCALL
IRQ --> HANDLE_TRAP
LINKME --> DEF_TRAP
PAGE_FAULT --> HANDLE_TRAP
REG_TRAP --> IRQ
REG_TRAP --> PAGE_FAULT
REG_TRAP --> SYSCALL
SYSCALL --> HANDLE_SYSCALL

Sources: src/trap.rs(L10 - L22)  src/trap.rs(L6 - L7) 

Handler Dispatch Mechanism

The trap handling system uses a macro-based dispatch mechanism that supports single-handler registration. When multiple handlers are registered for the same trap type, the system issues a warning and uses only the first handler.

flowchart TD
subgraph subGraph1["Handler Selection Logic"]
    ITERATOR["Handler slice iterator"]
    FIRST["Take first handler"]
    WARN["Warn if multiple handlers"]
    DEFAULT["Default behavior if none"]
end
subgraph subGraph0["Dispatch Flow"]
    TRAP_EVENT["Trap EventHardware generated"]
    ARCH_HANDLER["Architecture HandlerCaptures context"]
    HANDLE_MACRO["handle_trap! macroFinds registered handler"]
    USER_HANDLER["User HandlerApplication logic"]
    RETURN["Return ControlResume or terminate"]
end

ARCH_HANDLER --> HANDLE_MACRO
HANDLE_MACRO --> ITERATOR
HANDLE_MACRO --> USER_HANDLER
ITERATOR --> DEFAULT
ITERATOR --> FIRST
ITERATOR --> WARN
TRAP_EVENT --> ARCH_HANDLER
USER_HANDLER --> RETURN

Sources: src/trap.rs(L25 - L38)  src/trap.rs(L42 - L44) 

Architecture-Agnostic Abstractions

The library provides common abstractions that are implemented differently by each architecture but present a unified interface to higher-level code.

Core Data Structure Patterns

Each architecture implements the same core abstractions with architecture-specific layouts:

flowchart TD
subgraph subGraph1["Architecture Implementations"]
    X86_IMPL["x86_64 ImplementationCallee-saved regs + stack + TLS"]
    ARM_IMPL["aarch64 ImplementationR19-R29 + SP + TPIDR + FpState"]
    RISC_IMPL["riscv Implementationra, sp, s0-s11, tp + SATP"]
    LOONG_IMPL["loongarch64 ImplementationCallee-saved + SP + TP + FPU"]
end
subgraph subGraph0["Common Abstractions"]
    TASK_CONTEXT["TaskContextMinimal context for task switching"]
    TRAP_FRAME["TrapFrameComplete CPU state for exceptions"]
    EXTENDED_STATE["Extended StateFPU, SIMD, special registers"]
end

EXTENDED_STATE --> ARM_IMPL
EXTENDED_STATE --> LOONG_IMPL
EXTENDED_STATE --> RISC_IMPL
EXTENDED_STATE --> X86_IMPL
TASK_CONTEXT --> ARM_IMPL
TASK_CONTEXT --> LOONG_IMPL
TASK_CONTEXT --> RISC_IMPL
TASK_CONTEXT --> X86_IMPL
TRAP_FRAME --> ARM_IMPL
TRAP_FRAME --> LOONG_IMPL
TRAP_FRAME --> RISC_IMPL
TRAP_FRAME --> X86_IMPL

Sources: src/lib.rs(L14 - L28)  src/trap.rs(L5) 

Common Interface Design

The library uses Rust's module system and conditional compilation to provide a unified interface while allowing architecture-specific implementations:

flowchart TD
subgraph subGraph2["Architecture Modules"]
    X86_MOD["x86_64 moduleIntel/AMD implementation"]
    ARM_MOD["aarch64 moduleARM implementation"]
    RISC_MOD["riscv moduleRISC-V implementation"]
    LOONG_MOD["loongarch64 moduleLoongArch implementation"]
end
subgraph subGraph1["Architecture Selection"]
    CFG_IF["cfg_if! macroCompile-time arch selection"]
    TARGET_ARCH["target_arch attributesRust compiler directives"]
end
subgraph subGraph0["Interface Layer"]
    LIB_RS["src/lib.rsMain entry point"]
    TRAP_RS["src/trap.rsCross-arch trap handling"]
    PUBLIC_API["Public APIRe-exported types and functions"]
end

ARM_MOD --> PUBLIC_API
CFG_IF --> TARGET_ARCH
LIB_RS --> CFG_IF
LOONG_MOD --> PUBLIC_API
RISC_MOD --> PUBLIC_API
TARGET_ARCH --> ARM_MOD
TARGET_ARCH --> LOONG_MOD
TARGET_ARCH --> RISC_MOD
TARGET_ARCH --> X86_MOD
TRAP_RS --> PUBLIC_API
X86_MOD --> PUBLIC_API

Sources: src/lib.rs(L14 - L28) 

Feature-Based Conditional Compilation

The axcpu library uses Cargo features to enable optional functionality across all architectures. These features control the compilation of additional capabilities that may not be needed in all use cases.

Feature Flag System

FeaturePurposeAvailability
uspaceUser space support including system callsAll architectures
fp-simdFloating point and SIMD register managementAll architectures
tlsThread-local storage supportAll architectures

User Space Feature Integration

The uspace feature enables system call handling across all architectures:

flowchart TD
subgraph subGraph1["Architecture Integration"]
    X86_SYSCALL["x86_64: SYSCALL instructionGS_BASE, CR3 management"]
    ARM_SYSCALL["aarch64: SVC instructionTTBR0 user page tables"]
    RISC_SYSCALL["riscv: ECALL instructionSATP register switching"]
    LOONG_SYSCALL["loongarch64: SYSCALLPGDL page directory"]
end
subgraph subGraph0["uspace Feature"]
    FEATURE_FLAG["#[cfg(feature = uspace)]Conditional compilation"]
    SYSCALL_SLICE["SYSCALL handler sliceSystem call dispatch"]
    HANDLE_SYSCALL_FN["handle_syscall functionDirect invocation"]
end

FEATURE_FLAG --> SYSCALL_SLICE
HANDLE_SYSCALL_FN --> ARM_SYSCALL
HANDLE_SYSCALL_FN --> LOONG_SYSCALL
HANDLE_SYSCALL_FN --> RISC_SYSCALL
HANDLE_SYSCALL_FN --> X86_SYSCALL
SYSCALL_SLICE --> HANDLE_SYSCALL_FN

Sources: src/trap.rs(L19 - L22)  src/trap.rs(L41 - L44) 

Common Memory Management Abstractions

The library integrates with external crates to provide consistent memory management abstractions across architectures.

External Dependencies Integration

flowchart TD
subgraph subGraph2["Architecture Implementation"]
    MMU_INIT["init_mmu functionsArchitecture-specific setup"]
    PAGE_TABLES["Page table managementArchitecture-specific formats"]
    TLB_MGMT["TLB managementTranslation cache control"]
end
subgraph subGraph1["Cross-Architecture Usage"]
    VIRT_ADDR["VirtAddrVirtual address type"]
    PAGE_FAULT_FLAGS["PageFaultFlagsAlias for MappingFlags"]
    TRAP_HANDLERS["Distributed trap handlersCompile-time registration"]
end
subgraph subGraph0["External Crate Integration"]
    MEMORY_ADDR["memory_addr crateVirtAddr, PhysAddr types"]
    PAGE_TABLE_ENTRY["page_table_entry crateMappingFlags abstraction"]
    LINKME["linkme crateDistributed slice collection"]
end

LINKME --> TRAP_HANDLERS
MEMORY_ADDR --> VIRT_ADDR
PAGE_FAULT_FLAGS --> PAGE_TABLES
PAGE_TABLE_ENTRY --> PAGE_FAULT_FLAGS
TRAP_HANDLERS --> TLB_MGMT
VIRT_ADDR --> MMU_INIT

Sources: src/lib.rs(L9)  src/trap.rs(L3)  src/trap.rs(L8)  src/trap.rs(L6) 

Error Handling and Warnings

The cross-architecture framework includes built-in error handling and diagnostic capabilities that work consistently across all supported architectures.

Handler Registration Validation

The trap handling system validates handler registration at runtime and provides warnings for common configuration issues:

flowchart TD
subgraph subGraph0["Handler Validation Flow"]
    CHECK_ITER["Check handler iterator"]
    FIRST_HANDLER["Get first handler"]
    CHECK_MORE["Check for additional handlers"]
    WARN_MULTIPLE["Warn about multiple handlers"]
    INVOKE["Invoke selected handler"]
    NO_HANDLER["Warn about missing handlers"]
end

CHECK_ITER --> FIRST_HANDLER
CHECK_ITER --> NO_HANDLER
CHECK_MORE --> INVOKE
CHECK_MORE --> WARN_MULTIPLE
FIRST_HANDLER --> CHECK_MORE
NO_HANDLER --> INVOKE

The system issues specific warning messages for:

  • Multiple handlers registered for the same trap type
  • No handlers registered for a trap that occurs
  • Feature mismatches between compile-time and runtime expectations

Sources: src/trap.rs(L25 - L38) 

User Space Support

Relevant source files

This document covers the user space support functionality provided by the axcpu library, which enables transitions from kernel mode to user mode across supported architectures. This feature allows operating systems built on axcpu to execute user programs in unprivileged mode while maintaining proper isolation and control.

For architecture-specific trap handling that occurs when transitioning back from user space, see the trap handling sections for each architecture (2.2, 3.2, 4.2). For general context management during task switching, see the context management sections (2.1, 3.1, 4.1, 5.1).

Architecture Support Overview

The user space support is implemented through the uspace feature and provides a consistent interface across multiple architectures. Each supported architecture implements the functionality through a dedicated uspace.rs module that provides the UspaceContext structure and associated methods.

flowchart TD
subgraph subGraph3["User Space Support Architecture"]
    USPACE_FEAT["uspace feature flag"]
    subgraph subGraph2["Common Interface"]
        EMPTY["empty()"]
        NEW["new()"]
        FROM["from()"]
        GET_IP["get_ip()"]
        GET_SP["get_sp()"]
        SET_IP["set_ip()"]
        SET_SP["set_sp()"]
        SET_RETVAL["set_retval()"]
    end
    subgraph subGraph1["Core Components"]
        USPACE_CTX["UspaceContext struct"]
        TRAP_FRAME["TrapFrame wrapper"]
        ENTER_USPACE["enter_uspace() method"]
    end
    subgraph subGraph0["Architecture Implementations"]
        RISCV_USPACE["src/riscv/uspace.rsUspaceContext"]
        AARCH64_USPACE["src/aarch64/uspace.rsUspaceContext"]
        LOONGARCH64_USPACE["src/loongarch64/uspace.rsUspaceContext"]
    end
end

AARCH64_USPACE --> USPACE_CTX
LOONGARCH64_USPACE --> USPACE_CTX
RISCV_USPACE --> USPACE_CTX
USPACE_CTX --> EMPTY
USPACE_CTX --> ENTER_USPACE
USPACE_CTX --> FROM
USPACE_CTX --> GET_IP
USPACE_CTX --> GET_SP
USPACE_CTX --> NEW
USPACE_CTX --> SET_IP
USPACE_CTX --> SET_RETVAL
USPACE_CTX --> SET_SP
USPACE_CTX --> TRAP_FRAME
USPACE_FEAT --> AARCH64_USPACE
USPACE_FEAT --> LOONGARCH64_USPACE
USPACE_FEAT --> RISCV_USPACE

Sources: src/riscv/uspace.rs(L1 - L104)  src/aarch64/uspace.rs(L1 - L113)  src/loongarch64/uspace.rs(L1 - L98) 

UspaceContext Structure

The UspaceContext is implemented as a wrapper around the architecture-specific TrapFrame structure. This design provides a unified interface while leveraging the existing trap frame infrastructure for context management.

ArchitectureUspaceContext DefinitionUnderlying TrapFrame
RISC-Vpub struct UspaceContext(TrapFrame)ContainsGeneralRegisters,sepc,sstatus
AArch64pub struct UspaceContext(TrapFrame)Contains register arrayr[31],usp,elr,spsr
LoongArch64pub struct UspaceContext(TrapFrame)Contains registers,era,prmd

Sources: src/riscv/uspace.rs(L8)  src/aarch64/uspace.rs(L8)  src/loongarch64/uspace.rs(L8) 

Context Creation and Management

Each architecture provides consistent methods for creating and manipulating user space contexts:

Context Creation Methods

flowchart TD
subgraph subGraph1["Implementation Details"]
    EMPTY_ZERO["All registers zeroed"]
    NEW_SETUP["Entry point + stack + argument configured"]
    FROM_COPY["Copy from existing TrapFrame"]
end
subgraph subGraph0["UspaceContext Creation"]
    EMPTY["UspaceContext::empty()"]
    NEW["UspaceContext::new(entry, ustack_top, arg0)"]
    FROM["UspaceContext::from(trap_frame)"]
end

EMPTY --> EMPTY_ZERO
FROM --> FROM_COPY
NEW --> NEW_SETUP

Sources: src/riscv/uspace.rs(L12 - L35)  src/aarch64/uspace.rs(L12 - L38)  src/loongarch64/uspace.rs(L12 - L32) 

The new() method performs architecture-specific initialization:

Context Access Methods

All architectures provide consistent getter and setter methods:

MethodPurposeRISC-VAArch64LoongArch64
get_ip()Get instruction pointerself.0.sepcself.0.elrself.0.era
get_sp()Get stack pointerself.0.regs.spself.0.uspself.0.regs.sp
set_ip()Set instruction pointerself.0.sepc = pcself.0.elr = pcself.0.era = pc
set_sp()Set stack pointerself.0.regs.sp = spself.0.usp = spself.0.regs.sp = sp
set_retval()Set return valueself.0.regs.a0 = a0self.0.r[0] = r0self.0.regs.a0 = a0

Sources: src/riscv/uspace.rs(L37 - L61)  src/aarch64/uspace.rs(L40 - L63)  src/loongarch64/uspace.rs(L34 - L57) 

User Space Entry Process

The enter_uspace() method performs the critical transition from kernel mode to user mode. This is an unsafe operation that fundamentally changes the processor's execution context.

flowchart TD
subgraph subGraph1["Architecture-Specific Details"]
    RISCV_IMPL["RISC-V: sscratch, sepc, sret"]
    AARCH64_IMPL["AArch64: sp_el0, elr_el1, spsr_el1, eret"]
    LOONGARCH64_IMPL["LoongArch64: era, PRMD, ertn"]
end
subgraph subGraph0["enter_uspace() Flow"]
    START["enter_uspace(kstack_top)"]
    DISABLE_IRQ["Disable interrupts"]
    SETUP_KERNEL_STACK["Setup kernel stack for trap handling"]
    CONFIGURE_ARCH["Architecture-specific register setup"]
    RESTORE_CONTEXT["Restore user context from TrapFrame"]
    SWITCH_MODE["Switch to user mode"]
    USER_EXEC["Execute user code"]
    TRAP_RETURN["[On trap/syscall] Return to kernel"]
end

CONFIGURE_ARCH --> AARCH64_IMPL
CONFIGURE_ARCH --> LOONGARCH64_IMPL
CONFIGURE_ARCH --> RESTORE_CONTEXT
CONFIGURE_ARCH --> RISCV_IMPL
DISABLE_IRQ --> SETUP_KERNEL_STACK
RESTORE_CONTEXT --> SWITCH_MODE
SETUP_KERNEL_STACK --> CONFIGURE_ARCH
START --> DISABLE_IRQ
SWITCH_MODE --> USER_EXEC
USER_EXEC --> TRAP_RETURN

Sources: src/riscv/uspace.rs(L72 - L102)  src/aarch64/uspace.rs(L75 - L111)  src/loongarch64/uspace.rs(L69 - L96) 

Architecture-Specific Entry Implementation

Each architecture implements the final transition using inline assembly:

RISC-V Implementation:

AArch64 Implementation:

LoongArch64 Implementation:

Cross-Architecture Design Patterns

The user space support demonstrates several consistent design patterns across architectures:

Trap Frame Reuse

All implementations leverage the existing TrapFrame structure rather than defining separate user context formats. This provides consistency with the trap handling infrastructure and ensures that user contexts contain all necessary state for exception handling.

Kernel Stack Management

Each architecture properly configures the kernel stack pointer to handle subsequent traps or system calls from user mode:

Register State Initialization

The new() method in each architecture sets appropriate processor state flags for user mode execution, ensuring proper privilege levels and interrupt handling configuration.

Sources: src/riscv/uspace.rs(L1 - L104)  src/aarch64/uspace.rs(L1 - L113)  src/loongarch64/uspace.rs(L1 - L98) 

Core Trap Handling Framework

Relevant source files

Purpose and Scope

The Core Trap Handling Framework provides a unified, cross-architecture mechanism for registering and dispatching trap handlers in axcpu. This framework enables external code to register handlers for interrupts, page faults, and system calls without needing to know architecture-specific details. The framework uses Rust's linkme crate to create distributed slices that collect handlers at link time.

For architecture-specific trap handling implementations, see x86_64 Trap and Exception Handling, AArch64 Trap and Exception Handling, RISC-V Trap and Exception Handling, and related sections. For user space system call support, see User Space Support.

Handler Registration Mechanism

The framework uses linkme::distributed_slice to create static collections of handler functions that are populated at link time. This allows modules throughout the codebase to register handlers without requiring explicit registration calls.

Handler Registration Architecture

flowchart TD
subgraph subGraph2["External Modules"]
    EXT_IRQ["External IRQ Handler"]
    EXT_PF["External Page Fault Handler"]
    EXT_SYS["External Syscall Handler"]
end
subgraph subGraph1["Static Handler Collections"]
    IRQ_SLICE["IRQ: [fn(usize) -> bool]"]
    PF_SLICE["PAGE_FAULT: [fn(VirtAddr, PageFaultFlags, bool) -> bool]"]
    SYSCALL_SLICE["SYSCALL: [fn(&TrapFrame, usize) -> isize]"]
end
subgraph subGraph0["Handler Registration"]
    LINKME["linkme::distributed_slice"]
    DEF_MACRO["def_trap_handler macro"]
    REG_MACRO["register_trap_handler macro"]
end

DEF_MACRO --> IRQ_SLICE
DEF_MACRO --> PF_SLICE
DEF_MACRO --> SYSCALL_SLICE
EXT_IRQ --> IRQ_SLICE
EXT_PF --> PF_SLICE
EXT_SYS --> SYSCALL_SLICE
LINKME --> DEF_MACRO
LINKME --> REG_MACRO

Sources: src/trap.rs(L6 - L7)  src/trap.rs(L11 - L22) 

The framework exports two key macros:

  • def_trap_handler - Used internally to define handler collections
  • register_trap_handler - Used by external code to register handlers

Trap Types and Handler Signatures

The framework defines three primary trap types, each with specific handler signatures optimized for their use cases.

Trap TypeHandler SignaturePurposeFeature Gate
IRQfn(usize) -> boolHardware interrupt handlingAlways available
PAGE_FAULTfn(VirtAddr, PageFaultFlags, bool) -> boolMemory access violationsAlways available
SYSCALLfn(&TrapFrame, usize) -> isizeSystem call handlinguspacefeature

Trap Handler Details

flowchart TD
subgraph subGraph1["Page Fault Handler"]
    SYS_FRAME["TrapFrame"]
    SYS_RESULT["Return Value (isize)"]
    PF_ADDR["Virtual Address"]
    PF_USER["User Space (bool)"]
    IRQ_NUM["IRQ Number (usize)"]
    IRQ_RESULT["Handled (bool)"]
    subgraph subGraph2["Syscall Handler"]
        SYS_NUM["Syscall Number"]
        PF_RESULT["Handled (bool)"]
        PF_FLAGS["PageFaultFlags"]
        subgraph subGraph0["IRQ Handler"]
            SYS_FRAME["TrapFrame"]
            SYS_RESULT["Return Value (isize)"]
            PF_ADDR["Virtual Address"]
            IRQ_NUM["IRQ Number (usize)"]
            IRQ_RESULT["Handled (bool)"]
        end
    end
end

IRQ_NUM --> IRQ_RESULT
PF_ADDR --> PF_RESULT
PF_FLAGS --> PF_RESULT
PF_USER --> PF_RESULT
SYS_FRAME --> SYS_RESULT
SYS_NUM --> SYS_RESULT

Sources: src/trap.rs(L12)  src/trap.rs(L16)  src/trap.rs(L22) 

Trap Dispatching Framework

The framework provides a unified dispatching mechanism through the handle_trap macro and specialized functions for different trap types.

Dispatch Mechanism

flowchart TD
subgraph subGraph1["Error Handling"]
    MULTI_WARN["Multiple Handler Warning"]
    NO_HANDLER["No Handler Warning"]
end
subgraph subGraph0["Trap Dispatch Flow"]
    ARCH_TRAP["Architecture-Specific Trap Entry"]
    HANDLE_MACRO["handle_trap! macro"]
    HANDLER_LOOKUP["Handler Lookup"]
    SINGLE_CHECK["Single Handler Check"]
    HANDLER_CALL["Handler Function Call"]
end
subgraph subGraph2["Specialized Functions"]
    SYSCALL_FUNC["handle_syscall()"]
    DIRECT_CALL["Direct SYSCALL[0] call"]
end

ARCH_TRAP --> HANDLE_MACRO
HANDLER_LOOKUP --> NO_HANDLER
HANDLER_LOOKUP --> SINGLE_CHECK
HANDLE_MACRO --> HANDLER_LOOKUP
SINGLE_CHECK --> HANDLER_CALL
SINGLE_CHECK --> MULTI_WARN
SYSCALL_FUNC --> DIRECT_CALL

Sources: src/trap.rs(L25 - L38)  src/trap.rs(L42 - L44) 

Macro Implementation

The handle_trap macro provides a standardized way to dispatch to registered handlers:

  • Retrieves the handler slice for the specified trap type
  • Checks for exactly one registered handler
  • Issues warnings for multiple or missing handlers
  • Calls the handler with the provided arguments

System Call Specialization

System calls receive special handling through the handle_syscall function, which directly calls the first (and expected only) syscall handler without the overhead of the generic dispatch mechanism.

Sources: src/trap.rs(L41 - L44) 

Integration with Architecture-Specific Code

The Core Trap Handling Framework serves as the interface between architecture-specific trap entry points and higher-level system components.

Architecture Integration Flow

flowchart TD
subgraph subGraph2["System Components"]
    KERNEL["Kernel Services"]
    DRIVER["Device Drivers"]
    SYSCALL_SVC["System Call Service"]
end
subgraph subGraph1["Core Framework"]
    TRAP_MACRO["handle_trap! macro"]
    IRQ_HANDLERS["IRQ handler slice"]
    PF_HANDLERS["PAGE_FAULT handler slice"]
    SYS_HANDLERS["SYSCALL handler slice"]
end
subgraph subGraph0["Architecture Layer"]
    X86_TRAP["x86_64 trap handlers"]
    ARM_TRAP["aarch64 trap handlers"]
    RISCV_TRAP["riscv trap handlers"]
    LOONG_TRAP["loongarch64 trap handlers"]
end

ARM_TRAP --> TRAP_MACRO
IRQ_HANDLERS --> DRIVER
LOONG_TRAP --> TRAP_MACRO
PF_HANDLERS --> KERNEL
RISCV_TRAP --> TRAP_MACRO
SYS_HANDLERS --> SYSCALL_SVC
TRAP_MACRO --> IRQ_HANDLERS
TRAP_MACRO --> PF_HANDLERS
TRAP_MACRO --> SYS_HANDLERS
X86_TRAP --> TRAP_MACRO

Sources: src/lib.rs(L12)  src/lib.rs(L15 - L27) 

Type System Integration

The framework integrates with core axcpu types:

  • TrapFrame - Architecture-specific CPU state during traps
  • VirtAddr - Virtual memory addresses from the memory_addr crate
  • PageFaultFlags - Memory access flags from page_table_entry crate

This type integration ensures that handlers receive properly structured data regardless of the underlying architecture.

Sources: src/trap.rs(L3)  src/trap.rs(L5)  src/trap.rs(L8) 

Development and Build Configuration

Relevant source files

This document covers the build system, dependency management, and development environment configuration for the axcpu library. It explains how to set up the development environment, configure build targets, and understand the feature-based compilation system.

For information about architecture-specific implementations, see the respective architecture pages (x86_64, AArch64, RISC-V, LoongArch64). For details about cross-architecture features and their runtime behavior, see Cross-Architecture Features.

Build System Overview

The axcpu library uses Cargo as its build system with a sophisticated feature-based configuration that enables conditional compilation for different architectures and capabilities. The build system is designed to support both bare-metal (no_std) environments and multiple target architectures simultaneously.

Package Configuration

flowchart TD
subgraph Categories["Categories"]
    EMBEDDED["embedded"]
    NOSTD_CAT["no-std"]
    HWSUPPORT["hardware-support"]
    OS["os"]
end
subgraph subGraph1["Build Requirements"]
    RUST["Rust 1.88.0+rust-version requirement"]
    NOSTD["no_std EnvironmentBare metal support"]
    TARGETS["Multiple TargetsCross-compilation ready"]
end
subgraph subGraph0["Package Structure"]
    PKG["axcpu Packageversion: 0.1.1edition: 2021"]
    DESC["Multi-arch CPU abstractionGPL-3.0 | Apache-2.0 | MulanPSL-2.0"]
    REPO["Repositorygithub.com/arceos-org/axcpu"]
end

PKG --> DESC
PKG --> EMBEDDED
PKG --> HWSUPPORT
PKG --> NOSTD
PKG --> NOSTD_CAT
PKG --> OS
PKG --> REPO
PKG --> RUST
PKG --> TARGETS

The package configuration establishes axcpu as a foundational library in the embedded and OS development ecosystem, with specific version requirements and licensing terms.

Sources: Cargo.toml(L1 - L21) 

Feature Flag System

The axcpu library uses a feature-based compilation system that allows selective inclusion of functionality based on target requirements and capabilities.

Feature Flag Architecture

flowchart TD
subgraph subGraph2["Architecture Integration"]
    X86_FP["x86_64: FXSAVE/FXRSTORSSE/AVX support"]
    ARM_FP["aarch64: V-registersSIMD operations"]
    RISC_FP["riscv: Future FP supportExtension hooks"]
    LOONG_FP["loongarch64: F-registersFPU state management"]
    X86_TLS["x86_64: FS_BASESegment-based TLS"]
    ARM_TLS["aarch64: TPIDR_EL0Thread pointer register"]
    RISC_TLS["riscv: TP registerThread pointer"]
    LOONG_TLS["loongarch64: TP registerThread pointer"]
    X86_US["x86_64: CR3 + GS_BASESyscall interface"]
    ARM_US["aarch64: TTBR0EL0 support"]
    RISC_US["riscv: SATPSystem calls"]
    LOONG_US["loongarch64: PGDLSystem calls"]
end
subgraph subGraph1["Core Features"]
    DEFAULT["default = []Minimal feature set"]
    subgraph subGraph0["Optional Features"]
        FPSIMD["fp-simdFloating-point & SIMD"]
        TLS["tlsThread Local Storage"]
        USPACE["uspaceUser space support"]
    end
end

FPSIMD --> ARM_FP
FPSIMD --> LOONG_FP
FPSIMD --> RISC_FP
FPSIMD --> X86_FP
TLS --> ARM_TLS
TLS --> LOONG_TLS
TLS --> RISC_TLS
TLS --> X86_TLS
USPACE --> ARM_US
USPACE --> LOONG_US
USPACE --> RISC_US
USPACE --> X86_US

Each feature flag enables specific functionality across all supported architectures, with architecture-specific implementations handling the low-level details.

Sources: Cargo.toml(L23 - L27) 

Dependency Management

The dependency structure is organized into common dependencies and architecture-specific dependencies that are conditionally included based on the target architecture.

Common Dependencies

flowchart TD
subgraph subGraph1["Core Functionality"]
    AXCPU["axcpu Library"]
    CONTEXT["Context Management"]
    TRAP["Trap Handling"]
    INIT["System Initialization"]
end
subgraph subGraph0["Universal Dependencies"]
    LINKME["linkme = 0.3Static registration"]
    LOG["log = 0.4Logging framework"]
    CFGIF["cfg-if = 1.0Conditional compilation"]
    MEMADDR["memory_addr = 0.3Address types"]
    PTE["page_table_entry = 0.5Page table abstractions"]
    STATIC["static_assertions = 1.1.0Compile-time checks"]
end

CFGIF --> CONTEXT
LINKME --> TRAP
LOG --> AXCPU
MEMADDR --> CONTEXT
MEMADDR --> TRAP
PTE --> INIT
STATIC --> AXCPU

These dependencies provide foundational functionality used across all architectures, including memory management abstractions, logging, and compile-time utilities.

Sources: Cargo.toml(L29 - L35) 

Architecture-Specific Dependencies

flowchart TD
subgraph subGraph4["Target Conditions"]
    X86_TARGET["cfg(target_arch = x86_64)"]
    ARM_TARGET["cfg(target_arch = aarch64)"]
    RISC_TARGET["cfg(any(riscv32, riscv64))"]
    LOONG_TARGET["cfg(target_arch = loongarch64)"]
end
subgraph subGraph3["LoongArch64 Dependencies"]
    LOONG["loongArch64 = 0.2.4LoongArch ISA"]
    PTMULTI["page_table_multiarch = 0.5Multi-arch page tables"]
end
subgraph subGraph2["RISC-V Dependencies"]
    RISCV["riscv = 0.14RISC-V ISA support"]
end
subgraph subGraph1["AArch64 Dependencies"]
    AARCH64["aarch64-cpu = 10.0ARM64 CPU features"]
    TOCK["tock-registers = 0.9Register abstractions"]
end
subgraph subGraph0["x86_64 Dependencies"]
    X86["x86 = 0.52x86 instruction wrappers"]
    X86_64["x86_64 = 0.15.2x86_64 structures"]
    PERCPU["percpu = 0.2Per-CPU variables"]
    LAZY["lazyinit = 0.2Lazy initialization"]
end

ARM_TARGET --> AARCH64
ARM_TARGET --> TOCK
LOONG_TARGET --> LOONG
LOONG_TARGET --> PTMULTI
RISC_TARGET --> RISCV
X86_TARGET --> LAZY
X86_TARGET --> PERCPU
X86_TARGET --> X86
X86_TARGET --> X86_64

Each architecture includes specific dependencies that provide low-level access to architecture-specific features, instruction sets, and register management.

Sources: Cargo.toml(L37 - L52) 

Target Architecture Configuration

The build system supports multiple target architectures with specific configurations for documentation and cross-compilation.

Documentation Targets

The library is configured to build documentation for all supported target architectures:

TargetPurposeABI
x86_64-unknown-nonex86_64 bare metalHard float
aarch64-unknown-none-softfloatARM64 bare metalSoft float
riscv64gc-unknown-none-elfRISC-V 64-bitGeneral+Compressed
loongarch64-unknown-none-softfloatLoongArch64 bare metalSoft float
flowchart TD
subgraph subGraph2["Cross-Compilation Support"]
    RUSTC["rustc target support"]
    CARGO["cargo build system"]
    TOOLCHAIN["Architecture toolchains"]
end
subgraph subGraph1["Documentation Build"]
    ALL_FEATURES["all-features = trueComplete API coverage"]
    DOCS_RS["docs.rs integrationOnline documentation"]
end
subgraph subGraph0["Build Targets"]
    X86_TARGET["x86_64-unknown-noneIntel/AMD 64-bit"]
    ARM_TARGET["aarch64-unknown-none-softfloatARM 64-bit softfloat"]
    RISC_TARGET["riscv64gc-unknown-none-elfRISC-V 64-bit G+C"]
    LOONG_TARGET["loongarch64-unknown-none-softfloatLoongArch 64-bit"]
end

ALL_FEATURES --> DOCS_RS
ARM_TARGET --> ALL_FEATURES
ARM_TARGET --> RUSTC
CARGO --> TOOLCHAIN
LOONG_TARGET --> ALL_FEATURES
LOONG_TARGET --> RUSTC
RISC_TARGET --> ALL_FEATURES
RISC_TARGET --> RUSTC
RUSTC --> CARGO
X86_TARGET --> ALL_FEATURES
X86_TARGET --> RUSTC

Sources: Cargo.toml(L57 - L59) 

Development Workflow

Setting Up the Development Environment

  1. Rust Toolchain: Ensure Rust 1.88.0 or later is installed
  2. Target Installation: Install required target architectures using rustup target add
  3. Cross-Compilation Tools: Install architecture-specific toolchains if needed
  4. Feature Testing: Use cargo build --features to test specific feature combinations

Build Commands

# Build with all features
cargo build --all-features

# Build for specific architecture
cargo build --target x86_64-unknown-none

# Build with specific features
cargo build --features "fp-simd,tls,uspace"

# Generate documentation
cargo doc --all-features --open

Linting Configuration

The project includes specific linting rules to maintain code quality:

flowchart TD
subgraph subGraph1["Code Quality"]
    STATIC_ASSERT["static_assertionsCompile-time verification"]
    RUSTFMT["rustfmtCode formatting"]
    CHECKS["Automated checksCI/CD pipeline"]
end
subgraph subGraph0["Clippy Configuration"]
    CLIPPY["clippy lints"]
    NEWDEFAULT["new_without_default = allowConstructor pattern flexibility"]
end

CLIPPY --> NEWDEFAULT
CLIPPY --> STATIC_ASSERT
RUSTFMT --> CHECKS
STATIC_ASSERT --> RUSTFMT

Sources: Cargo.toml(L54 - L55) 

The development and build configuration ensures consistent behavior across all supported architectures while providing flexibility for different deployment scenarios and feature requirements.

Dependencies and Package Structure

Relevant source files

This document explains the dependency structure and package organization of the axcpu library. It covers the external crates that provide architecture-specific functionality, common utilities, and abstractions used across all supported architectures. For detailed information about specific architecture implementations, see the individual architecture sections (x86_64, AArch64, RISC-V, LoongArch64).

Core Package Structure

The axcpu crate is organized as a single package that provides multi-architecture CPU abstraction through conditional compilation and architecture-specific dependencies. The library selectively includes dependencies based on the target architecture being compiled for.

Main Dependency Categories

flowchart TD
subgraph subGraph4["axcpu Package Structure"]
    AXCPU["axcpu v0.1.1Main Package"]
    subgraph subGraph3["Build Support"]
        CFG_IF["cfg-if v1.0.1Conditional compilation"]
        STATIC_ASSERTIONS["static_assertions v1.1.0Compile-time checks"]
        LOG["log v0.4.27Logging facade"]
    end
    subgraph subGraph2["System Utilities"]
        PERCPU["percpu v0.2.0Per-CPU data structures"]
        LAZYINIT["lazyinit v0.2.2Lazy initialization"]
        LINKME["linkme v0.3.33Linker-based collections"]
        TOCK_REGISTERS["tock-registers v0.9.0Register field access"]
    end
    subgraph subGraph1["Memory Management"]
        MEMORY_ADDR["memory_addr v0.3.2Address type abstractions"]
        PAGE_TABLE_ENTRY["page_table_entry v0.5.3Page table entry types"]
        PAGE_TABLE_MULTI["page_table_multiarch v0.5.3Multi-arch page tables"]
    end
    subgraph subGraph0["Architecture Support Crates"]
        X86["x86 v0.52.0x86 CPU abstractions"]
        X86_64["x86_64 v0.15.2x86_64 specific features"]
        AARCH64["aarch64-cpu v10.0.0ARM64 register access"]
        RISCV["riscv v0.14.0RISC-V CSR and instructions"]
        LOONGARCH["loongArch64 v0.2.5LoongArch64 support"]
    end
end

AXCPU --> AARCH64
AXCPU --> CFG_IF
AXCPU --> LAZYINIT
AXCPU --> LINKME
AXCPU --> LOG
AXCPU --> LOONGARCH
AXCPU --> MEMORY_ADDR
AXCPU --> PAGE_TABLE_ENTRY
AXCPU --> PAGE_TABLE_MULTI
AXCPU --> PERCPU
AXCPU --> RISCV
AXCPU --> STATIC_ASSERTIONS
AXCPU --> TOCK_REGISTERS
AXCPU --> X86
AXCPU --> X86_64

Sources: Cargo.lock(L21 - L39) 

Architecture-Specific Dependencies

Each supported architecture relies on specialized crates that provide low-level access to CPU features, registers, and instructions.

x86/x86_64 Dependencies

flowchart TD
subgraph subGraph2["x86_64 Architecture Support"]
    X86_CRATE["x86 v0.52.0"]
    X86_64_CRATE["x86_64 v0.15.2"]
    subgraph subGraph1["x86_64 Dependencies"]
        BITFLAGS_2["bitflags v2.9.1"]
        VOLATILE["volatile v0.4.6"]
        RUSTVERSION["rustversion v1.0.21"]
    end
    subgraph subGraph0["x86 Dependencies"]
        BIT_FIELD["bit_field v0.10.2"]
        BITFLAGS_1["bitflags v1.3.2"]
        RAW_CPUID["raw-cpuid v10.7.0"]
    end
end

X86_64_CRATE --> BITFLAGS_2
X86_64_CRATE --> BIT_FIELD
X86_64_CRATE --> RUSTVERSION
X86_64_CRATE --> VOLATILE
X86_CRATE --> BITFLAGS_1
X86_CRATE --> BIT_FIELD
X86_CRATE --> RAW_CPUID
CratePurposeKey Features
x8632-bit x86 supportCPU ID, MSR access, I/O port operations
x86_6464-bit extensionsPage table management, 64-bit registers, VirtAddr/PhysAddr
raw-cpuidCPU feature detectionCPUID instruction wrapper
volatileMemory access controlPrevents compiler optimizations on memory operations

Sources: Cargo.lock(L315 - L335) 

AArch64 Dependencies

flowchart TD
subgraph subGraph0["AArch64 Architecture Support"]
    AARCH64_CRATE["aarch64-cpu v10.0.0"]
    TOCK_REG["tock-registers v0.9.0"]
end

AARCH64_CRATE --> TOCK_REG

The aarch64-cpu crate provides register access patterns and system control for ARM64 processors, built on top of the tock-registers framework for type-safe register field manipulation.

Sources: Cargo.lock(L6 - L12) 

RISC-V Dependencies

flowchart TD
subgraph subGraph1["RISC-V Architecture Support"]
    RISCV_CRATE["riscv v0.14.0"]
    subgraph subGraph0["RISC-V Dependencies"]
        CRITICAL_SECTION["critical-section v1.2.0"]
        EMBEDDED_HAL["embedded-hal v1.0.0"]
        PASTE["paste v1.0.15"]
        RISCV_MACROS["riscv-macros v0.2.0"]
        RISCV_PAC["riscv-pac v0.2.0"]
    end
end

RISCV_CRATE --> CRITICAL_SECTION
RISCV_CRATE --> EMBEDDED_HAL
RISCV_CRATE --> PASTE
RISCV_CRATE --> RISCV_MACROS
RISCV_CRATE --> RISCV_PAC
ComponentPurpose
riscv-macrosProcedural macros for CSR access
riscv-pacPeripheral Access Crate for RISC-V
critical-sectionInterrupt-safe critical sections
pasteToken pasting for macro generation

Sources: Cargo.lock(L229 - L239) 

LoongArch64 Dependencies

flowchart TD
subgraph subGraph1["LoongArch64 Architecture Support"]
    LOONGARCH_CRATE["loongArch64 v0.2.5"]
    subgraph subGraph0["LoongArch Dependencies"]
        BIT_FIELD_LA["bit_field v0.10.2"]
        BITFLAGS_LA["bitflags v2.9.1"]
    end
end

LOONGARCH_CRATE --> BITFLAGS_LA
LOONGARCH_CRATE --> BIT_FIELD_LA

The LoongArch64 support uses bit manipulation utilities for register and instruction field access.

Sources: Cargo.lock(L120 - L127) 

Memory Management Dependencies

The memory management subsystem relies on a set of interconnected crates that provide address abstractions and page table management across architectures.

flowchart TD
subgraph subGraph2["Memory Management Dependency Chain"]
    PAGE_TABLE_ENTRY_CRATE["page_table_entry v0.5.3PTE abstractions"]
    PAGE_TABLE_MULTI_CRATE["page_table_multiarch v0.5.3Multi-arch page tables"]
    MEMORY_ADDR_CRATE["memory_addr v0.3.2VirtAddr, PhysAddr types"]
    subgraph subGraph1["Multi-arch Page Table Dependencies"]
        PTM_LOG["log"]
        PTM_MEMORY_ADDR["memory_addr"]
        PTM_PAGE_TABLE_ENTRY["page_table_entry"]
        PTM_RISCV["riscv v0.12.1"]
        PTM_X86["x86"]
    end
    subgraph subGraph0["Page Table Entry Dependencies"]
        PTE_AARCH64["aarch64-cpu"]
        PTE_X86_64["x86_64"]
        PTE_BITFLAGS["bitflags v2.9.1"]
        PTE_MEMORY_ADDR["memory_addr"]
    end
end

MEMORY_ADDR_CRATE --> PAGE_TABLE_ENTRY_CRATE
PAGE_TABLE_ENTRY_CRATE --> PAGE_TABLE_MULTI_CRATE
PAGE_TABLE_ENTRY_CRATE --> PTE_AARCH64
PAGE_TABLE_ENTRY_CRATE --> PTE_BITFLAGS
PAGE_TABLE_ENTRY_CRATE --> PTE_MEMORY_ADDR
PAGE_TABLE_ENTRY_CRATE --> PTE_X86_64
PAGE_TABLE_MULTI_CRATE --> PTM_LOG
PAGE_TABLE_MULTI_CRATE --> PTM_MEMORY_ADDR
PAGE_TABLE_MULTI_CRATE --> PTM_PAGE_TABLE_ENTRY
PAGE_TABLE_MULTI_CRATE --> PTM_RISCV
PAGE_TABLE_MULTI_CRATE --> PTM_X86

Sources: Cargo.lock(L130 - L158) 

System Utility Dependencies

Per-CPU Data Management

flowchart TD
subgraph subGraph2["percpu Crate Structure"]
    PERCPU_CRATE["percpu v0.2.0Per-CPU data structures"]
    subgraph subGraph1["Macro Dependencies"]
        PROC_MACRO2["proc-macro2 v1.0.95"]
        QUOTE["quote v1.0.40"]
        SYN["syn v2.0.104"]
    end
    subgraph subGraph0["percpu Dependencies"]
        PERCPU_CFG_IF["cfg-if v1.0.1"]
        PERCPU_MACROS["percpu_macros v0.2.0"]
        PERCPU_SPIN["spin v0.9.8"]
        PERCPU_X86["x86 v0.52.0"]
    end
end

PERCPU_CRATE --> PERCPU_CFG_IF
PERCPU_CRATE --> PERCPU_MACROS
PERCPU_CRATE --> PERCPU_SPIN
PERCPU_CRATE --> PERCPU_X86
PERCPU_MACROS --> PROC_MACRO2
PERCPU_MACROS --> QUOTE
PERCPU_MACROS --> SYN

The percpu crate provides architecture-aware per-CPU data structures with x86-specific optimizations for CPU-local storage access.

Sources: Cargo.lock(L167 - L187) 

Linker-Based Collections

The linkme crate enables compile-time collection of distributed static data, used for registering trap handlers and other system components across architecture modules.

CratePurposeDependencies
linkmeDistributed static collectionslinkme-impl
linkme-implImplementation macrosproc-macro2,quote,syn

Sources: Cargo.lock(L84 - L101) 

Conditional Compilation Structure

The axcpu library uses feature-based compilation to include only the dependencies and code paths relevant to the target architecture.


Sources: Cargo.lock(L60 - L63) 

Build-Time Dependencies

Static Assertions and Verification

The static_assertions crate provides compile-time verification of type sizes, alignment requirements, and other invariants critical for low-level CPU context management.

Procedural Macro Infrastructure

Macro CratePurposeUsed By
proc-macro2Procedural macro toolkitlinkme-impl,percpu_macros,riscv-macros
quoteCode generation helperAll macro implementations
synRust syntax parsingAll macro implementations

Sources: Cargo.lock(L190 - L294) 

Version Constraints and Compatibility

The dependency graph maintains compatibility across architecture-specific crates through careful version selection:

  • Bitflags: Uses both v1.3.2 (for x86) and v2.9.1 (for newer crates)
  • RISC-V: Uses v0.14.0 for axcpu, v0.12.1 for page table compatibility
  • Register Access: Standardized on tock-registers v0.9.0 for AArch64

This structure ensures that each architecture module can use the most appropriate version of its dependencies while maintaining overall system compatibility.

Sources: Cargo.lock(L1 - L336) 

Toolchain Configuration

Relevant source files

This document covers the Rust toolchain configuration required for building and developing the axcpu multi-architecture CPU abstraction library. It details the specific compiler targets, development tools, and build requirements needed to support all four target architectures.

For information about the dependency structure and external crates, see Dependencies and Package Structure.

Rust Toolchain Requirements

The axcpu project requires the Rust nightly toolchain to access advanced features needed for low-level systems programming. The toolchain configuration is centrally managed through the rust-toolchain.toml file.

Toolchain Specification

The project uses a minimal nightly toolchain profile with essential development components:

flowchart TD
subgraph subGraph3["Rust Toolchain Configuration"]
    TOOLCHAIN["rust-toolchain.toml"]
    subgraph subGraph2["Target Architectures"]
        X86_TARGET["x86_64-unknown-none"]
        RISC_TARGET["riscv64gc-unknown-none-elf"]
        ARM_TARGET["aarch64-unknown-none-softfloat"]
        LOONG_TARGET["loongarch64-unknown-none-softfloat"]
    end
    subgraph subGraph1["Development Components"]
        RUSTFMT["rustfmtCode formatting"]
        CLIPPY["clippyLinting and analysis"]
    end
    subgraph subGraph0["Core Settings"]
        PROFILE["profile: minimal"]
        CHANNEL["channel: nightly"]
    end
end

TOOLCHAIN --> ARM_TARGET
TOOLCHAIN --> CHANNEL
TOOLCHAIN --> CLIPPY
TOOLCHAIN --> LOONG_TARGET
TOOLCHAIN --> PROFILE
TOOLCHAIN --> RISC_TARGET
TOOLCHAIN --> RUSTFMT
TOOLCHAIN --> X86_TARGET

Toolchain Configuration Mapping

Sources: rust-toolchain.toml(L1 - L10) 

Target Architecture Configuration

Each supported architecture requires a specific Rust target triple that defines the compilation environment and ABI requirements for bare-metal development.

Architecture Target Mapping

ArchitectureTarget TriplePurpose
x86_64x86_64-unknown-none64-bit x86 bare-metal
RISC-Vriscv64gc-unknown-none-elf64-bit RISC-V with compressed instructions
AArch64aarch64-unknown-none-softfloat64-bit ARM with software floating-point
LoongArch64loongarch64-unknown-none-softfloat64-bit LoongArch with software floating-point
flowchart TD
subgraph subGraph5["Architecture to Target Mapping"]
    subgraph subGraph4["Build System"]
        CARGO["cargo build --target"]
        CROSS_COMPILE["Cross-compilation"]
    end
    subgraph subGraph3["LoongArch64 Architecture"]
        LOONG_ARCH["loongarch64 CPU Support"]
        LOONG_TARGET["loongarch64-unknown-none-softfloat"]
    end
    subgraph subGraph2["AArch64 Architecture"]
        ARM_ARCH["aarch64 CPU Support"]
        ARM_TARGET["aarch64-unknown-none-softfloat"]
    end
    subgraph subGraph1["RISC-V Architecture"]
        RISC_ARCH["riscv64 CPU Support"]
        RISC_TARGET["riscv64gc-unknown-none-elf"]
    end
    subgraph subGraph0["x86_64 Architecture"]
        X86_ARCH["x86_64 CPU Support"]
        X86_TARGET["x86_64-unknown-none"]
    end
end

ARM_ARCH --> ARM_TARGET
ARM_TARGET --> CARGO
CARGO --> CROSS_COMPILE
LOONG_ARCH --> LOONG_TARGET
LOONG_TARGET --> CARGO
RISC_ARCH --> RISC_TARGET
RISC_TARGET --> CARGO
X86_ARCH --> X86_TARGET
X86_TARGET --> CARGO

Target Architecture Configuration

Sources: rust-toolchain.toml(L5 - L10) 

Target Characteristics

x86_64-unknown-none

  • Bare-metal x86_64 target without operating system
  • Supports full x86_64 instruction set including SSE/AVX
  • Used for kernel-level CPU abstraction implementations

riscv64gc-unknown-none-elf

  • 64-bit RISC-V with general-purpose and compressed instruction extensions
  • ELF format for bare-metal environments
  • Supports the standard RISC-V calling convention

aarch64-unknown-none-softfloat

  • 64-bit ARM architecture without hardware floating-point
  • Suitable for systems programming where FPU may not be available
  • Uses software emulation for floating-point operations

loongarch64-unknown-none-softfloat

  • 64-bit LoongArch architecture with software floating-point
  • Bare-metal target for the Chinese LoongArch instruction set
  • Supports the LoongArch ABI for systems programming

Sources: rust-toolchain.toml(L6 - L9) 

Development Tools Configuration

The toolchain includes essential development tools for code quality and maintenance across all target architectures.

Included Components

rustfmt

  • Automatic code formatting according to Rust style guidelines
  • Ensures consistent code style across all architecture implementations
  • Configured through rustfmt.toml (if present)

clippy

  • Advanced linting and static analysis tool
  • Catches common programming errors and suggests improvements
  • Particularly important for unsafe code used in low-level CPU operations
flowchart TD
subgraph subGraph2["Development Workflow"]
    SOURCE["Source Code"]
    RUSTFMT["rustfmt"]
    CLIPPY["clippy"]
    COMPILER["rustc"]
    subgraph subGraph1["Target Outputs"]
        X86_LIB["x86_64 Library"]
        RISC_LIB["riscv64 Library"]
        ARM_LIB["aarch64 Library"]
        LOONG_LIB["loongarch64 Library"]
    end
    subgraph subGraph0["Quality Gates"]
        FORMAT_CHECK["Format Check"]
        LINT_CHECK["Lint Analysis"]
        COMPILE_CHECK["Compilation"]
    end
end

CLIPPY --> LINT_CHECK
COMPILER --> COMPILE_CHECK
COMPILE_CHECK --> ARM_LIB
COMPILE_CHECK --> LOONG_LIB
COMPILE_CHECK --> RISC_LIB
COMPILE_CHECK --> X86_LIB
FORMAT_CHECK --> CLIPPY
LINT_CHECK --> COMPILER
RUSTFMT --> FORMAT_CHECK
SOURCE --> RUSTFMT

Development Tools Integration

Sources: rust-toolchain.toml(L4) 

Cross-Compilation Setup

The toolchain configuration enables seamless cross-compilation to all supported architectures from any development host.

Build Process

The rust-toolchain.toml file automatically configures the development environment when developers run rustup in the project directory. This ensures all contributors use the same compiler version and have access to the required target architectures.

Automatic Target Installation

  • Running rustup target add for each specified target
  • Ensures consistent build environment across development machines
  • Eliminates manual toolchain setup requirements

Development Commands

  • cargo build - Build for the host architecture
  • cargo build --target x86_64-unknown-none - Cross-compile for x86_64
  • cargo build --target riscv64gc-unknown-none-elf - Cross-compile for RISC-V
  • cargo build --target aarch64-unknown-none-softfloat - Cross-compile for AArch64
  • cargo build --target loongarch64-unknown-none-softfloat - Cross-compile for LoongArch64

Sources: rust-toolchain.toml(L1 - L10) 

Nightly Features Usage

The nightly toolchain requirement enables access to unstable Rust features essential for systems programming:

  • Inline Assembly: Required for architecture-specific assembly code
  • Custom Target Specifications: Enables bare-metal target definitions
  • Advanced Const Generics: Used for compile-time architecture dispatch
  • Unstable Library Features: Access to experimental standard library APIs

These features are critical for implementing low-level CPU abstractions and context switching routines across different architectures.

Sources: rust-toolchain.toml(L3) 

Overview

Relevant source files

This document introduces the axfs_crates repository, a collection of Rust crates designed to provide filesystem abstractions for embedded and OS development environments. The repository focuses on providing lightweight, modular filesystem components that can be used in both standard and no_std environments.

For detailed information about specific components, see File System Architecture, Virtual File System Interface, Device File System, or RAM File System.

Purpose and Scope

The axfs_crates repository implements a virtual filesystem framework with concrete implementations designed for operating systems and embedded environments. Its key goals are:

  1. Provide a clean, trait-based filesystem abstraction layer
  2. Offer concrete filesystem implementations for common use cases
  3. Support no_std environments for embedded systems development
  4. Maintain a modular design that allows picking only needed components

The framework follows Unix-like filesystem semantics while leveraging Rust's type system and memory safety features.

Sources: README.md(L1 - L10)  Cargo.toml(L1 - L21) 

Repository Structure

The repository consists of three primary crates:

CrateDescription
axfs_vfsDefines the virtual filesystem interfaces and traits that other filesystem implementations must implement
axfs_devfsImplements a device filesystem for managing device files (similar to /dev in Unix systems)
axfs_ramfsImplements a RAM-based filesystem that stores all data in memory

These crates can be used independently or together depending on the needs of the application.

Sources: README.md(L5 - L9)  Cargo.toml(L4 - L8) 

High-Level Architecture

Component Relationships

flowchart TD
A["Applications"]
B["axfs_vfs: Virtual File System Interface"]
C["axfs_devfs: Device File System"]
D["axfs_ramfs: RAM-based File System"]
E["Hardware Devices"]
F["System Memory"]

A --> B
B --> C
B --> D
C --> E
D --> F

The diagram above illustrates the layered architecture of axfs_crates. Applications interact with filesystems through the VFS interface provided by axfs_vfs. The concrete implementations (axfs_devfs and axfs_ramfs) implement this interface and interact with their respective resources.

Sources: README.md(L5 - L9) 

Core Interface Implementation

classDiagram
class VfsNodeOps {
    <<trait>>
    
    +get_attr() -~ VfsResult~VfsNodeAttr~
    +parent() -~ Option~VfsNodeRef~
    +lookup(path: &str) -~ VfsResult~VfsNodeRef~
    +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) -~ VfsResult~usize~
    +read_at(offset: u64, buf: &mut [u8]) -~ VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) -~ VfsResult~usize~
    +create(path: &str, ty: VfsNodeType) -~ VfsResult
    +remove(path: &str) -~ VfsResult
    +truncate(size: u64) -~ VfsResult
}

class VfsOps {
    <<trait>>
    
    +mount(path: &str, mount_point: VfsNodeRef) -~ VfsResult
    +root_dir() -~ VfsNodeRef
}

class DeviceFileSystem {
    
    +new() -~ DeviceFileSystem
    +mkdir(name: &str) -~ Arc
    +add(name: &str, node: VfsNodeRef)
}

class RamFileSystem {
    
    +new() -~ RamFileSystem
}

class DeviceNodes {
    
    
}

class RamNodes {
    
    
}

VfsOps  ..|>  DeviceFileSystem
VfsOps  ..|>  RamFileSystem
VfsNodeOps  ..|>  DeviceNodes
VfsNodeOps  ..|>  RamNodes

This diagram shows the key traits defined in axfs_vfs and how they are implemented by the filesystem implementations. The VfsNodeOps trait defines operations that can be performed on filesystem nodes (files, directories, devices), while the VfsOps trait defines filesystem-level operations.

Sources: README.md(L7 - L9) 

Key Design Characteristics

The axfs_crates architecture embodies several key design characteristics:

  1. Trait-Based Design: Uses Rust traits (VfsNodeOps, VfsOps) to define interfaces, allowing polymorphic behavior and clean separation of concerns.
  2. Reference-Counted Memory Management: Employs Arc (Atomic Reference Counting) and Weak references to manage object lifetimes and prevent circular references.
  3. Hierarchical Structure: Filesystems are organized in a tree-like structure similar to traditional Unix filesystems, with directories containing other directories and files.
  4. Concurrent Access Support: Utilizes synchronization primitives to ensure thread safety in multithreaded environments.
  5. No-std Compatibility: Designed to work in environments without the standard library, making it suitable for embedded systems and operating system development.

Example Filesystem Structure

The axfs_crates framework allows for creating filesystem hierarchies like the following:

flowchart TD
A["Root Directory"]
B["/dev/null"]
C["/dev/zero"]
D["/mnt Directory"]
E["/mnt/data Directory"]
F["/mnt/data/file.txt"]

A --> B
A --> C
A --> D
D --> E
E --> F

This structure shows a typical filesystem arrangement with special device files (/dev/null, /dev/zero) and a mounted filesystem with directories and regular files.

Common Usage Pattern

The typical usage flow for using axfs_crates components looks like:

  1. Create a filesystem instance (DeviceFileSystem, RamFileSystem, etc.)
  2. Obtain the root directory node
  3. Create the desired hierarchy (directories, files)
  4. Perform operations on nodes (read, write, etc.)
  5. Mount other filesystems at specific mount points if needed

For specific implementation details and examples, see the dedicated pages for each component: Virtual File System Interface, Device File System, or RAM File System.

Repository Structure

Relevant source files

This page provides an overview of the organization of the axfs_crates repository, including its workspace setup, available crates, and the relationships between these components. For information about the overall filesystem architecture, see File System Architecture.

Workspace Organization

The axfs_crates repository is structured as a Rust workspace containing three primary crates, each implementing a distinct aspect of the filesystem infrastructure:

flowchart TD
A["axfs_crates Workspace"]
B["axfs_vfs: Virtual File System Interface"]
C["Implements VFS interface"]
D["Implements VFS interface"]

A --> B
A --> C
A --> D
B --> C
B --> D

Title: Repository Workspace Structure

Sources: Cargo.toml(L1 - L8)  README.md(L5 - L9) 

The workspace configuration is defined in the root Cargo.toml file, which specifies the member crates and shared metadata, including version information, author details, and licensing.

Crate Overview

The repository consists of the following crates:

CrateDescriptionRole in the System
axfs_vfsVirtual Filesystem InterfaceDefines traits and interfaces that filesystem implementations must implement
axfs_devfsDevice FilesystemProvides access to system devices through the VFS interface
axfs_ramfsRAM FilesystemImplements an in-memory filesystem using the VFS interface

Sources: README.md(L5 - L9)  Cargo.toml(L4 - L8) 

Dependency Relationships

The crates follow a clear hierarchical relationship, with axfs_vfs serving as the foundation that other filesystems build upon:

flowchart TD
subgraph subGraph1["External Usage"]
    app["Application Code"]
end
subgraph subGraph0["Crate Dependencies"]
    vfs["axfs_vfs: VfsNodeOps, VfsOps traits"]
    devfs["axfs_devfs: DeviceFileSystem"]
    ramfs["axfs_ramfs: RAMFileSystem"]
end

app --> devfs
app --> ramfs
app --> vfs
devfs --> vfs
ramfs --> vfs

Title: Dependency Structure Between Crates

Sources: Cargo.toml(L19 - L20) 

Key aspects of this dependency structure:

  • axfs_vfs has no dependencies on other filesystem crates in the workspace
  • Both axfs_devfs and axfs_ramfs depend on axfs_vfs to implement its interfaces
  • Application code can use any or all of these crates, typically interacting through the axfs_vfs abstractions

Internal Structure of the Crates

axfs_vfs

The Virtual File System (VFS) crate defines the core interfaces that filesystem implementations must adhere to:

classDiagram
class VfsNodeOps {
    <<trait>>
    
    +get_attr() VfsResult~VfsNodeAttr~
    +parent() Option~VfsNodeRef~
    +lookup(path: str) VfsResult~VfsNodeRef~
    +read_dir(start_idx: usize, dirents: [VfsDirEntry]) VfsResult~usize~
    +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) VfsResult~usize~
    +create(path: str, ty: VfsNodeType) VfsResult
    +remove(path: str) VfsResult
    +truncate(size: u64) VfsResult
}

class VfsOps {
    <<trait>>
    
    +mount(path: str, mount_point: VfsNodeRef) VfsResult
    +root_dir() VfsNodeRef
}

class VfsNodeType {
    <<enum>>
    File
    Dir
    SymLink
    CharDevice
    BlockDevice
    Socket
    NamedPipe
    
}

Title: Core Traits in axfs_vfs

Sources: Based on the system architecture diagrams

axfs_devfs

The Device Filesystem provides a hierarchical structure for accessing system devices:

classDiagram
class DeviceFileSystem {
    -parent: Once~VfsNodeRef~
    -root: Arc~DirNode~
    +new() DeviceFileSystem
    +mkdir(name: str) Arc~DirNode~
    +add(name: str, node: VfsNodeRef) void
}

class DirNode {
    -parent: RwLock~Weak~dyn VfsNodeOps~~
    -children: RwLock~BTreeMap~str, VfsNodeRef~~
    +new(parent: Option~&VfsNodeRef~) Arc
    +mkdir(name: str) Arc
    +add(name: str, node: VfsNodeRef)
}

class NullDev {
    implements VfsNodeOps
    
}

class ZeroDev {
    implements VfsNodeOps
    
}

DeviceFileSystem  -->  DirNode : "contains root"
DirNode  -->  NullDev : "can contain"
DirNode  -->  ZeroDev : "can contain"

Title: DeviceFileSystem Implementation Structure

Sources: Based on the system architecture diagrams

axfs_ramfs

The RAM Filesystem provides an in-memory implementation of the filesystem interface:

classDiagram
class RAMFileSystem {
    implements VfsOps
    -root: Arc~DirNode~
    +new() RAMFileSystem
    +root_dir() VfsNodeRef
}

class DirNode {
    implements VfsNodeOps
    
}

class FileNode {
    implements VfsNodeOps
    -content: RwLock~Vec~u8~~
    
}

RAMFileSystem  -->  DirNode : "contains root"
DirNode  -->  DirNode
DirNode  -->  DirNode : "can contain subdirectories"
DirNode  -->  DirNode
DirNode  -->  FileNode : "can contain files"

Title: RAMFileSystem Implementation Structure

Sources: Based on the system architecture diagrams

Repository Configuration

The repository follows standard Rust project conventions and includes the following key configuration files:

FilePurpose
Cargo.tomlDefines the workspace and its members, shared dependencies, and package metadata
.gitignoreSpecifies files to be excluded from version control (build artifacts, IDE files, etc.)
README.mdProvides a brief overview of the repository and its components

Sources: Cargo.toml(L1 - L21)  .gitignore(L1 - L4)  README.md(L1 - L10) 

Publishing Information

The crates in this repository are published on crates.io and have the following shared metadata:

MetadataValue
Version0.1.1
LicenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
Documentationhttps://arceos-org.github.io/axfs_crates
Repositoryhttps://github.com/arceos-org/axfs_crates
Categoriesos, no-std, filesystem

Sources: Cargo.toml(L10 - L17) 

This repository structure enables the development of modular filesystem components that can be used independently or together to provide filesystem functionality for the ArceOS operating system or other embedded systems projects.

Development and Contribution

Relevant source files

This document outlines the development workflow, testing processes, and continuous integration setup for the AxFS crates project. It provides guidance for contributors on how to set up their development environment, run tests, and ensure their contributions meet the project's quality standards. For information about the repository structure, see Repository Structure.

Development Environment Setup

To begin contributing to the AxFS crates project, you'll need to set up your development environment with the following prerequisites:

  • Rust toolchain (nightly version required)
  • Rust source components, Clippy, and rustfmt
  • Git for version control

Installing Prerequisites

  1. Install Rust (nightly) - The project requires the nightly toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly
rustup component add rust-src clippy rustfmt
  1. Add target platforms - The project supports multiple targets:
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
  1. Clone the repository:
git clone https://github.com/arceos-org/axfs_crates.git
cd axfs_crates

Sources: .github/workflows/ci.yml(L14 - L19) 

Development Workflow

The typical development workflow for contributing to AxFS crates follows these steps:

flowchart TD
A["Fork Repository"]
B["Clone Repository"]
C["Create Feature Branch"]
D["Make Changes"]
E["Run Local Tests"]
F["Format Code"]
G["Run Clippy"]
H["Commit Changes"]
I["Push to Fork"]
J["Create Pull Request"]
K["CI Checks"]
L["Code Review"]
M["Address Feedback"]
N["Merge"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
K --> L
L --> M
L --> N
M --> D

Diagram: AxFS Crates Development Workflow

Sources: .github/workflows/ci.yml(L3 - L30) 

Coding Standards

When contributing to the project, ensure you follow these coding standards:

  1. All code must be formatted with rustfmt
  2. Code must pass Clippy linting with no warnings (with the exception of clippy::new_without_default)
  3. All code must compile for all supported targets
  4. New functionality should include appropriate tests

You can check your code against these standards with:

cargo fmt --all
cargo clippy --all-features -- -A clippy::new_without_default

Sources: .github/workflows/ci.yml(L22 - L25) 

Testing

The project uses Rust's built-in testing framework. Testing processes include:

  1. Unit Tests: Tests for individual components
  2. Integration Tests: Tests that verify the interaction between different parts

Running Tests

To run all tests locally:

cargo test --all-features -- --nocapture

Note that comprehensive tests only run on the x86_64-unknown-linux-gnu target in the CI pipeline, but you should verify functionality on other targets as appropriate for your changes.

flowchart TD
A["cargo test"]
B["Unit Tests"]
C["Integration Tests"]
D["Pass?"]
E["Commit Changes"]
F["Fix Issues"]

A --> B
A --> C
B --> D
C --> D
D --> E
D --> F
F --> A

Diagram: AxFS Testing Process

Sources: .github/workflows/ci.yml(L28 - L30) 

Continuous Integration

The project uses GitHub Actions for continuous integration. The CI pipeline is triggered on both push and pull request events and consists of two main jobs: code validation and documentation building.

CI Pipeline Architecture

flowchart TD
subgraph subGraph1["Documentation Job"]
    F["Setup Rust"]
    G["Build Docs"]
    H["Deploy to GitHub Pages"]
    A["Setup Rust Toolchain"]
    B["Check Formatting"]
    C["Run Clippy"]
end
subgraph subGraph0["CI Job"]
    F["Setup Rust"]
    G["Build Docs"]
    H["Deploy to GitHub Pages"]
    A["Setup Rust Toolchain"]
    B["Check Formatting"]
    C["Run Clippy"]
    D["Build"]
    E["Run Tests"]
end
Push["Push/PR Event"]
CI["CI Job"]
Doc["Documentation Job"]

A --> B
B --> C
C --> D
D --> E
F --> G
G --> H
Push --> CI
Push --> Doc

Diagram: AxFS Continuous Integration Architecture

Sources: .github/workflows/ci.yml(L5 - L53) 

CI Environments and Targets

The CI pipeline tests across multiple Rust targets to ensure cross-platform compatibility:

TargetDescription
x86_64-unknown-linux-gnuStandard Linux target
x86_64-unknown-noneBare-metal x86_64
riscv64gc-unknown-none-elfRISC-V 64-bit
aarch64-unknown-none-softfloatARM 64-bit without floating point

Sources: .github/workflows/ci.yml(L12) 

Documentation

Documentation Standards

The project enforces strict documentation standards:

  1. All public items must be documented
  2. Documentation must not contain broken intra-doc links
  3. The documentation is generated with a unified index page

These standards are enforced by the RUSTDOCFLAGS environment variable set in the CI workflow:

-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs

Sources: .github/workflows/ci.yml(L40) 

Generating Documentation

To generate documentation locally:

RUSTDOCFLAGS="-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features

The documentation is automatically built and deployed to GitHub Pages when changes are pushed to the default branch.

Sources: .github/workflows/ci.yml(L42 - L53) 

Contribution Guidelines

Pull Request Process

  1. Fork the repository and create your branch from the default branch
  2. Ensure your code follows the project's coding standards
  3. Make sure all tests pass locally
  4. Update documentation as needed
  5. Submit a pull request
  6. Address any feedback from reviewers

Code Review

All contributions undergo code review before being merged. The review process checks:

  1. Code quality and adherence to project standards
  2. Test coverage
  3. Documentation completeness
  4. Design consistency

Once a pull request passes CI checks and receives approval from maintainers, it can be merged into the main codebase.

Sources: .github/workflows/ci.yml(L3 - L30) 

Project Structure Reference

To understand how your contributions fit into the overall project structure, refer to the repository structure and codebase architecture information in the Overview and Repository Structure pages.

File System Architecture

Relevant source files

This document provides an overview of the architectural design of the file system implementation in axfs_crates. It covers the foundational components, their relationships, and the core design patterns that enable the flexible and extensible file system abstraction.

For detailed information about the Virtual File System Interface implementation, see Virtual File System Interface (axfs_vfs). For specific implementations, see Device File System (axfs_devfs) and RAM File System (axfs_ramfs).

Core Components

The axfs_crates file system architecture consists of three main crates that work together to provide a complete file system abstraction:

flowchart TD
Apps["Applications"]
VfsInterface["axfs_vfs (VfsOps/VfsNodeOps traits)"]
DevFS["axfs_devfs::DeviceFileSystem"]
RamFS["axfs_ramfs::RamFileSystem"]
DevNodes["Device Nodes (NullDev, ZeroDev)"]
MemNodes["Memory-backed Nodes"]

Apps --> VfsInterface
DevFS --> DevNodes
RamFS --> MemNodes
VfsInterface --> DevFS
VfsInterface --> RamFS

Core Components:

  1. axfs_vfs: Defines the virtual file system interface through traits that other file systems implement
  2. axfs_devfs: Implements a device file system following the VFS interface
  3. axfs_ramfs: Provides a RAM-based file system implementation

Sources: README.md(L5 - L9)  axfs_devfs/src/lib.rs(L1 - L4) 

Layered Architecture Design

The file system architecture follows a layered design pattern, separating abstract interfaces from concrete implementations:

classDiagram
class VfsNodeOps {
    <<trait>>
    
    +get_attr() VfsResult~VfsNodeAttr~
    +parent() Option~VfsNodeRef~
    +lookup(path) VfsResult~VfsNodeRef~
    +read_dir(start_idx, dirents) VfsResult~usize~
    +read_at(offset, buf) VfsResult~usize~
    +write_at(offset, buf) VfsResult~usize~
    +create(path, type) VfsResult
    +remove(path) VfsResult
    +truncate(size) VfsResult
}

class VfsOps {
    <<trait>>
    
    +mount(path, mount_point) VfsResult
    +root_dir() VfsNodeRef
}

class DeviceFileSystem {
    -parent: Once~VfsNodeRef~
    -root: Arc~DirNode~
    +new() DeviceFileSystem
    +mkdir(name) Arc~DirNode~
    +add(name, node) void
}

class DirNode {
    -parent: RwLock~Weak~VfsNodeOps~~
    -children: RwLock~BTreeMap~String,VfsNodeRef~~
    +new() Arc~DirNode~
    +mkdir(name) Arc~DirNode~
    +add(name, node) void
}

VfsOps  ..|>  DeviceFileSystem
VfsNodeOps  ..|>  DirNode

The diagram shows the core traits and their implementations:

  • VfsNodeOps: Defines operations on individual file system nodes
  • VfsOps: Defines operations on the file system as a whole
  • DeviceFileSystem: Implements VfsOps for device files
  • DirNode: Implements VfsNodeOps for directory operations

Sources: axfs_devfs/src/lib.rs(L25 - L71) 

File System Implementation Structure

The actual file system implementation forms a hierarchical tree structure:

flowchart TD
DevFS["DeviceFileSystem"]
Root["root: Arc"]
Child1["Child DirNode (e.g., 'dev')"]
Dev1["NullDev (/null)"]
Dev2["ZeroDev (/zero)"]
GrandChild["Subdirectory DirNode"]
Dev3["Device Node"]

Child1 --> Dev3
Child1 --> GrandChild
DevFS --> Root
Root --> Child1
Root --> Dev1
Root --> Dev2

The DeviceFileSystem structure contains:

  • A root directory node (root: Arc<DirNode>)
  • An optional parent node for mounting (parent: Once<VfsNodeRef>)

The tree structure supports:

  1. Creating subdirectories using mkdir(name) method
  2. Adding device nodes with add(name, node) method
  3. Standard file system path traversal

Sources: axfs_devfs/src/lib.rs(L25 - L49) 

Memory Management and Reference Counting

The architecture uses Rust's reference counting mechanisms for memory safety and object lifetime management:

flowchart TD
DevFS["DeviceFileSystem"]
Root["Root DirNode"]
Dir["Subdirectory DirNode"]
Dev["Device Node"]

DevFS --> Root
Dir --> Root
Root --> Dev
Root --> Dir

Key memory management features:

  • Arc<T> (Atomic Reference Counting) for shared ownership of nodes
  • Weak<T> references for parent pointers to avoid reference cycles
  • RwLock<T> for thread-safe concurrent access to shared data
  • Once<T> for one-time initialization of parent references during mounting

This approach guarantees memory safety while allowing flexible node relationships:

  1. Strong references (Arc) from parent to children
  2. Weak references from children to parents
  3. Thread-safe access through RwLock

Sources: axfs_devfs/src/lib.rs(L20 - L21) 

Path Resolution Process

When accessing a file or directory, the file system employs a recursive path resolution process:

sequenceDiagram
    participant Application as "Application"
    participant DeviceFileSystem as "DeviceFileSystem"
    participant RootDirNode as "Root DirNode"
    participant SubdirectoryDirNode as "Subdirectory DirNode"
    participant FileNode as "FileNode"

    Application ->> DeviceFileSystem: lookup("/dir/file")
    DeviceFileSystem ->> RootDirNode: root_dir().lookup("/dir/file")
    RootDirNode ->> RootDirNode: Parse "dir" component
    RootDirNode ->> SubdirectoryDirNode: children["dir"].lookup("file")
    SubdirectoryDirNode ->> SubdirectoryDirNode: Parse "file" component
    SubdirectoryDirNode ->> FileNode: children["file"]
    FileNode ->> SubdirectoryDirNode: Return node reference
    SubdirectoryDirNode ->> RootDirNode: Return node reference
    RootDirNode ->> DeviceFileSystem: Return node reference
    DeviceFileSystem ->> Application: Return node reference

This multi-step resolution process navigates the file system hierarchy to locate requested resources. Path resolution is managed through:

  1. The lookup method in VfsNodeOps trait
  2. Path component parsing (directory names)
  3. Recursively traversing the file system tree until reaching the target node

Sources: axfs_devfs/src/lib.rs(L52 - L65) 

Mount Operation Flow

The following sequence diagram illustrates the mounting process:

sequenceDiagram
    participant Application as "Application"
    participant VFSLayer as "VFS Layer"
    participant DeviceFileSystem as "DeviceFileSystem"
    participant MountPointNode as "MountPoint Node"
    participant RootDirNode as "Root DirNode"

    Application ->> VFSLayer: Mount file system at "/mnt"
    VFSLayer ->> MountPointNode: Get mount point node
    VFSLayer ->> DeviceFileSystem: mount("/mnt", mountPointNode)
    DeviceFileSystem ->> RootDirNode: set_parent(mountPointNode.parent())
    RootDirNode ->> DeviceFileSystem: Success
    DeviceFileSystem ->> VFSLayer: VfsResult::Ok(())
    VFSLayer ->> Application: Mount complete

The mount operation connects a file system's root directory to a specified mount point in another file system, enabling seamless path traversal across multiple file systems.

The key steps in the mounting process are:

  1. Setting the parent of the file system's root node
  2. Establishing the connection between mount point and file system

Sources: axfs_devfs/src/lib.rs(L53 - L60) 

Implementation Inheritance Hierarchy

The complete inheritance hierarchy shows how concrete implementations relate to the core traits:

classDiagram
class VfsNodeOps {
    <<trait>>
    +Core node operations
    
}

class VfsOps {
    <<trait>>
    +File system operations
    
}

class DeviceFileSystem {
    -parent: Once
    -root: Arc
    +new()
    +mkdir(name)
    +add(name, node)
}

class DirNode {
    -parent: RwLock~
    -children: RwLock~
    +new()
    +mkdir(name)
    +add(name, node)
}

class NullDev {
    
    +read_at()
    +write_at()
}

class ZeroDev {
    
    +read_at()
    +write_at()
}

VfsOps  ..|>  DeviceFileSystem
VfsNodeOps  ..|>  DirNode
VfsNodeOps  ..|>  NullDev
VfsNodeOps  ..|>  ZeroDev

This diagram shows the complete implementation hierarchy, illustrating how different components inherit from and implement the core VFS traits.

Sources: axfs_devfs/src/lib.rs(L16 - L18)  axfs_devfs/src/lib.rs(L25 - L71) 

Summary

The axfs_crates file system architecture employs:

  1. Trait-Based Design: Defines behavior through traits like VfsNodeOps and VfsOps
  2. Layered Architecture: Separates interface (axfs_vfs) from implementations (axfs_devfs, axfs_ramfs)
  3. Memory Safety: Uses reference counting and thread-safe primitives
  4. Hierarchical Structure: Organizes nodes in a tree-like structure
  5. Polymorphism: Allows different implementations to be used interchangeably

This design provides a flexible, extensible foundation for building various file system implementations while maintaining a consistent interface for applications.

Sources: README.md(L5 - L9)  axfs_devfs/src/lib.rs(L1 - L71) 

Virtual File System Interface (axfs_vfs)

Relevant source files

Purpose and Scope

The axfs_vfs crate provides a virtual file system (VFS) abstraction layer for the axfs_crates ecosystem. It defines the fundamental interfaces and data structures that concrete file system implementations must adhere to. This abstraction allows applications to interact with different file system types through a unified interface without needing to understand their specific implementations.

This document covers the core traits, data structures, and interfaces defined in the axfs_vfs crate. For specific implementations, see Device File System (axfs_devfs) or RAM File System (axfs_ramfs).

Architecture Overview

The Virtual File System Interface serves as the foundational abstraction layer in the axfs_crates architecture. It defines contracts that concrete file systems implement to provide specific functionality.

flowchart TD
subgraph subGraph0["axfs_vfs Core Components"]
    I1["VfsNodeOps Trait"]
    I2["VfsOps Trait"]
    I3["Path Resolution"]
    I4["Data Structures"]
    I5["Error Types"]
end
A["Applications"]
B["axfs_vfs Interface"]
C["axfs_devfs Implementation"]
D["axfs_ramfs Implementation"]

A --> B
B --> C
B --> D

Sources: README.md

Core Traits

The VFS interface is built around two primary traits that define the contract for file system implementations:

VfsNodeOps Trait

The VfsNodeOps trait represents file system nodes (files, directories, devices) and defines operations that can be performed on these nodes.

classDiagram
class VfsNodeOps {
    <<trait>>
    
    +get_attr() -~ VfsResult~VfsNodeAttr~
    +parent() -~ Option~VfsNodeRef~
    +lookup(path: &str) -~ VfsResult~VfsNodeRef~
    +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) -~ VfsResult~usize~
    +read_at(offset: u64, buf: &mut [u8]) -~ VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) -~ VfsResult~usize~
    +create(path: &str, ty: VfsNodeType) -~ VfsResult
    +remove(path: &str) -~ VfsResult
    +truncate(size: u64) -~ VfsResult
}

  • get_attr(): Retrieves attributes of the node (size, permissions, type, etc.)
  • parent(): Returns a reference to the parent node if it exists
  • lookup(): Resolves a path relative to this node
  • read_dir(): Lists directory entries, returning the number of entries read
  • read_at()/write_at(): Read/write operations at specified offsets
  • create(): Creates a new file/directory under this node
  • remove(): Removes a file/directory
  • truncate(): Changes file size

VfsOps Trait

The VfsOps trait represents operations at the file system level rather than individual nodes.

classDiagram
class VfsOps {
    <<trait>>
    
    +mount(path: &str, mount_point: VfsNodeRef) -~ VfsResult
    +root_dir() -~ VfsNodeRef
}

  • mount(): Mounts a file system at the specified mount point
  • root_dir(): Returns a reference to the root directory of the file system

Sources: High-Level System Architecture diagrams

Data Structures

The axfs_vfs crate defines several key data structures used throughout the file system interface:

Node Types and References

classDiagram
class VfsNodeRef {
    Arc~dyn VfsNodeOps~
    
}

class VfsNodeType {
    <<enum>>
    File
    Dir
    SymLink
    CharDevice
    BlockDevice
    Socket
    NamedPipe
    
}

class VfsNodeAttr {
    mode: VfsMode
    type: VfsNodeType
    size: u64
    blocks: u64
    atime: Timespec
    mtime: Timespec
    ctime: Timespec
    
}

  • VfsNodeRef: A reference-counted pointer to a trait object implementing VfsNodeOps
  • VfsNodeType: Enumerates the possible types of file system nodes
  • VfsNodeAttr: Contains metadata about a file system node

Directory Entries

classDiagram
class VfsDirEntry {
    name: String
    type: VfsNodeType
    
}

The VfsDirEntry structure represents entries in a directory, containing the name and type of each entry.

Sources: High-Level System Architecture diagrams

Error Handling

The VFS interface uses the VfsResult type alias for error handling:

classDiagram
class VfsResult~T~ {
    Result~T, VfsError~
    
}

class VfsError {
    <<enum>>
    NotFound
    AlreadyExists
    InvalidInput
    NotADirectory
    IsADirectory
    NotEmpty
    ReadOnly
    PermissionDenied
    IoError
    // Other error variants
    
}

This approach provides a consistent error handling mechanism across all file system implementations.

Sources: High-Level System Architecture diagrams

Path Resolution

Path resolution is a key functionality of the VFS interface, allowing for traversal of the file system hierarchy:

sequenceDiagram
    participant Client as Client
    participant RootNode as "Root Node"
    participant DirectoryNode as "Directory Node"
    participant TargetNode as "Target Node"

    Client ->> RootNode: lookup("/path/to/file")
    RootNode ->> RootNode: Parse "path" component
    RootNode ->> DirectoryNode: lookup("to/file")
    DirectoryNode ->> DirectoryNode: Parse "to" component
    DirectoryNode ->> TargetNode: lookup("file")
    TargetNode ->> DirectoryNode: Return node reference
    DirectoryNode ->> RootNode: Return node reference
    RootNode ->> Client: VfsNodeRef to target

The path resolution algorithm:

  1. Splits the path into components
  2. Traverses the file system hierarchy recursively
  3. At each step, calls the lookup() method on the current node
  4. Returns a reference to the target node if found, or an error

Sources: High-Level System Architecture diagrams

Memory Management

The VFS interface uses Rust's memory management features to ensure safety and prevent memory leaks:

flowchart TD
A["VfsNodeRef (Arc)"]
B["VfsNodeOps implementation"]
C["Weak reference"]
D["Parent Node"]

A --> B
B --> D
C --> B
  • Uses Arc (Atomic Reference Counting) for shared ownership of nodes
  • Employs weak references to prevent reference cycles (e.g., between parent and child nodes)
  • Ensures thread safety for concurrent access through synchronization primitives

Sources: High-Level System Architecture diagrams

Integration with File System Implementations

The axfs_vfs interfaces are implemented by concrete file systems to provide specific functionality:

classDiagram
class VfsNodeOps {
    <<trait>>
    // Other methods
    +lookup()
    +read_at()
    +write_at()
    +read_dir()
}

class VfsOps {
    <<trait>>
    
    +mount()
    +root_dir()
}

class DeviceFileSystem {
    -parent: Once
    -root: Arc
    
}

class RamFileSystem {
    -root: Arc
    
}

class DirNode {
    -children: RwLock
    
}

class RamDirNode {
    -entries: RwLock
    
}

VfsOps  ..|>  DeviceFileSystem
VfsOps  ..|>  RamFileSystem
VfsNodeOps  ..|>  DirNode
RamDirNode  ..|>  DirNode
  • axfs_devfs implements the VFS interface to provide device files (/dev/null, /dev/zero, etc.)
  • axfs_ramfs implements the VFS interface to provide a RAM-based file system
  • Both implementations create their own node types that implement the VfsNodeOps trait

Sources: High-Level System Architecture diagrams

Summary

The axfs_vfs crate provides a flexible, trait-based abstraction layer for file systems in the axfs_crates ecosystem. By defining common interfaces and data structures, it enables the development of different file system implementations that can be used interchangeably. The architecture follows Rust's ownership model and makes extensive use of trait objects and reference counting for safe and efficient memory management.

Device File System (axfs_devfs)

Relevant source files

Purpose and Scope

The Device File System (axfs_devfs) is a specialized file system implementation for managing access to device files within the ArceOS operating system. It provides a hierarchical structure for organizing and accessing device files, similar to the /dev directory in Unix-like systems. This document covers the architecture, components, and usage of axfs_devfs.

For information about the underlying virtual file system interface that axfs_devfs implements, see Virtual File System Interface (axfs_vfs).

Sources: axfs_devfs/Cargo.toml(L1 - L18)  axfs_devfs/README.md(L1 - L10)  axfs_devfs/src/lib.rs(L1 - L4) 

System Overview

The Device File System is built on the Virtual File System (VFS) abstraction layer provided by axfs_vfs. It implements the core VFS interfaces and provides a directory structure for organizing device nodes. The primary purpose is to expose hardware and virtual devices through a file-like interface.

flowchart TD
subgraph subGraph1["axfs_devfs Components"]
    F["DeviceFileSystem"]
    G["DirNode"]
    H["Device Nodes"]
    I["NullDev"]
    J["ZeroDev"]
end
subgraph subGraph0["AxFS Ecosystem"]
    A["Applications"]
    B["axfs_vfs: VFS Interface"]
    C["axfs_devfs: Device File System"]
    D["Hardware/Virtual Devices"]
    E["Other File Systems"]
end

A --> B
B --> C
B --> E
C --> D
C --> F
F --> G
F --> H
H --> I
H --> J

Sources: axfs_devfs/src/lib.rs(L1 - L71) 

Core Components

DeviceFileSystem

The DeviceFileSystem is the main entry point for the device file system. It implements the VfsOps trait from axfs_vfs, which provides the core file system operations.

classDiagram
class DeviceFileSystem {
    -parent: Once~VfsNodeRef~
    -root: Arc~DirNode~
    +new() DeviceFileSystem
    +mkdir(name: str) Arc~DirNode~
    +add(name: str, node: VfsNodeRef) void
    +mount(path: str, mount_point: VfsNodeRef) VfsResult
    +root_dir() VfsNodeRef
}

class VfsOps {
    <<trait>>
    
    +mount(path: str, mount_point: VfsNodeRef) VfsResult
    +root_dir() VfsNodeRef
}

VfsOps  ..|>  DeviceFileSystem

Key features of the DeviceFileSystem:

  • Maintains a root directory node
  • Provides methods for creating directories and adding device nodes
  • Implements the VfsOps trait for mounting and root directory access

Sources: axfs_devfs/src/lib.rs(L24 - L71) 

Directory Structure

The device file system organizes its contents in a hierarchical structure, with directories containing other directories or device nodes.

flowchart TD
subgraph subGraph0["Example DevFS Structure"]
    A["Root Directory"]
    B["/dev/null"]
    C["/dev/zero"]
    D["/custom Directory"]
    E["/custom/device1"]
    F["/custom/subdir"]
    G["/custom/subdir/device2"]
end

A --> B
A --> C
A --> D
D --> E
D --> F
F --> G

The root directory is created when initializing the DeviceFileSystem, and subdirectories and device nodes can be added using the provided methods:

// Create a new device file system
let devfs = DeviceFileSystem::new();

// Create a subdirectory
let custom_dir = devfs.mkdir("custom");

// Add a device node to the root
devfs.add("null", Arc::new(NullDev::new()));

// Add a device node to a subdirectory
custom_dir.add("device1", Arc::new(ZeroDev::new()));

Sources: axfs_devfs/src/lib.rs(L31 - L49) 

Device Types

Null Device

The NullDev implementation represents a /dev/null-like device that:

  • Discards all data written to it
  • Returns EOF (end-of-file) when read from
  • Takes no storage space
flowchart TD
A["Application"]
B["NullDev"]
C["(Void)"]
D["Application"]

A --> B
B --> C
B --> D
D --> B

Sources: axfs_devfs/src/lib.rs(L10 - L17) 

Zero Device

The ZeroDev implementation represents a /dev/zero-like device that:

  • Discards all data written to it
  • Returns an infinite stream of zero bytes when read from
  • Takes no storage space
flowchart TD
A["Application"]
B["ZeroDev"]
C["(Void)"]
D["Application"]

A --> B
B --> C
B --> D
D --> B

Sources: axfs_devfs/src/lib.rs(L11 - L17) 

Path Resolution

When accessing files in the device file system, paths are resolved by traversing the directory structure:

sequenceDiagram
    participant Application as "Application"
    participant DeviceFileSystem as "DeviceFileSystem"
    participant RootDirNode as "Root DirNode"
    participant Subdirectory as "Subdirectory"
    participant DeviceNode as "Device Node"

    Application ->> DeviceFileSystem: lookup("/path/to/device")
    DeviceFileSystem ->> RootDirNode: root_dir()
    DeviceFileSystem ->> RootDirNode: lookup("path/to/device")
    RootDirNode ->> RootDirNode: Find "path" component
    RootDirNode ->> Subdirectory: lookup("to/device")
    Subdirectory ->> Subdirectory: Find "to" component
    Subdirectory ->> DeviceNode: lookup("device")
    Subdirectory ->> RootDirNode: Return device node
    RootDirNode ->> DeviceFileSystem: Return device node
    DeviceFileSystem ->> Application: Return device node

The path resolution process:

  1. Starts at the root directory
  2. Splits the path into components
  3. Processes each component sequentially
  4. Returns the target node or an error if not found

Sources: axfs_devfs/src/lib.rs(L52 - L65) 

Integration with VFS

The DeviceFileSystem implements the VfsOps trait from axfs_vfs, which allows it to be mounted as part of a larger file system hierarchy:

flowchart TD
subgraph subGraph0["VFS Integration"]
    A["Root File System"]
    B["Mount Points"]
    C["/dev Mount Point"]
    D["DeviceFileSystem"]
    E["Device Nodes"]
end

A --> B
B --> C
C --> D
D --> E

When mounted, the device file system:

  1. Sets the parent of its root directory to the mount point
  2. Exposes its root directory and all children through the mount point
  3. Handles file operations through the VFS interfaces

Sources: axfs_devfs/src/lib.rs(L52 - L65) 

Usage Example

Below is a typical usage example for creating and using a device file system:

// Create a new device file system
let devfs = DeviceFileSystem::new();

// Add standard device nodes
devfs.add("null", Arc::new(NullDev::new()));
devfs.add("zero", Arc::new(ZeroDev::new()));

// Create custom subdirectories and devices
let custom_dir = devfs.mkdir("custom");
custom_dir.add("special_device", Arc::new(MyCustomDevice::new()));

// Mount the device file system (assuming a root file system exists)
root_fs.mount("/dev", devfs.root_dir()).expect("Failed to mount devfs");

// Now accessible via paths like "/dev/null", "/dev/custom/special_device"

Sources: axfs_devfs/src/lib.rs(L31 - L49)  axfs_devfs/src/lib.rs(L52 - L65) 

Summary

The Device File System (axfs_devfs) provides a hierarchical structure for organizing and accessing device files within the ArceOS operating system. It implements the VFS interfaces defined by axfs_vfs and includes implementations for common device types such as null and zero devices. Its modular design allows for easy extension with custom device implementations.

Sources: axfs_devfs/src/lib.rs(L1 - L71) 

Directory Structure

Relevant source files

This document details the directory structure implementation in the axfs_devfs filesystem. It focuses on how directories are represented, managed, and traversed within the device filesystem. For information about the overall device filesystem architecture, see Device File System (axfs_devfs).

Directory Node Implementation

The directory structure in axfs_devfs is built around the DirNode struct, which represents directory nodes in the filesystem hierarchy. Each directory can contain other directories or device nodes, forming a tree-like structure similar to traditional filesystems.

classDiagram
class DirNode {
    -parent: RwLock~Weak~dyn VfsNodeOps~~
    -children: RwLock~BTreeMap~&'static str, VfsNodeRef~~
    +new(parent: Option~&VfsNodeRef~) Arc
    +set_parent(parent: Option~&VfsNodeRef~) void
    +mkdir(name: &'static str) Arc
    +add(name: &'static str, node: VfsNodeRef) void
}

class VfsNodeOps {
    <<trait>>
    
    +get_attr() VfsResult~VfsNodeAttr~
    +parent() Option~VfsNodeRef~
    +lookup(path: &str) VfsResult~VfsNodeRef~
    +read_dir(start_idx: usize, dirents: &mut [VfsDirEntry]) VfsResult~usize~
    +create(path: &str, ty: VfsNodeType) VfsResult
    +remove(path: &str) VfsResult
}

VfsNodeOps  ..|>  DirNode

Sources: axfs_devfs/src/dir.rs(L7 - L40)  axfs_devfs/src/dir.rs(L42 - L131) 

Key Components

The DirNode struct consists of two primary fields:

  1. parent: A read-write lock containing a weak reference to the parent directory
  2. children: A read-write lock containing a BTree map of child nodes indexed by name

This structure allows for efficient directory traversal and lookup operations while preventing strong reference cycles between parent and child nodes.

Sources: axfs_devfs/src/dir.rs(L10 - L13) 

Directory Operations

The DirNode implementation provides several key operations for directory management:

Directory Creation

sequenceDiagram
    participant DeviceFileSystem as "DeviceFileSystem"
    participant ParentDirNode as "Parent DirNode"
    participant NewDirNode as "New DirNode"

    DeviceFileSystem ->> ParentDirNode: mkdir("subdir")
    ParentDirNode ->> NewDirNode: new(Some(&parent))
    NewDirNode -->> ParentDirNode: Arc<DirNode>
    ParentDirNode ->> ParentDirNode: children.write().insert("subdir", node)
    ParentDirNode -->> DeviceFileSystem: Arc<DirNode>

Sources: axfs_devfs/src/dir.rs(L29 - L34)  axfs_devfs/src/lib.rs(L39 - L42) 

Node Addition

Directories can contain various types of nodes including other directories and device nodes. The add method allows adding any node that implements the VfsNodeOps trait to a directory.

Sources: axfs_devfs/src/dir.rs(L36 - L39)  axfs_devfs/src/lib.rs(L44 - L49) 

Path Lookup

The lookup method is a crucial operation that navigates the directory structure to find nodes by path. It implements path traversal using a recursive approach:

  1. Split the path into the current component and the rest
  2. Look up the current component in the children map
  3. If there's a remaining path and the found node exists, recursively call lookup on that node
  4. Otherwise, return the found node

Sources: axfs_devfs/src/dir.rs(L51 - L69)  axfs_devfs/src/dir.rs(L133 - L138) 

Directory Listing

The read_dir method returns directory entries for the current directory, including the special "." and ".." entries.

Sources: axfs_devfs/src/dir.rs(L71 - L88) 

Path Resolution Process

Path resolution is a fundamental operation in the directory structure. The split_path helper function separates a path into its first component and the remainder, enabling recursive traversal.

flowchart TD
A["lookup(path)"]
B["split_path(path)"]
C["name is?"]
D["Return self"]
E["Return parent"]
F["Look up name in children"]
G["Return NotFound error"]
H["rest path?"]
I["node.lookup(rest)"]
J["Return node"]

A --> B
B --> C
C --> D
C --> E
C --> F
F --> G
F --> H
H --> I
H --> J

Sources: axfs_devfs/src/dir.rs(L51 - L69)  axfs_devfs/src/dir.rs(L133 - L138) 

Special Path Components

The directory implementation handles special path components:

  • "." - Refers to the current directory
  • ".." - Refers to the parent directory
  • "" (empty component) - Treated as the current directory

Sources: axfs_devfs/src/dir.rs(L53 - L55) 

Directory Hierarchy in DeviceFileSystem

The DeviceFileSystem struct maintains the root directory of the filesystem. It provides methods to manipulate the root directory and implements the VfsOps trait required by the VFS layer.

flowchart TD
A["DeviceFileSystem"]
B["Root DirNode"]
C["Child Node 1"]
D["Child Node 2"]
E["Subdirectory"]
F["Child Node 3"]

A --> B
B --> C
B --> D
B --> E
E --> F

Sources: axfs_devfs/src/lib.rs(L25 - L28)  axfs_devfs/src/lib.rs(L30 - L49) 

Mount Behavior

When the DeviceFileSystem is mounted, it sets the parent of its root directory to the specified mount point. This establishes the connection between the device filesystem and the broader filesystem hierarchy.

Sources: axfs_devfs/src/lib.rs(L53 - L60) 

Concurrency and Memory Management

The directory implementation includes several key design aspects to handle concurrency and memory management:

  1. Reader-Writer Locks: Both the parent reference and children map are protected by RwLock to allow concurrent read access while ensuring exclusive write access.
  2. Weak References: Parent references are stored as weak references (Weak<dyn VfsNodeOps>) to prevent reference cycles and memory leaks.
  3. Arc for Shared Ownership: Nodes are wrapped in Arc (Atomic Reference Counting) to allow shared ownership across the filesystem.

Sources: axfs_devfs/src/dir.rs(L10 - L13)  axfs_devfs/src/dir.rs(L16 - L22) 

Limitations

The device filesystem has some intentional limitations in its directory structure:

  1. Static Names: Node names must be &'static str, implying they must be string literals or have a 'static lifetime.
  2. No Dynamic Node Creation/Removal: The create and remove methods are implemented to reject runtime addition or removal of nodes through the VFS interface.

Sources: axfs_devfs/src/dir.rs(L90 - L109)  axfs_devfs/src/dir.rs(L111 - L128) 

Usage Example

The following example shows how to build a directory structure in the device filesystem:

flowchart TD
subgraph subGraph0["Device Filesystem Example"]
    A["DeviceFileSystem"]
    B["root (DirNode)"]
    C["dev (DirNode)"]
    D["null (NullDev)"]
    E["zero (ZeroDev)"]
    F["tmp (DirNode)"]
end

A --> B
B --> C
B --> F
C --> D
C --> E

This creates a structure with paths:

  • /dev/null
  • /dev/zero
  • /tmp

Sources: axfs_devfs/src/lib.rs(L30 - L49) 

Null Device

Relevant source files

Purpose and Overview

The Null Device in axfs_devfs implements a virtual device that behaves like the traditional /dev/null found in Unix-like operating systems. It serves as a "bit bucket" or "black hole" for data - all writes to this device are discarded, and reads yield no data. This component is useful for discarding unwanted output, testing I/O operations, or as a sink for data that doesn't need to be stored.

For information about other special devices, see Zero Device and for a broader context of the device filesystem architecture, see Device File System (axfs_devfs).

Implementation Details

The Null Device is implemented as the NullDev struct in the axfs_devfs crate. It implements the VfsNodeOps trait from axfs_vfs, which defines the interface for all file system nodes.

classDiagram
class VfsNodeOps {
    <<trait>>
    ... other methods ...
    +get_attr() VfsResult~VfsNodeAttr~
    +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) VfsResult~usize~
    +truncate(size: u64) VfsResult
}

class NullDev {
    
    +get_attr() VfsResult~VfsNodeAttr~
    +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) VfsResult~usize~
    +truncate(size: u64) VfsResult
}

VfsNodeOps  ..|>  NullDev : implements

The NullDev struct is notably minimal - it contains no data members and implements only the necessary trait methods to provide null device behavior.

Sources: axfs_devfs/src/null.rs(L6 - L7) 

Key Method Implementations

The NullDev struct implements the following key methods from the VfsNodeOps trait:

  1. get_attr(): Reports the node as a character device with default file permissions and zero size.
  2. read_at(): Always returns 0 bytes read, regardless of the requested buffer or offset.
  3. write_at(): Returns the full buffer length as if all bytes were successfully written, but actually discards the data.
  4. truncate(): Does nothing and reports success.

Other methods required by the VfsNodeOps trait are provided through the impl_vfs_non_dir_default! macro.

Sources: axfs_devfs/src/null.rs(L8 - L30) 

Behavioral Characteristics

The Null Device exhibits the following behaviors when interacted with:

sequenceDiagram
    participant Client as Client
    participant NullDev as NullDev

    Client ->> NullDev: read_at(offset, buf)
    Note over ,NullDev: Ignores offset and buffer
    NullDev -->> Client: 0 (no bytes read)
    Client ->> NullDev: write_at(offset, buf)
    Note over ,NullDev: Discards all data<br>Reports success
    NullDev -->> Client: buf.len() (all bytes "written")
    Client ->> NullDev: get_attr()
    NullDev -->> Client: CharDevice type, 0 size
    Client ->> NullDev: truncate(size)
    NullDev -->> Client: Success (no action)

Sources: axfs_devfs/src/null.rs(L8 - L30) 

Read Operation

When reading from the null device, it always returns 0, indicating no data was read. The provided buffer remains unchanged.

#![allow(unused)]
fn main() {
fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult<usize> {
    Ok(0)
}
}

This behavior can be verified in the tests, where reading from a null device doesn't alter the buffer's content:

let mut buf = [1; N];
assert_eq!(node.read_at(0, &mut buf)?, 0);  // Returns 0 bytes read
assert_eq!(buf, [1; N]);  // Buffer still contains all 1s

Sources: axfs_devfs/src/null.rs(L18 - L20)  axfs_devfs/src/tests.rs(L27 - L28) 

Write Operation

When writing to the null device, all data is discarded, but the operation reports success by returning the full length of the input buffer.

#![allow(unused)]
fn main() {
fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult<usize> {
    Ok(buf.len())
}
}

In the test code, we can see this behavior verified:

assert_eq!(node.write_at(N as _, &buf)?, N);  // Reports all bytes as written

Sources: axfs_devfs/src/null.rs(L22 - L24)  axfs_devfs/src/tests.rs(L29) 

Node Attributes

The null device reports itself as a character device with default file permissions and zero size:

#![allow(unused)]
fn main() {
fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
    Ok(VfsNodeAttr::new(
        VfsNodePerm::default_file(),
        VfsNodeType::CharDevice,
        0,
        0,
    ))
}
}

Sources: axfs_devfs/src/null.rs(L9 - L16) 

Integration with Device File System

The Null Device can be added to the Device File System at any location, typically at /dev/null. Here's how it fits into the overall device file system architecture:

flowchart TD
subgraph subGraph0["DeviceFileSystem Structure"]
    root["Root Directory"]
    null["/null (NullDev)"]
    zero["/zero (ZeroDev)"]
    foo["/foo Directory"]
    bar["/foo/bar Directory"]
    f1["/foo/bar/f1 (NullDev)"]
end

bar --> f1
foo --> bar
root --> foo
root --> null
root --> zero

Sources: axfs_devfs/src/tests.rs(L96 - L102) 

A NullDev instance can be added to the DeviceFileSystem using the add method:

let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));

This makes the null device accessible through path resolution from the root of the device file system.

Sources: axfs_devfs/src/tests.rs(L104 - L106) 

Usage Examples

The tests in the axfs_devfs crate provide practical examples of how to use the null device:

Creating and Accessing a Null Device

// Create device file system with a null device
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));

// Access the null device (supports various path formats)
let node = root.lookup("////null")?;

// Verify it's a character device
assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice);

Sources: axfs_devfs/src/tests.rs(L104 - L106)  axfs_devfs/src/tests.rs(L23 - L25) 

Adding a Null Device to Subdirectories

The null device can also be added to subdirectories:

let devfs = DeviceFileSystem::new();

// Create directory structure
let dir_foo = devfs.mkdir("foo");
let dir_bar = dir_foo.mkdir("bar");

// Add null device in a subdirectory
dir_bar.add("f1", Arc::new(NullDev));  // Creates /foo/bar/f1 as a null device

Sources: axfs_devfs/src/tests.rs(L108 - L111) 

Technical Specifications

PropertyValue
Node TypeCharacter Device
Size0 bytes
PermissionsDefault file permissions
Read BehaviorAlways returns 0 bytes, buffer unchanged
Write BehaviorDiscards data, reports full buffer length as written
Truncate BehaviorNo-op, reports success
Directory OperationsNot supported (reportsNotADirectoryerror)

Sources: axfs_devfs/src/null.rs(L8 - L30)  axfs_devfs/src/tests.rs(L23 - L30) 

Summary

The Null Device (NullDev) is a simple yet essential virtual device that adheres to the standard semantics of /dev/null in Unix-like systems. It discards all written data and provides no data when read, making it useful for scenarios where data needs to be ignored or when a non-functional sink or source is required. It can be integrated anywhere in the device file system hierarchy and behaves consistently with its traditional counterpart.

See also:

Zero Device

Relevant source files

The Zero Device (ZeroDev) is a special character device in the AxFS Device File System that mimics the behavior of the /dev/zero device in Unix-like operating systems. It provides an infinite stream of null bytes (\0) when read and silently discards any data written to it. This page documents the implementation, behavior, and usage of the Zero Device in the axfs_crates repository.

For information about the Null Device, which behaves differently, see Null Device.

Implementation

The Zero Device is implemented as a simple Rust struct that implements the VfsNodeOps trait defined in the Virtual File System interface.

classDiagram
class VfsNodeOps {
    <<trait>>
    +many more methods...
    +get_attr() VfsResult~VfsNodeAttr~
    +read_at(offset: u64, buf: &mut [u8]) VfsResult~usize~
    +write_at(offset: u64, buf: &[u8]) VfsResult~usize~
    +truncate(size: u64) VfsResult
}

class ZeroDev {
    <<struct>>
    
    
}

VfsNodeOps  --|>  ZeroDev : implements

The ZeroDev struct itself is extremely simple, containing no fields:

pub struct ZeroDev;

It implements the VfsNodeOps trait to provide the behavior expected of a zero device.

Sources: axfs_devfs/src/zero.rs(L1 - L32) 

Behavior

Read Operations

When any read operation is performed on the Zero Device, it fills the provided buffer completely with null bytes (\0) and returns the length of the buffer as the number of bytes read. This gives the appearance of an infinite stream of zeros.

sequenceDiagram
    participant Client as Client
    participant ZeroDev as ZeroDev

    Client ->> ZeroDev: read_at(offset, buffer)
    Note over ,ZeroDev: Fill buffer with zeros<br>(buffer.fill(0))
    ZeroDev ->> Client: Return buffer.len()

The implementation is straightforward:

#![allow(unused)]
fn main() {
fn read_at(&self, _offset: u64, buf: &mut [u8]) -> VfsResult<usize> {
    buf.fill(0);
    Ok(buf.len())
}
}

Sources: axfs_devfs/src/zero.rs(L18 - L21) 

Write Operations

The Zero Device silently discards any data written to it, but it reports success as if the write operation completed successfully. The device always reports that all bytes were written, returning the length of the buffer.

sequenceDiagram
    participant Client as Client
    participant ZeroDev as ZeroDev

    Client ->> ZeroDev: write_at(offset, buffer)
    Note over ,ZeroDev: Discard all data<br>(do nothing)
    ZeroDev ->> Client: Return buffer.len()

The implementation is extremely minimal:

#![allow(unused)]
fn main() {
fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult<usize> {
    Ok(buf.len())
}
}

Sources: axfs_devfs/src/zero.rs(L23 - L25) 

Truncate Operation

The truncate method is implemented but effectively does nothing, as the Zero Device has no actual storage:

#![allow(unused)]
fn main() {
fn truncate(&self, _size: u64) -> VfsResult {
    Ok(())
}
}

Sources: axfs_devfs/src/zero.rs(L27 - L29) 

Attributes

The Zero Device reports itself as a character device with size 0 and default file permissions:

#![allow(unused)]
fn main() {
fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
    Ok(VfsNodeAttr::new(
        VfsNodePerm::default_file(),
        VfsNodeType::CharDevice,
        0,
        0,
    ))
}
}

Sources: axfs_devfs/src/zero.rs(L9 - L16) 

Integration with Device File System

The Zero Device is typically mounted in a Device File System (DevFS) structure. The creation and mounting process is simple:

flowchart TD
subgraph subGraph0["File System Structure"]
    E["/"]
    F["/zero"]
    G["/null"]
    H["/foo"]
    I["/foo/f2 (zero)"]
    J["/foo/bar"]
    K["/foo/bar/f1 (null)"]
end
A["DeviceFileSystem::new()"]
B["devfs"]
C["devfs.add('zero', Arc::new(ZeroDev))"]
D["'/zero' accessible in file system"]

A --> B
B --> C
C --> D
E --> F
E --> G
E --> H
H --> I
H --> J
J --> K

Sources: axfs_devfs/src/tests.rs(L96 - L106) 

Usage Examples

Creating a Zero Device

To create a Zero Device and add it to a Device File System, you need to:

  1. Create a new ZeroDev instance
  2. Wrap it in an Arc (Atomic Reference Count)
  3. Add it to the Device File System with a name
let devfs = DeviceFileSystem::new();
devfs.add("zero", Arc::new(ZeroDev));

You can also create Zero Devices in subdirectories:

let dir_foo = devfs.mkdir("foo");
dir_foo.add("f2", Arc::new(ZeroDev));

Sources: axfs_devfs/src/tests.rs(L104 - L109) 

Reading from a Zero Device

When reading from a Zero Device, the buffer will be filled with zeros regardless of the read position:

let node = devfs.root_dir().lookup("zero")?;
let mut buf = [1; 32];  // Buffer filled with 1s
assert_eq!(node.read_at(10, &mut buf)?, 32);
assert_eq!(buf, [0; 32]);  // Buffer now filled with 0s

This demonstrates that regardless of the initial buffer content and the offset parameter, the Zero Device will always fill the buffer with zeros and return the full buffer length.

Sources: axfs_devfs/src/tests.rs(L32 - L37) 

Writing to a Zero Device

Writing to a Zero Device always succeeds, even though the data is discarded:

let node = devfs.root_dir().lookup("zero")?;
let buf = [1; 32];
assert_eq!(node.write_at(0, &buf)?, 32);  // Returns success with the buffer length

Sources: axfs_devfs/src/tests.rs(L38) 

Summary

The Zero Device (ZeroDev) is a simple but useful virtual device in the AxFS Device File System that:

  1. Provides an infinite stream of zero bytes (\0) when read
  2. Silently discards all data written to it
  3. Reports itself as a character device
  4. Can be mounted at any location in the device file system

This implementation matches the behavior of the /dev/zero device in Unix-like systems, providing a consistent interface for applications expecting such a device.

Sources: axfs_devfs/src/zero.rs(L1 - L32)  axfs_devfs/src/tests.rs(L32 - L38) 

Usage Examples

Relevant source files

This page provides practical examples of how to use the Device File System (axfs_devfs) in your applications. It demonstrates the creation, configuration, and interaction with a device filesystem, focusing on real-world usage patterns. For details about the core DevFS implementation, see Device File System (axfs_devfs) and for specific device implementations, refer to Null Device and Zero Device.

Creating and Configuring a Device File System

The most basic usage of the Device File System involves creating an instance and adding various devices and directories to it. The following example demonstrates how to create a complete filesystem structure:

flowchart TD
subgraph subGraph0["Device File System Example Structure"]
    R["/"]
    N["/null"]
    Z["/zero"]
    F["/foo"]
    B["/foo/bar"]
    F2["/foo/f2"]
    F1["/foo/bar/f1"]
end

B --> F1
F --> B
F --> F2
R --> F
R --> N
R --> Z

Sources: axfs_devfs/src/tests.rs(L94 - L112) 

The code to create this structure:

  1. First, create a new DeviceFileSystem instance
  2. Add device nodes (null and zero) to the root
  3. Create directories and add more devices to them
let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
devfs.add("zero", Arc::new(ZeroDev));

let dir_foo = devfs.mkdir("foo");
dir_foo.add("f2", Arc::new(ZeroDev));
let dir_bar = dir_foo.mkdir("bar");
dir_bar.add("f1", Arc::new(NullDev));

Sources: axfs_devfs/src/tests.rs(L104 - L111) 

Path Resolution Examples

The Device File System supports flexible path resolution with various path formats, including absolute paths, relative paths, and paths with special components like . and ... The following diagram illustrates how path resolution works:

flowchart TD
subgraph subGraph0["Path Resolution Process"]
    P1["Root lookup: '/foo/bar/f1'"]
    S1["Split into components: 'foo', 'bar', 'f1'"]
    L1["Find 'foo' in root"]
    L2["Find 'bar' in foo"]
    L3["Find 'f1' in bar"]
    R1["Return f1 node"]
    P2["Path with dot components: './foo/../foo/bar'"]
    S2["Process dot components"]
    N2["Normalize to 'foo/bar'"]
    L4["Resolve 'foo/bar'"]
end

L1 --> L2
L2 --> L3
L3 --> R1
N2 --> L4
P1 --> S1
P2 --> S2
S1 --> L1
S2 --> N2

Sources: axfs_devfs/src/tests.rs(L22 - L41)  axfs_devfs/src/tests.rs(L75 - L89) 

Examples of Valid Path Formats

The following table shows examples of path formats that resolve to the same nodes:

Path FormatResolves ToNotes
"null"Null device at rootSimple node name
"////null"Null device at rootMultiple slashes are ignored
".///.//././/.////zero"Zero device at rootDot directories are processed
"/foo/.."Root directoryParent directory reference
"foo/.//f2"f2 node in fooMixed formats
"./foo/././bar/../.."Root directoryComplex path with various components
"/.//foo/"foo directoryMixed absolute path

Sources: axfs_devfs/src/tests.rs(L23 - L41)  axfs_devfs/src/tests.rs(L76 - L89) 

Interacting with Devices and Directories

Once you have a reference to a file system node, you can interact with it according to its type:

sequenceDiagram
    participant Application as Application
    participant DeviceFileSystem as DeviceFileSystem
    participant RootDir as RootDir
    participant FileNode as FileNode

    Application ->> DeviceFileSystem: root_dir()
    DeviceFileSystem -->> Application: root node
    Application ->> RootDir: lookup("zero")
    RootDir -->> Application: zero device node
    Application ->> FileNode: get_attr()
    FileNode -->> Application: attributes (type=CharDevice)
    Application ->> FileNode: read_at(10, buf)
    FileNode -->> Application: N bytes of zeros
    Note over Application,FileNode: Different interaction with directories
    Application ->> RootDir: lookup("foo")
    RootDir -->> Application: foo directory node
    Application ->> FileNode: get_attr()
    FileNode -->> Application: attributes (type=Dir)
    Application ->> FileNode: read_at(10, buf)
    FileNode -->> Application: Error: IsADirectory

Sources: axfs_devfs/src/tests.rs(L7 - L60) 

Reading and Writing to Device Nodes

The example below shows how to read from and write to device nodes:

  1. Null Device:
  • Reading returns 0 bytes and doesn't modify the buffer
  • Writing accepts any number of bytes (discards them) and returns the number of bytes written
  1. Zero Device:
  • Reading fills the buffer with zeros and returns the buffer size
  • Writing accepts any number of bytes (discards them) and returns the number of bytes written
// For null device:
let node = root.lookup("null")?;
assert_eq!(node.read_at(0, &mut buf)?, 0);  // Returns 0 bytes
assert_eq!(buf, [1; N]);                    // Buffer unchanged
assert_eq!(node.write_at(N as _, &buf)?, N); // Returns N bytes written

// For zero device:
let node = root.lookup("zero")?;
assert_eq!(node.read_at(10, &mut buf)?, N); // Returns N bytes
assert_eq!(buf, [0; N]);                    // Buffer filled with zeros
assert_eq!(node.write_at(0, &buf)?, N);     // Returns N bytes written

Sources: axfs_devfs/src/tests.rs(L22 - L39) 

Directory Navigation and Parent References

The Device File System allows navigation through the directory structure using parent references. This is particularly useful for implementing commands like cd .. in a shell or navigating relative paths.

flowchart TD
subgraph subGraph0["Parent Navigation Example"]
    R["Root"]
    F["foo"]
    B["bar"]
    F1["f1"]
    F2["f2"]
    L1["lookup('foo/bar')"]
    L2["lookup('foo/bar/..')"]
    L3["lookup('foo/bar/../..')"]
end

B --> F1
F --> B
F --> F2
L1 --> B
L2 --> F
L3 --> R
R --> F

Sources: axfs_devfs/src/tests.rs(L69 - L73)  axfs_devfs/src/tests.rs(L75 - L89) 

You can navigate the file system using parent references:

// Get a reference to the 'bar' directory
let node = root.lookup(".//foo/bar")?;

// Get its parent (which should be 'foo')
let parent = node.parent().unwrap();

// Verify it's the same as directly looking up 'foo'
assert!(Arc::ptr_eq(&parent, &root.lookup("foo")?));

// Various ways to navigate up the tree
assert!(Arc::ptr_eq(&root.lookup("/foo/..")?, &root.lookup(".")?));

Sources: axfs_devfs/src/tests.rs(L69 - L77) 

Common Use Patterns

Here are some typical usage patterns for the Device File System:

Pattern 1: Creating a Standard /dev Directory

A common pattern is to create a /dev directory with standard Unix-like device nodes:

let devfs = DeviceFileSystem::new();
devfs.add("null", Arc::new(NullDev));
devfs.add("zero", Arc::new(ZeroDev));
// Add other standard devices as needed

Sources: axfs_devfs/src/tests.rs(L104 - L106) 

Pattern 2: Testing File System Structure

When implementing file system-related functionality, you can use this pattern to verify correct path resolution:

// Test complex path resolution
assert!(Arc::ptr_eq(
    &root.lookup("///foo//bar///..//././")?,
    &devfs.root_dir().lookup(".//./foo/")?,
));

Sources: axfs_devfs/src/tests.rs(L82 - L85) 

Pattern 3: Testing Device Characteristics

This pattern is useful for verifying that device nodes behave as expected:

let node = root.lookup("zero")?;
assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice);
assert!(!node.get_attr()?.is_dir());
assert_eq!(node.get_attr()?.size(), 0);

// Test zero device behavior
let mut buf = [1; 32];
assert_eq!(node.read_at(10, &mut buf)?, 32);
assert_eq!(buf, [0; 32]);

Sources: axfs_devfs/src/tests.rs(L32 - L38) 

Error Handling

When using the Device File System, various errors can occur. Here's how to handle common error cases:

// Handle "not found" errors
match root.lookup("urandom") {
    Ok(node) => { /* use node */ },
    Err(VfsError::NotFound) => { /* handle not found */ },
    Err(e) => { /* handle other errors */ },
}

// Handle "not a directory" errors
match root.lookup("zero/") {
    Ok(node) => { /* use node */ },
    Err(VfsError::NotADirectory) => { /* handle not a directory */ },
    Err(e) => { /* handle other errors */ },
}

// Handle "is a directory" errors when trying to read/write
match foo.read_at(10, &mut buf) {
    Ok(bytes_read) => { /* use bytes_read */ },
    Err(VfsError::IsADirectory) => { /* handle is a directory */ },
    Err(e) => { /* handle other errors */ },
}

Sources: axfs_devfs/src/tests.rs(L14 - L21)  axfs_devfs/src/tests.rs(L42 - L45) 

Integration with VFS

The Device File System is designed to integrate with the Virtual File System interface. This lets you use it alongside other file systems in a unified hierarchy.

flowchart TD
subgraph subGraph0["DFS Implementation"]
    DFS["DeviceFileSystem"]
    D1["root_dir()"]
    D2["mount()"]
end
A["Application"]
VFS["VfsOps Interface"]
RFS["RamFileSystem"]
DR["Returns root DirNode"]
DM["Sets parent relationship"]

A --> VFS
D1 --> DR
D2 --> DM
DFS --> D1
DFS --> D2
VFS --> DFS
VFS --> RFS

Sources: axfs_devfs/src/lib.rs(L52 - L65) 

For more information on VFS integration, see Virtual File System Interface (axfs_vfs).

RAM File System (axfs_ramfs)

Relevant source files

Purpose and Scope

axfs_ramfs implements a RAM-based file system for the axfs_crates ecosystem. It provides an in-memory storage solution that conforms to the Virtual File System (VFS) interfaces defined in axfs_vfs. This document covers the architecture, components, and usage patterns of the RAM File System.

For information about the VFS interface that axfs_ramfs implements, see Virtual File System Interface (axfs_vfs).

Sources: Cargo.toml(L4 - L8)  README.md(L7 - L9) 

System Architecture

The RAM File System is one of the concrete implementations in the axfs_crates ecosystem, alongside the Device File System (axfs_devfs).

Integration with axfs_crates

flowchart TD
A["Applications"]
B["axfs_vfs: VfsOps & VfsNodeOps Traits"]
C["axfs_ramfs: RamFileSystem"]
D["axfs_devfs: DeviceFileSystem"]
E["System Memory (RAM)"]
F["Hardware Devices"]

A --> B
B --> C
B --> D
C --> E
D --> F

This diagram illustrates how axfs_ramfs fits within the overall architecture. Applications interact with file systems through the VFS interface, which delegates operations to the appropriate implementation based on mount points.

Sources: Cargo.toml(L4 - L8)  README.md(L7 - L9) 

Core Components

Based on the VFS interface requirements, the RAM File System likely consists of these core components:

Component Relationships

classDiagram
class VfsOps {
    <<trait>>
    
    +mount(path, mount_point)
    +root_dir()
}

class VfsNodeOps {
    <<trait>>
    
    +get_attr()
    +lookup(path)
    +read_dir(start_idx, dirents)
    +read_at(offset, buf)
    +write_at(offset, buf)
    +create(path, type)
    +remove(path)
    +truncate(size)
}

class RamFileSystem {
    -root
    +new()
    +root_dir()
    +mount(path, mount_point)
}

class RamDirNode {
    -parent
    -children
    +new()
    +lookup(path)
    +create(name, type)
}

class RamFileNode {
    -parent
    -content
    +new()
    +read_at(offset, buf)
    +write_at(offset, buf)
    +truncate(size)
}

VfsOps  ..|>  RamFileSystem : implements
VfsNodeOps  ..|>  RamDirNode : implements
VfsNodeOps  ..|>  RamFileNode : implements
RamFileSystem  -->  RamDirNode : contains
RamDirNode  -->  RamDirNode
RamDirNode  -->  RamDirNode : contains
RamDirNode  -->  RamDirNode
RamDirNode  -->  RamFileNode : contains

The key components likely include:

ComponentPurposeKey Responsibilities
RamFileSystemMain file system implementationImplementsVfsOpstrait, manages mount operations, provides access to root directory
RamDirNodeDirectory node implementationStores hierarchical structure, manages children (files and subdirectories)
RamFileNodeFile node implementationStores file content in memory, handles read/write operations

These components work together to provide a complete in-memory file system hierarchy with the same interface as other file systems.

Sources: Based on common Rust patterns and the VFS architecture shown in the high-level diagrams

In-Memory Hierarchy

A typical RAM file system hierarchy structure:

flowchart TD
A["RamDirNode: Root '/'"]
B["RamDirNode: '/tmp'"]
C["RamDirNode: '/home'"]
D["RamFileNode: 'temp.txt'"]
E["RamDirNode: 'cache'"]
F["RamFileNode: 'data.bin'"]
G["RamDirNode: 'user1'"]
H["RamFileNode: 'document.txt'"]
I["RamFileNode: 'image.png'"]

A --> B
A --> C
B --> D
B --> E
C --> G
E --> F
G --> H
G --> I

This structure mirrors traditional file systems but exists entirely in memory. Each node (RamDirNode or RamFileNode) implements the VfsNodeOps trait, providing a consistent interface regardless of node type.

Sources: Based on common file system hierarchy patterns

Operation Patterns

The RAM File System implements file system operations defined by the VFS traits:

File System Operations (VfsOpsTrait)

  • mount: Attaches the RAM file system to a mount point
  • root_dir: Provides access to the root directory

Node Operations (VfsNodeOpsTrait)

OperationDescriptionImplementation Considerations
get_attrGet node attributes (size, type, etc.)Return cached attributes or calculate on demand
lookupFind a node by pathTraverse directory hierarchy to locate node
read_dirList directory contentsReturn entries from directory's children map
read_atRead file data from specified offsetAccess in-memory buffer at offset
write_atWrite file data at specified offsetModify in-memory buffer, potentially resize
createCreate new file or directoryAllocate new node, add to parent's children
removeDelete file or directoryRemove from parent's children, free memory
truncateChange file sizeResize in-memory buffer

Sources: Based on VFS interface requirements

Usage Lifecycle

sequenceDiagram
    participant Application as "Application"
    participant VFSInterface as "VFS Interface"
    participant RamFileSystem as "RamFileSystem"
    participant RamDirNode as "RamDirNode"
    participant RamFileNode as "RamFileNode"

    Application ->> RamFileSystem: RamFileSystem::new()
    RamFileSystem ->> RamDirNode: RamDirNode::new()
    RamDirNode ->> RamFileSystem: Arc<RamDirNode>
    RamFileSystem ->> Application: RamFileSystem instance
    Application ->> VFSInterface: mount("/tmp", ramfs.root_dir())
    VFSInterface ->> RamFileSystem: mount operation
    Application ->> VFSInterface: create("/tmp/file.txt", VfsNodeType::File)
    VFSInterface ->> RamFileSystem: create request
    RamFileSystem ->> RamDirNode: lookup("/tmp")
    RamDirNode ->> RamFileSystem: tmp directory node
    RamFileSystem ->> RamDirNode: create("file.txt", VfsNodeType::File)
    RamDirNode ->> RamFileNode: RamFileNode::new()
    RamFileNode ->> RamDirNode: Arc<RamFileNode>
    RamDirNode ->> RamFileSystem: Success
    RamFileSystem ->> VFSInterface: Success
    VFSInterface ->> Application: Success
    Application ->> VFSInterface: lookup("/tmp/file.txt")
    VFSInterface ->> RamFileSystem: lookup request
    RamFileSystem ->> RamDirNode: lookup("/tmp")
    RamDirNode ->> RamFileSystem: tmp directory node
    RamFileSystem ->> RamDirNode: lookup("file.txt")
    RamDirNode ->> RamFileNode: Get file node
    RamFileNode ->> RamDirNode: file node
    RamDirNode ->> RamFileSystem: file node
    RamFileSystem ->> VFSInterface: file node
    VFSInterface ->> Application: file node
    Application ->> VFSInterface: write_at(file, 0, data)
    VFSInterface ->> RamFileNode: write_at(0, data)
    RamFileNode ->> RamFileNode: Store data in memory buffer
    RamFileNode ->> VFSInterface: bytes_written
    VFSInterface ->> Application: bytes_written

This sequence diagram illustrates the typical lifecycle of RAM file system usage, from creation to file operations.

Sources: Based on common file system operation patterns

Implementation Considerations

The RAM File System implementation likely uses these Rust features:

  • Arc<T> for shared ownership of nodes
  • Weak<T> references to prevent reference cycles (particularly for parent pointers)
  • RwLock<T> or Mutex<T> for concurrent access to mutable data
  • Vec<u8> or similar for storing file contents
  • BTreeMap<String, VfsNodeRef> for directory entries

Advantages and Limitations

Advantages

  • Performance: Memory operations are faster than disk I/O
  • Simplicity: No need for persistent storage management
  • Portability: Works in environments without storage devices

Limitations

  • Volatility: All data is lost on system shutdown or restart
  • Capacity: Limited by available RAM
  • Concurrent Access: Requires careful synchronization for thread safety

Use Cases

  • Temporary file storage
  • Cache for frequently accessed data
  • Test environments
  • Embedded systems with limited storage
  • Performance-critical applications

Sources: README.md(L9) 

Overview

Relevant source files

Purpose and Scope

The scheduler crate provides a unified interface for multiple scheduling algorithms within the ArceOS operating system project. This document covers the overall architecture, core components, and design principles of the scheduler system. The crate implements three distinct scheduling algorithms—FIFO, Round Robin, and Completely Fair Scheduler (CFS)—through a common BaseScheduler trait interface.

For detailed information about individual scheduler implementations, see Scheduler Implementations. For guidance on extending or modifying the scheduler system, see Development Guide.

System Architecture

The scheduler crate follows a trait-based design pattern that enables polymorphic usage of different scheduling algorithms within the ArceOS kernel. The system consists of a core trait definition, multiple algorithm implementations, and associated task wrapper types.

Core Components Overview


Sources: src/lib.rs(L24 - L68)  Cargo.toml(L1 - L14) 

Trait Implementation Mapping

classDiagram
class BaseScheduler {
    <<trait>>
    +SchedItem type
    +init()
    +add_task(task)
    +remove_task(task) Option~SchedItem~
    +pick_next_task() Option~SchedItem~
    +put_prev_task(prev, preempt)
    +task_tick(current) bool
    +set_priority(task, prio) bool
}

class FifoScheduler {
    +SchedItem Arc~FifoTask~T~~
    +scheduler_name() "FIFO"
}

class RRScheduler {
    +SchedItem Arc~RRTask~T~~
    +scheduler_name() "Round-robin"
}

class CFScheduler {
    +SchedItem Arc~CFSTask~T~~
    +scheduler_name() "CFS"
}

BaseScheduler  ..|>  FifoScheduler
BaseScheduler  ..|>  RRScheduler
BaseScheduler  ..|>  CFScheduler

Sources: src/lib.rs(L28 - L68)  src/lib.rs(L20 - L22) 

Algorithm Characteristics

The crate provides three scheduling algorithms with distinct characteristics and use cases:

AlgorithmTypePreemptionPriority SupportData Structure
FIFOCooperativeNoNolinked_list_r4l::List
Round RobinPreemptiveTimer-basedNoVecDeque
CFSPreemptiveVirtual runtimeNice valuesBTreeMap

Core Interface Design

The BaseScheduler trait defines the fundamental operations required by all scheduling algorithms. The trait uses an associated type SchedItem to represent scheduled entities, enabling type-safe implementation across different task wrapper types.

Key operations include:

  • Task Management: add_task(), remove_task() for inserting and removing tasks from the scheduler
  • Scheduling Decisions: pick_next_task() for selecting the next task to execute
  • Context Switching: put_prev_task() for handling previously running tasks
  • Timer Integration: task_tick() for time-based scheduling decisions
  • Priority Control: set_priority() for dynamic priority adjustment

Sources: src/lib.rs(L28 - L68) 

Integration with ArceOS

The scheduler crate is designed as a foundational component for the ArceOS operating system, providing the scheduling subsystem that manages task execution. The unified interface allows the kernel to interact with different scheduling policies through the same API, enabling runtime scheduler selection or algorithm-specific optimizations.

The crate uses #![cfg_attr(not(test), no_std)] to support both hosted testing environments and bare-metal execution contexts required by ArceOS.

Sources: src/lib.rs(L9)  Cargo.toml(L8 - L10) 

Testing and Validation

The crate includes a comprehensive testing framework that validates the behavior of all scheduler implementations. The testing system uses macro-based test generation to ensure consistent validation across different algorithms.

For detailed information about the testing framework and development workflow, see Testing Framework and Development Guide.

Sources: src/lib.rs(L15 - L16) 

Next Steps

This overview provides the foundation for understanding the scheduler crate architecture. For more detailed information:

Core Architecture

Relevant source files

This document explains the foundational design of the scheduler crate, focusing on the BaseScheduler trait and the architectural principles that enable a unified interface across different scheduling algorithms. For specific implementation details of individual schedulers, see Scheduler Implementations. For testing methodology, see Testing Framework.

Unified Scheduler Interface

The scheduler crate's core architecture is built around the BaseScheduler trait, which provides a consistent interface for all scheduling algorithm implementations. This trait defines eight essential methods that every scheduler must implement, ensuring predictable behavior across different scheduling policies.

BaseScheduler Trait Structure

classDiagram
class BaseScheduler {
    <<trait>>
    +SchedItem: type
    +init()
    +add_task(task: SchedItem)
    +remove_task(task: &SchedItem) Option~SchedItem~
    +pick_next_task() Option~SchedItem~
    +put_prev_task(prev: SchedItem, preempt: bool)
    +task_tick(current: &SchedItem) bool
    +set_priority(task: &SchedItem, prio: isize) bool
}

class FifoScheduler {
    +SchedItem: Arc~FifoTask~T~~
    +scheduler_name() : "FIFO"
}

class RRScheduler {
    +SchedItem: Arc~RRTask~T,S~~
    +scheduler_name() : "Round-robin"
}

class CFScheduler {
    +SchedItem: Arc~CFSTask~T~~
    +scheduler_name() : "CFS"
}

BaseScheduler  ..|>  FifoScheduler
BaseScheduler  ..|>  RRScheduler
BaseScheduler  ..|>  CFScheduler

Sources: src/lib.rs(L24 - L68) 

The trait uses an associated type SchedItem to allow each scheduler implementation to define its own task wrapper type, providing type safety while maintaining interface consistency.

Task Lifecycle Management

The architecture defines a clear task lifecycle through the trait methods, ensuring that all schedulers handle task state transitions uniformly.

Task State Flow

stateDiagram-v2
[*] --> Ready : "add_task()"
Ready --> Running : "pick_next_task()"
Running --> Ready : "put_prev_task(preempt=false)"
Running --> Preempted : "put_prev_task(preempt=true)"
Preempted --> Ready : "scheduler decision"
Running --> TimerTick : "task_tick()"
TimerTick --> Running : "return false"
TimerTick --> NeedResched : "return true"
NeedResched --> Ready : "put_prev_task()"
Ready --> Removed : "remove_task()"
Running --> Removed : "remove_task()"
Removed --> [*]

Sources: src/lib.rs(L35 - L67) 

Core Methods and Responsibilities

The BaseScheduler trait defines specific responsibilities for each method:

MethodPurposeReturn TypeSafety Requirements
initInitialize scheduler state()None
add_taskAdd runnable task to scheduler()Task must be runnable
remove_taskRemove task by referenceOptionTask must exist in scheduler
pick_next_taskSelect and remove next taskOptionNone
put_prev_taskReturn task to scheduler()None
task_tickProcess timer tickboolCurrent task must be valid
set_priorityModify task priorityboolTask must exist in scheduler

Sources: src/lib.rs(L32 - L67) 

Scheduler Implementation Pattern

The architecture follows a consistent pattern where each scheduler implementation manages its own specialized task wrapper type and internal data structures.

Implementation Hierarchy

flowchart TD
BaseScheduler["BaseScheduler<trait>"]
FifoScheduler["FifoScheduler"]
RRScheduler["RRScheduler"]
CFScheduler["CFScheduler"]
FifoTask["FifoTask<T>"]
RRTask["RRTask<T,S>"]
CFSTask["CFSTask<T>"]
inner_fifo["inner: T"]
inner_rr["inner: T"]
time_slice["time_slice: AtomicIsize"]
inner_cfs["inner: T"]
vruntime["init_vruntime: AtomicIsize"]
nice["nice: AtomicIsize"]
delta["delta: AtomicIsize"]

BaseScheduler --> CFScheduler
BaseScheduler --> FifoScheduler
BaseScheduler --> RRScheduler
CFSTask --> delta
CFSTask --> inner_cfs
CFSTask --> nice
CFSTask --> vruntime
CFScheduler --> CFSTask
FifoScheduler --> FifoTask
FifoTask --> inner_fifo
RRScheduler --> RRTask
RRTask --> inner_rr
RRTask --> time_slice

Sources: src/lib.rs(L20 - L22) 

Design Principles

Type Safety through Associated Types

The architecture uses Rust's associated type system to ensure compile-time type safety while allowing each scheduler to define its own task wrapper:

type SchedItem; // Defined per implementation

This approach prevents mixing task types between different scheduler implementations while maintaining a unified interface.

Memory Safety Patterns

The trait design incorporates several memory safety patterns:

  • Ownership Transfer: pick_next_task() transfers ownership of tasks out of the scheduler
  • Reference-based Operations: remove_task() and task_tick() use references to avoid unnecessary ownership transfers
  • Optional Returns: Methods return Option<SchedItem> to handle empty scheduler states safely

Preemption Awareness

The architecture explicitly supports both cooperative and preemptive scheduling through the preempt parameter in put_prev_task(), allowing schedulers to implement different policies for preempted versus yielding tasks.

Sources: src/lib.rs(L55 - L58) 

Module Organization

The crate follows a modular architecture with clear separation of concerns:

flowchart TD
lib_rs["lib.rs"]
trait_def["BaseScheduler trait definition"]
exports["Public exports"]
fifo_mod["mod fifo"]
rr_mod["mod round_robin"]
cfs_mod["mod cfs"]
tests_mod["mod tests"]
FifoScheduler["FifoScheduler"]
FifoTask["FifoTask"]
RRScheduler["RRScheduler"]
RRTask["RRTask"]
CFScheduler["CFScheduler"]
CFSTask["CFSTask"]

cfs_mod --> CFSTask
cfs_mod --> CFScheduler
exports --> CFSTask
exports --> CFScheduler
exports --> FifoScheduler
exports --> FifoTask
exports --> RRScheduler
exports --> RRTask
fifo_mod --> FifoScheduler
fifo_mod --> FifoTask
lib_rs --> cfs_mod
lib_rs --> exports
lib_rs --> fifo_mod
lib_rs --> rr_mod
lib_rs --> tests_mod
lib_rs --> trait_def
rr_mod --> RRScheduler
rr_mod --> RRTask

Sources: src/lib.rs(L11 - L22) 

This modular design ensures that each scheduling algorithm is self-contained while contributing to the unified interface through the central trait definition.

Scheduler Implementations

Relevant source files

This document provides an overview of the three scheduler algorithm implementations provided by the crate: FIFO, Round Robin, and Completely Fair Scheduler (CFS). Each implementation adheres to the unified BaseScheduler interface while providing distinct scheduling behaviors and performance characteristics.

For details about the BaseScheduler trait and core architecture, see Core Architecture. For detailed documentation of each individual scheduler, see FIFO Scheduler, Completely Fair Scheduler (CFS), and Round Robin Scheduler.

Scheduler Overview

The crate provides three distinct scheduling algorithms, each designed for different use cases and performance requirements:

SchedulerTypePriority SupportPreemptionData StructureUse Case
FifoSchedulerCooperativeNoNolinked_list_r4l::ListSimple sequential execution
RRSchedulerPreemptiveNoTime-basedVecDequeFair time sharing
CFSchedulerPreemptiveYes (nice values)Virtual runtimeBTreeMapAdvanced fair scheduling

Each scheduler manages tasks wrapped in scheduler-specific container types that add necessary scheduling metadata.

Sources: src/lib.rs(L1 - L8)  src/fifo.rs(L14 - L22)  src/round_robin.rs(L46 - L56)  src/cfs.rs(L100 - L102) 

Implementation Architecture

Trait Implementation Hierarchy

flowchart TD
subgraph subGraph3["CFS Implementation"]
    CFS["CFScheduler<T>"]
    CFT["Arc<CFSTask<T>>"]
    CFS_queue["ready_queue: BTreeMap"]
    CFS_vtime["vruntime tracking"]
    CFS_nice["nice values"]
end
subgraph subGraph2["Round Robin Implementation"]
    RRS["RRScheduler<T, MAX_TIME_SLICE>"]
    RRT["Arc<RRTask<T, S>>"]
    RRS_queue["ready_queue: VecDeque"]
    RRS_slice["time_slice: AtomicIsize"]
end
subgraph subGraph1["FIFO Implementation"]
    FS["FifoScheduler<T>"]
    FT["Arc<FifoTask<T>>"]
    FS_queue["ready_queue: List"]
end
subgraph subGraph0["BaseScheduler Trait"]
    BS["BaseScheduler<SchedItem>"]
    BS_init["init()"]
    BS_add["add_task(task)"]
    BS_remove["remove_task(task)"]
    BS_pick["pick_next_task()"]
    BS_put["put_prev_task(prev, preempt)"]
    BS_tick["task_tick(current)"]
    BS_prio["set_priority(task, prio)"]
end

BS --> CFS
BS --> FS
BS --> RRS
CFS --> CFS_nice
CFS --> CFS_queue
CFS --> CFS_vtime
CFS --> CFT
FS --> FS_queue
FS --> FT
RRS --> RRS_queue
RRS --> RRS_slice
RRS --> RRT

Sources: src/lib.rs(L24 - L68)  src/fifo.rs(L23 - L38)  src/round_robin.rs(L58 - L73)  src/cfs.rs(L103 - L122) 

Task Wrapper Architecture

flowchart TD
subgraph subGraph2["Scheduling Metadata"]
    LinkedList["linked_list_r4l node"]
    TimeSlice["time_slice: AtomicIsize"]
    VRuntime["init_vruntime: AtomicIsize"]
    Delta["delta: AtomicIsize"]
    Nice["nice: AtomicIsize"]
    TaskId["id: AtomicIsize"]
end
subgraph subGraph1["Inner Task"]
    T["T (user task)"]
end
subgraph subGraph0["Task Wrappers"]
    FifoTask["FifoTask<T>"]
    RRTask["RRTask<T, MAX_TIME_SLICE>"]
    CFSTask["CFSTask<T>"]
end

CFSTask --> Delta
CFSTask --> Nice
CFSTask --> T
CFSTask --> TaskId
CFSTask --> VRuntime
FifoTask --> LinkedList
FifoTask --> T
RRTask --> T
RRTask --> TimeSlice

Sources: src/fifo.rs(L7 - L12)  src/round_robin.rs(L7 - L13)  src/cfs.rs(L7 - L14) 

Scheduling Behavior Comparison

Core Scheduling Methods

Each scheduler implements the BaseScheduler methods differently:

Task Selection (pick_next_task):

Task Insertion (put_prev_task):

Preemption Logic (task_tick):

Sources: src/fifo.rs(L40 - L68)  src/round_robin.rs(L75 - L113)  src/cfs.rs(L124 - L193) 

Priority Support

Only the CFScheduler supports dynamic priority adjustment through nice values:

flowchart TD
subgraph subGraph0["CFS Priority Implementation"]
    CFSWeight["get_weight() from nice value"]
    CFSVruntime["adjust virtual runtime calculation"]
    CFSNICES["NICE2WEIGHT_POS/NEG arrays"]
end
SetPrio["set_priority(task, prio)"]
FifoCheck["FifoScheduler?"]
RRCheck["RRScheduler?"]
CFSCheck["CFScheduler?"]
FifoReturn["return false"]
RRReturn["return false"]
CFSRange["prio in [-20, 19]?"]
CFSSet["task.set_priority(prio)"]
CFSFalse["return false"]
CFSTrue["return true"]

CFSCheck --> CFSRange
CFSRange --> CFSFalse
CFSRange --> CFSSet
CFSSet --> CFSTrue
CFSSet --> CFSWeight
CFSWeight --> CFSNICES
CFSWeight --> CFSVruntime
FifoCheck --> FifoReturn
RRCheck --> RRReturn
SetPrio --> CFSCheck
SetPrio --> FifoCheck
SetPrio --> RRCheck

Sources: src/fifo.rs(L65 - L67)  src/round_robin.rs(L110 - L112)  src/cfs.rs(L185 - L192)  src/cfs.rs(L23 - L29)  src/cfs.rs(L43 - L50) 

Data Structure Performance Characteristics

OperationFifoSchedulerRRSchedulerCFScheduler
add_taskO(1)O(1)O(log n)
pick_next_taskO(1)O(1)O(log n)
remove_taskO(1)O(n)O(log n)
put_prev_taskO(1)O(1)O(log n)
Memory overheadMinimalLowModerate

The choice of data structure reflects each scheduler's priorities:

  • FifoScheduler uses linked_list_r4l::List for optimal insertion/removal performance
  • RRScheduler uses VecDeque for simple front/back operations but suffers from O(n) arbitrary removal
  • CFScheduler uses BTreeMap keyed by (vruntime, task_id) to maintain virtual runtime ordering

Sources: src/fifo.rs(L23 - L25)  src/round_robin.rs(L58 - L60)  src/cfs.rs(L103 - L107) 

Thread Safety and Atomic Operations

All task metadata requiring atomic access is implemented using AtomicIsize:

This design enables safe concurrent access to task state during scheduling operations without requiring additional synchronization primitives.

Sources: src/round_robin.rs(L10 - L13)  src/cfs.rs(L8 - L14)  src/fifo.rs(L7 - L12) 

FIFO Scheduler

Relevant source files

Purpose and Scope

This document covers the FIFO (First-In-First-Out) scheduler implementation within the ArceOS scheduler crate. The FIFO scheduler provides cooperative task scheduling with simple queue-based ordering. For information about the unified scheduler interface, see Core Architecture. For comparisons with other scheduling algorithms, see Scheduler Comparison.

Overview

The FIFO scheduler is the simplest scheduling algorithm provided by the scheduler crate. It implements a cooperative, non-preemptive scheduling policy where tasks are executed in the order they were added to the ready queue.

Key Characteristics

CharacteristicValue
Scheduling PolicyCooperative, Non-preemptive
Data StructureLinked List (linked_list_r4l::List)
Task OrderingFirst-In-First-Out
Priority SupportNo
Timer PreemptionNo
Scheduler Name"FIFO"

Sources: src/fifo.rs(L14 - L22)  src/fifo.rs(L35 - L37) 

Implementation Architecture

FIFO Scheduler Structure

The FifoScheduler<T> maintains a single ready queue implemented as a linked list:

flowchart TD
subgraph subGraph1["Task Flow"]
    NewTask["New Task"]
    QueueTail["Queue Tail"]
    QueueHead["Queue Head"]
    RunningTask["Running Task"]
end
subgraph subGraph0["Ready Queue Operations"]
    ready_queue["ready_queue: List<Arc<FifoTask<T>>>"]
    push_back["push_back()"]
    pop_front["pop_front()"]
    remove["remove()"]
end
FifoScheduler["FifoScheduler<T>"]

FifoScheduler --> ready_queue
NewTask --> push_back
QueueHead --> pop_front
pop_front --> RunningTask
push_back --> QueueTail
ready_queue --> pop_front
ready_queue --> push_back
ready_queue --> remove

Sources: src/fifo.rs(L23 - L25)  src/fifo.rs(L46)  src/fifo.rs(L54)  src/fifo.rs(L50) 

Task Wrapper Implementation

FifoTask Structure

The FifoTask<T> wraps user tasks to work with the linked list data structure:

flowchart TD
subgraph subGraph2["Memory Management"]
    Arc["Arc<FifoTask<T>>"]
    SharedOwnership["Shared ownership across scheduler"]
end
subgraph subGraph1["Macro Generated"]
    def_node["def_node! macro"]
    NodeMethods["Node manipulation methods"]
end
subgraph subGraph0["Task Wrapper"]
    UserTask["User Task T"]
    FifoTask["FifoTask<T>"]
    LinkedListNode["Linked List Node"]
end

Arc --> SharedOwnership
FifoTask --> Arc
FifoTask --> LinkedListNode
UserTask --> FifoTask
def_node --> LinkedListNode
def_node --> NodeMethods

The FifoTask is defined using the def_node! macro from linked_list_r4l, which automatically generates the necessary linked list node methods.

Sources: src/fifo.rs(L7 - L12) 

BaseScheduler Implementation

Core Scheduling Methods

The FifoScheduler implements all required BaseScheduler trait methods:

flowchart TD
subgraph subGraph1["FIFO Behavior"]
    push_back_queue["push_back to ready_queue"]
    pop_front_queue["pop_front from ready_queue"]
    push_back_queue2["push_back to ready_queue"]
    no_reschedule["return false (no reschedule)"]
    not_supported["return false (not supported)"]
end
subgraph subGraph0["Required Methods"]
    FifoScheduler["FifoScheduler<T>"]
    init["init()"]
    add_task["add_task()"]
    remove_task["remove_task()"]
    pick_next_task["pick_next_task()"]
    put_prev_task["put_prev_task()"]
    task_tick["task_tick()"]
    set_priority["set_priority()"]
end
BaseScheduler["BaseScheduler trait"]

BaseScheduler --> FifoScheduler
FifoScheduler --> add_task
FifoScheduler --> init
FifoScheduler --> pick_next_task
FifoScheduler --> put_prev_task
FifoScheduler --> remove_task
FifoScheduler --> set_priority
FifoScheduler --> task_tick
add_task --> push_back_queue
pick_next_task --> pop_front_queue
put_prev_task --> push_back_queue2
set_priority --> not_supported
task_tick --> no_reschedule

Sources: src/fifo.rs(L40 - L68) 

Method Implementations

MethodImplementationReturn Value
init()No initialization required()
add_task()Adds task to rear of queue viapush_back()()
remove_task()Removes task using unsaferemove()OptionSelf::SchedItem
pick_next_task()Gets task from front viapop_front()OptionSelf::SchedItem
put_prev_task()Adds task back to rear of queue()
task_tick()No action (cooperative scheduling)false
set_priority()No priority supportfalse

Sources: src/fifo.rs(L43 - L67) 

Scheduling Behavior

Task Lifecycle in FIFO Scheduler

sequenceDiagram
    participant ArceOSKernel as "ArceOS Kernel"
    participant FifoScheduler as "FifoScheduler"
    participant ready_queue as "ready_queue"
    participant FifoTask as "FifoTask"

    ArceOSKernel ->> FifoScheduler: add_task(task)
    FifoScheduler ->> ready_queue: push_back(task)
    Note over ready_queue: Task added to rear
    ArceOSKernel ->> FifoScheduler: pick_next_task()
    ready_queue ->> FifoScheduler: pop_front()
    FifoScheduler ->> ArceOSKernel: return task
    Note over ArceOSKernel: Task runs cooperatively
    ArceOSKernel ->> FifoScheduler: task_tick(current_task)
    FifoScheduler ->> ArceOSKernel: return false
    Note over ArceOSKernel: No preemption occurs
    ArceOSKernel ->> FifoScheduler: put_prev_task(task, preempt)
    FifoScheduler ->> ready_queue: push_back(task)
    Note over ready_queue: Task goes to rear again

Sources: src/fifo.rs(L45 - L46)  src/fifo.rs(L53 - L54)  src/fifo.rs(L57 - L58)  src/fifo.rs(L61 - L62) 

Cooperative Scheduling Model

The FIFO scheduler operates under a cooperative model where:

  1. No Preemption: Tasks run until they voluntarily yield control
  2. Timer Tick Ignored: The task_tick() method always returns false, indicating no need for rescheduling
  3. Simple Ordering: Tasks are scheduled strictly in FIFO order
  4. No Priorities: All tasks have equal scheduling priority

Sources: src/fifo.rs(L20)  src/fifo.rs(L61 - L62)  src/fifo.rs(L65 - L66) 

Constructor and Utility Methods

The FifoScheduler provides simple construction and identification:

// Constructor
pub const fn new() -> Self

// Scheduler identification  
pub fn scheduler_name() -> &'static str

The new() method creates an empty scheduler with an initialized but empty ready_queue. The scheduler_name() method returns the string "FIFO" for identification purposes.

Sources: src/fifo.rs(L28 - L33)  src/fifo.rs(L34 - L37) 

Completely Fair Scheduler (CFS)

Relevant source files

This document covers the Completely Fair Scheduler (CFS) implementation provided by the scheduler crate. The CFS implementation aims to provide fair CPU time allocation among tasks based on virtual runtime calculations and supports priority adjustment through nice values.

For information about the overall scheduler architecture and BaseScheduler trait, see Core Architecture. For comparisons with other scheduler implementations, see Scheduler Comparison.

Purpose and Fairness Model

The CFScheduler implements a simplified version of the Linux Completely Fair Scheduler algorithm. It maintains fairness by tracking virtual runtime for each task and always selecting the task with the lowest virtual runtime for execution. Tasks with higher priority (lower nice values) accumulate virtual runtime more slowly, allowing them to run more frequently.

Key Fairness Principles:

  • Tasks are ordered by virtual runtime in a red-black tree (implemented as BTreeMap)
  • Virtual runtime accumulates based on actual execution time and task weight
  • Higher priority tasks (negative nice values) get lower weights, accumulating vruntime slower
  • The scheduler always picks the leftmost task (lowest virtual runtime) from the tree

Sources: src/cfs.rs(L100 - L122) 

Core Data Structures

CFSTask Wrapper

The CFSTask<T> struct wraps user tasks with CFS-specific metadata:

FieldTypePurpose
innerTThe wrapped user task
init_vruntimeAtomicIsizeInitial virtual runtime baseline
deltaAtomicIsizeAccumulated execution time delta
niceAtomicIsizePriority value (-20 to 19 range)
idAtomicIsizeUnique task identifier
flowchart TD
CFSTask["CFSTask<T>"]
inner["inner: T"]
init_vruntime["init_vruntime: AtomicIsize"]
delta["delta: AtomicIsize"]
nice["nice: AtomicIsize"]
id["id: AtomicIsize"]
vruntime_calc["Virtual Runtime Calculation"]
weight_lookup["Nice-to-Weight Lookup"]
scheduling_decision["Scheduling Decision"]

CFSTask --> delta
CFSTask --> id
CFSTask --> init_vruntime
CFSTask --> inner
CFSTask --> nice
delta --> vruntime_calc
init_vruntime --> vruntime_calc
nice --> weight_lookup
vruntime_calc --> scheduling_decision
weight_lookup --> vruntime_calc

Sources: src/cfs.rs(L7 - L14)  src/cfs.rs(L31 - L41) 

CFScheduler Structure

The main scheduler maintains a BTreeMap as the ready queue and tracks global minimum virtual runtime:

flowchart TD
CFScheduler["CFScheduler<T>"]
ready_queue["ready_queue: BTreeMap<(isize, isize), Arc<CFSTask<T>>>"]
min_vruntime["min_vruntime: Option<AtomicIsize>"]
id_pool["id_pool: AtomicIsize"]
key_structure["Key: (vruntime, taskid)"]
value_structure["Value: Arc<CFSTask<T>>"]
ordering["BTreeMap Natural Ordering"]
leftmost["Leftmost = Lowest vruntime"]
pick_next["pick_next_task()"]

CFScheduler --> id_pool
CFScheduler --> min_vruntime
CFScheduler --> ready_queue
key_structure --> ordering
leftmost --> pick_next
ordering --> leftmost
ready_queue --> key_structure
ready_queue --> value_structure

Sources: src/cfs.rs(L103 - L107)  src/cfs.rs(L109 - L122) 

Virtual Runtime Calculation

The virtual runtime algorithm ensures fairness by adjusting how quickly tasks accumulate runtime based on their priority:

Nice-to-Weight Conversion

CFS uses Linux-compatible lookup tables to convert nice values to weights:

flowchart TD
nice_value["Nice Value (-20 to 19)"]
weight_check["nice >= 0?"]
pos_table["NICE2WEIGHT_POS[nice]"]
neg_table["NICE2WEIGHT_NEG[-nice]"]
weight_result["Task Weight"]
vruntime_formula["vruntime = init + delta * 1024 / weight"]

neg_table --> weight_result
nice_value --> weight_check
pos_table --> weight_result
weight_check --> neg_table
weight_check --> pos_table
weight_result --> vruntime_formula

The weight tables are defined as constants with nice value 0 corresponding to weight 1024:

  • Positive nice values (0-19): Lower priority, higher weights
  • Negative nice values (-20 to -1): Higher priority, lower weights

Sources: src/cfs.rs(L16 - L29)  src/cfs.rs(L43 - L50) 

Runtime Accumulation

flowchart TD
task_tick["task_tick()"]
delta_increment["delta.fetch_add(1)"]
get_vruntime["get_vruntime()"]
nice_check["nice == 0?"]
simple_calc["init_vruntime + delta"]
weighted_calc["init_vruntime + delta * 1024 / weight"]
final_vruntime["Virtual Runtime"]
btreemap_key["BTreeMap Key: (vruntime, id)"]

final_vruntime --> btreemap_key
get_vruntime --> nice_check
nice_check --> simple_calc
nice_check --> weighted_calc
simple_calc --> final_vruntime
task_tick --> delta_increment
weighted_calc --> final_vruntime

Sources: src/cfs.rs(L56 - L63)  src/cfs.rs(L83 - L85) 

Scheduling Algorithm Flow

Task Addition and Removal

sequenceDiagram
    participant Kernel as Kernel
    participant CFScheduler as CFScheduler
    participant BTreeMap as BTreeMap
    participant CFSTask as CFSTask

    Kernel ->> CFScheduler: add_task(task)
    CFScheduler ->> CFScheduler: assign_id()
    CFScheduler ->> CFSTask: set_vruntime(min_vruntime)
    CFScheduler ->> CFSTask: set_id(taskid)
    CFScheduler ->> BTreeMap: insert((vruntime, taskid), task)
    CFScheduler ->> CFScheduler: update_min_vruntime()
    Note over CFScheduler,BTreeMap: Task ordered by virtual runtime
    Kernel ->> CFScheduler: pick_next_task()
    CFScheduler ->> BTreeMap: pop_first()
    BTreeMap -->> CFScheduler: leftmost task
    CFScheduler -->> Kernel: task with lowest vruntime

Sources: src/cfs.rs(L129 - L143)  src/cfs.rs(L161 - L167) 

Preemption Decision

The CFS scheduler makes preemption decisions in task_tick() by comparing the current task's virtual runtime with the minimum virtual runtime in the ready queue:

flowchart TD
task_tick_call["task_tick(current)"]
increment_delta["current.task_tick()"]
check_empty["ready_queue.is_empty()?"]
no_preempt["return false"]
compare_vruntime["current.vruntime > min_vruntime?"]
should_preempt["return true"]
continue_running["return false"]

check_empty --> compare_vruntime
check_empty --> no_preempt
compare_vruntime --> continue_running
compare_vruntime --> should_preempt
increment_delta --> check_empty
task_tick_call --> increment_delta

Sources: src/cfs.rs(L176 - L183) 

Priority Management

CFS supports dynamic priority changes through the nice value interface:

flowchart TD
set_priority["set_priority(task, nice)"]
validate_range["nice in [-20, 19]?"]
return_false["return false"]
save_current["current_vruntime = task.get_vruntime()"]
update_init["init_vruntime = current_vruntime"]
reset_delta["delta = 0"]
set_nice["nice = new_nice"]
return_true["return true"]

reset_delta --> set_nice
save_current --> update_init
set_nice --> return_true
set_priority --> validate_range
update_init --> reset_delta
validate_range --> return_false
validate_range --> save_current

This implementation preserves the task's current virtual runtime position while applying the new priority for future runtime accumulation.

Sources: src/cfs.rs(L69 - L77)  src/cfs.rs(L185 - L192) 

BaseScheduler Trait Integration

The CFScheduler implements the BaseScheduler trait with the following type and method mappings:

BaseScheduler MethodCFS ImplementationBehavior
SchedItemArc<CFSTask>Reference-counted task wrapper
add_task()Insert into BTreeMapAssigns ID and initial vruntime
remove_task()Remove from BTreeMapUpdates min_vruntime tracking
pick_next_task()pop_first()Selects leftmost (lowest vruntime)
put_prev_task()Re-insert with new IDMaintains vruntime ordering
task_tick()Compare with min_vruntimePreemption based on fairness
set_priority()Update nice valueRange validation and vruntime preservation

Sources: src/cfs.rs(L124 - L193) 

Thread Safety and Atomics

All CFS task metadata uses atomic operations for thread-safe access:

flowchart TD
CFSTask_atomics["CFSTask Atomic Fields"]
init_vruntime_atomic["init_vruntime: AtomicIsize"]
delta_atomic["delta: AtomicIsize"]
nice_atomic["nice: AtomicIsize"]
id_atomic["id: AtomicIsize"]
ordering_acquire["Ordering::Acquire/Release"]
memory_safety["Memory-safe concurrent access"]

CFSTask_atomics --> delta_atomic
CFSTask_atomics --> id_atomic
CFSTask_atomics --> init_vruntime_atomic
CFSTask_atomics --> nice_atomic
delta_atomic --> ordering_acquire
id_atomic --> ordering_acquire
init_vruntime_atomic --> ordering_acquire
nice_atomic --> ordering_acquire
ordering_acquire --> memory_safety

The scheduler itself is not thread-safe and requires external synchronization, but individual task metadata can be safely accessed concurrently.

Sources: src/cfs.rs(L10 - L13)  src/cfs.rs(L44)  src/cfs.rs(L57)  src/cfs.rs(L66)  src/cfs.rs(L76)  src/cfs.rs(L84) 

Round Robin Scheduler

Relevant source files

Purpose and Scope

This document covers the Round Robin scheduler implementation (RRScheduler) in the scheduler crate, which provides preemptive scheduling based on time quantum allocation. The Round Robin scheduler ensures fair CPU time distribution among tasks by rotating execution using fixed time slices.

For information about the unified scheduler interface, see Core Architecture. For comparisons with other scheduling algorithms, see Scheduler Comparison.

Architecture Overview

The Round Robin scheduler consists of two main components: the RRTask wrapper that adds time slice tracking to tasks, and the RRScheduler that implements the preemptive scheduling logic using a FIFO queue with time quantum management.

Round Robin Scheduler Components

flowchart TD
subgraph subGraph1["Round Robin Scheduler System"]
    RRScheduler["RRScheduler<T, MAX_TIME_SLICE>"]
    VecDeque["VecDeque<Arc<RRTask>>"]
    RRTask["RRTask<T, MAX_TIME_SLICE>"]
    AtomicIsize["AtomicIsize time_slice"]
    subgraph subGraph0["BaseScheduler Interface"]
        AddTask["add_task()"]
        RemoveTask["remove_task()"]
        PickNext["pick_next_task()"]
        PutPrev["put_prev_task()"]
        TaskTick["task_tick()"]
        SetPriority["set_priority()"]
    end
end

RRScheduler --> AddTask
RRScheduler --> PickNext
RRScheduler --> PutPrev
RRScheduler --> RemoveTask
RRScheduler --> SetPriority
RRScheduler --> TaskTick
RRScheduler --> VecDeque
RRTask --> AtomicIsize
VecDeque --> RRTask

Sources: src/round_robin.rs(L1 - L114) 

RRTask Wrapper

The RRTask wrapper extends any task type T with time slice management capabilities. It maintains an atomic counter that tracks the remaining time slice for preemption decisions.

RRTask Structure and Methods

MethodPurposeAtomicity
new(inner: T)Creates wrapper with initial time slice set toMAX_TIME_SLICE-
time_slice()Returns current time slice valueAcquireordering
reset_time_slice()Resets time slice toMAX_TIME_SLICEReleaseordering
inner()Provides access to wrapped task-

The time slice counter is implemented as an AtomicIsize to ensure thread-safe access during concurrent scheduler operations.

Sources: src/round_robin.rs(L7 - L44) 

RRScheduler Implementation

The RRScheduler implements the BaseScheduler trait using a VecDeque as the ready queue. This provides O(1) insertion and removal at both ends, but O(n) removal from arbitrary positions.

Scheduler Data Structure and Core Methods

flowchart TD
subgraph subGraph2["RRScheduler Operations"]
    ReadyQueue["ready_queue: VecDeque"]
    subgraph subGraph1["Time Management"]
        TaskTick["task_tick()"]
        TimeSlice["time_slice counter"]
    end
    subgraph subGraph0["Task Management"]
        AddTask["add_task()"]
        PickNext["pick_next_task()"]
        PutPrev["put_prev_task()"]
        RemoveTask["remove_task()"]
    end
end

AddTask --> ReadyQueue
PutPrev --> ReadyQueue
ReadyQueue --> PickNext
RemoveTask --> ReadyQueue
TaskTick --> TimeSlice
TimeSlice --> PutPrev

Key Implementation Details

  • Ready Queue: Uses VecDeque<Arc<RRTask<T, MAX_TIME_SLICE>>> for task storage
  • Task Addition: New tasks are added to the back of the queue via push_back()
  • Task Selection: Next task is selected from the front via pop_front()
  • Task Removal: Requires O(n) search using Arc::ptr_eq() for pointer comparison
  • Priority Support: Not supported - set_priority() always returns false

Sources: src/round_robin.rs(L58 - L113) 

Time Slice Management

The Round Robin scheduler's core feature is its time quantum management system. Each task receives a fixed time slice that determines how long it can execute before being preempted.

Time Slice Lifecycle

flowchart TD
subgraph subGraph0["put_prev_task() Logic"]
    PutPrev["put_prev_task(prev, preempt)"]
    CheckPreempt["preempt && time_slice > 0?"]
    QueueFront["push_front()"]
    ResetAndBack["reset_time_slice() + push_back()"]
end
TaskCreated["Task Created"]
InitTimeSlice["time_slice = MAX_TIME_SLICE"]
Running["Task Running"]
TimerTick["Timer Tick Occurs"]
DecrementSlice["time_slice--"]
CheckSlice["time_slice <= 0?"]
Preempted["Task Preempted"]
ResetSlice["reset_time_slice()"]
BackToQueue["Added to Queue Back"]

BackToQueue --> QueueFront
BackToQueue --> ResetAndBack
CheckPreempt --> QueueFront
CheckPreempt --> ResetAndBack
CheckSlice --> Preempted
CheckSlice --> Running
DecrementSlice --> CheckSlice
InitTimeSlice --> Running
Preempted --> ResetSlice
PutPrev --> CheckPreempt
ResetSlice --> BackToQueue
Running --> TimerTick
TaskCreated --> InitTimeSlice
TimerTick --> DecrementSlice

Timer Tick Implementation

The task_tick() method implements the time slice decrement logic:

// From src/round_robin.rs:105-108
fn task_tick(&mut self, current: &Self::SchedItem) -> bool {
    let old_slice = current.time_slice.fetch_sub(1, Ordering::Release);
    old_slice <= 1
}

This atomic operation decrements the time slice and returns true when the task should be preempted (when the slice reaches zero).

Sources: src/round_robin.rs(L105 - L108) 

Scheduling Behavior

The Round Robin scheduler exhibits specific behavior patterns that distinguish it from other scheduling algorithms in the crate.

Task State Transitions

Scheduler MethodActionQueue PositionTime Slice Action
add_task()Add new taskBack of queueSet toMAX_TIME_SLICE
pick_next_task()Select taskRemove from frontNo change
put_prev_task()(preempted, slice > 0)Voluntary yieldFront of queueNo change
put_prev_task()(preempted, slice = 0)Time expiredBack of queueReset toMAX_TIME_SLICE
put_prev_task()(not preempted)Cooperative yieldBack of queueReset toMAX_TIME_SLICE

Preemption Logic

The scheduler distinguishes between voluntary and involuntary preemption in its put_prev_task() implementation:

  • Voluntary Preemption: Task still has time slice remaining and yielded voluntarily - placed at front of queue
  • Time Expiration: Task's time slice reached zero - time slice reset and task placed at back of queue
  • Cooperative Yield: Task not preempted (blocking I/O, etc.) - time slice reset and task placed at back of queue

Sources: src/round_robin.rs(L96 - L103) 

Performance Characteristics

Computational Complexity

OperationTime ComplexitySpace ComplexityNotes
add_task()O(1)O(1)VecDeque::push_back()
pick_next_task()O(1)O(1)VecDeque::pop_front()
remove_task()O(n)O(1)Linear search withArc::ptr_eq()
put_prev_task()O(1)O(1)VecDeque::push_front()orpush_back()
task_tick()O(1)O(1)Atomic decrement operation

Design Tradeoffs

  • Fairness: Provides time-based fairness through fixed time slices
  • Preemption: Supports timer-based preemption unlike FIFO scheduler
  • Simplicity: Simpler than CFS but more complex than FIFO
  • Performance: O(n) task removal limits scalability for workloads with frequent task removal
  • Priority: No priority support - all tasks receive equal time allocation

Sources: src/round_robin.rs(L84 - L90)  src/round_robin.rs(L110 - L112) 

Scheduler Comparison

Relevant source files

This document compares the three scheduler implementations provided by the scheduler crate: FIFO, Round Robin, and Completely Fair Scheduler (CFS). The comparison covers their scheduling algorithms, data structures, performance characteristics, and appropriate use cases.

For detailed implementation specifics of each scheduler, see FIFO Scheduler, Round Robin Scheduler, and Completely Fair Scheduler.

Scheduler Overview

The scheduler crate provides three distinct scheduling algorithms, each implementing the BaseScheduler trait but with fundamentally different approaches to task management and execution ordering.

SchedulerTypeData StructurePreemptionPriority SupportComplexity
FifoSchedulerCooperativeList<Arc<FifoTask>>NoneNoneO(1) add/pick, O(n) remove
RRSchedulerPreemptiveVecDeque<Arc<RRTask<T, S>>>Time-basedNoneO(1) add/pick, O(n) remove
CFSchedulerPreemptiveBTreeMap<(isize, isize), Arc<CFSTask>>Virtual runtimeNice values (-20 to 19)O(log n) all operations

Sources: src/fifo.rs(L23 - L25)  src/round_robin.rs(L58 - L60)  src/cfs.rs(L103 - L107) 

Data Structure Comparison

Ready Queue Implementation Comparison

flowchart TD
subgraph subGraph2["CFScheduler Data Flow"]
    C1["BTreeMap<(isize, isize), Arc>>"]
    C2["insert((vruntime, taskid))"]
    C3["pop_first()"]
    C5["Virtual Runtime Ordering"]
    B1["VecDeque>>"]
    B2["push_back()"]
    B3["pop_front()"]
    B5["Time Slice Management"]
    A1["List>>"]
    A2["push_back()"]
    A3["pop_front()"]
    A4["FIFO Order"]
    subgraph subGraph1["RRScheduler Data Flow"]
        C4["remove_entry()"]
        B4["remove(idx)"]
        subgraph subGraph0["FifoScheduler Data Flow"]
            C1["BTreeMap<(isize, isize), Arc>>"]
            C2["insert((vruntime, taskid))"]
            C3["pop_first()"]
            C5["Virtual Runtime Ordering"]
            B1["VecDeque>>"]
            B2["push_back()"]
            B3["pop_front()"]
            B5["Time Slice Management"]
            A1["List>>"]
            A2["push_back()"]
            A3["pop_front()"]
            A4["FIFO Order"]
        end
    end
end

A1 --> A2
A1 --> A3
A2 --> A4
A3 --> A4
B1 --> B2
B1 --> B3
B1 --> B4
B2 --> B5
B3 --> B5
B4 --> B5
C1 --> C2
C1 --> C3
C1 --> C4
C2 --> C5
C3 --> C5
C4 --> C5

Sources: src/fifo.rs(L46 - L47)  src/round_robin.rs(L81 - L82)  src/cfs.rs(L137) 

Task Wrapper Feature Comparison

flowchart TD
subgraph CFSTask<T>["CFSTask"]
    C1["inner: T"]
    C2["init_vruntime: AtomicIsize"]
    C4["nice: AtomicIsize"]
    C5["id: AtomicIsize"]
    R1["inner: T"]
    R2["time_slice: AtomicIsize"]
    F1["inner: T"]
    F2["No additional state"]
    subgraph subGraph1["RRTask"]
        C3["delta: AtomicIsize"]
        R3["MAX_TIME_SLICE: const"]
        subgraph FifoTask<T>["FifoTask"]
            C1["inner: T"]
            C2["init_vruntime: AtomicIsize"]
            R1["inner: T"]
            R2["time_slice: AtomicIsize"]
            F1["inner: T"]
            F2["No additional state"]
        end
    end
end

Sources: src/fifo.rs(L7 - L12)  src/round_robin.rs(L10 - L13)  src/cfs.rs(L8 - L14) 

Scheduling Behavior Analysis

Preemption and Time Management

MethodFifoSchedulerRRSchedulerCFScheduler
task_tick()Always returnsfalseDecrements time slice, returnstruewhen<= 1Incrementsdelta, compares withmin_vruntime
put_prev_task()Always re-queues at backConditional placement based on preemption and time sliceRe-inserts with updatedvruntime
Preemption LogicNone (cooperative)old_slice <= 1current.get_vruntime() > min_vruntime

Sources: src/fifo.rs(L61 - L63)  src/round_robin.rs(L105 - L108)  src/cfs.rs(L176 - L183) 

Priority and Weight System

flowchart TD
subgraph subGraph2["CFS Priority Handling"]
    C1["set_priority(nice)"]
    C2["Range check: -20 <= nice <= 19"]
    C3["NICE2WEIGHT_POS/NEG lookup"]
    C4["Virtual runtime adjustment"]
    C5["Weight-based scheduling"]
    R1["set_priority()"]
    R2["return false"]
    F1["set_priority()"]
    F2["return false"]
end

C1 --> C2
C2 --> C3
C3 --> C4
C4 --> C5
F1 --> F2
R1 --> R2

Sources: src/fifo.rs(L65 - L67)  src/round_robin.rs(L110 - L112)  src/cfs.rs(L185 - L192) 

Performance Characteristics

Algorithmic Complexity

OperationFifoSchedulerRRSchedulerCFScheduler
add_task()O(1) -push_back()O(1) -push_back()O(log n) -BTreeMap::insert()
remove_task()O(n) - linked list traversalO(n) -VecDequesearchO(log n) -BTreeMap::remove_entry()
pick_next_task()O(1) -pop_front()O(1) -pop_front()O(log n) -BTreeMap::pop_first()
put_prev_task()O(1) -push_back()O(1) - conditional placementO(log n) -BTreeMap::insert()

Sources: src/fifo.rs(L45 - L58)  src/round_robin.rs(L80 - L102)  src/cfs.rs(L129 - L174) 

Memory Overhead

flowchart TD
subgraph subGraph1["Scheduler State"]
    subgraph subGraph0["Memory per Task"]
        D["FifoScheduler: List head/tail pointers"]
        E["RRScheduler: VecDeque capacity + length"]
        F["CFScheduler: BTreeMap tree + AtomicIsize + AtomicIsize"]
        A["FifoTask: sizeof(T) + linked list pointers"]
        B["RRTask: sizeof(T) + AtomicIsize"]
        C["CFSTask: sizeof(T) + 4 × AtomicIsize"]
    end
end

Sources: src/fifo.rs(L7 - L12)  src/round_robin.rs(L10 - L13)  src/cfs.rs(L8 - L14)  src/cfs.rs(L103 - L107) 

Virtual Runtime Calculation in CFS

The CFS implementation uses a sophisticated virtual runtime system based on Linux CFS:

flowchart TD
A["task_tick()"]
B["delta.fetch_add(1)"]
C["get_vruntime()"]
D["nice == 0?"]
E["vruntime = init_vruntime + delta"]
F["vruntime = init_vruntime + delta * 1024 / weight"]
G["weight = NICE2WEIGHT_POS/NEG[nice]"]
H["Compare with min_vruntime"]
I["Preemption decision"]

A --> B
B --> C
C --> D
D --> E
D --> F
E --> H
F --> G
F --> H
H --> I

Sources: src/cfs.rs(L56 - L63)  src/cfs.rs(L83 - L85)  src/cfs.rs(L23 - L29) 

Use Case Recommendations

FifoScheduler

Best for:

  • Embedded systems with predictable workloads
  • Cooperative multitasking environments
  • Systems where task completion order matters
  • Low-overhead requirements

Limitations:

  • No fairness guarantees
  • No preemption support
  • Potential for task starvation

RRScheduler

Best for:

  • Interactive systems requiring responsiveness
  • Equal priority tasks
  • Time-sharing systems
  • Simple preemptive scheduling needs

Limitations:

  • No priority differentiation
  • Fixed time quantum may not suit all workloads
  • O(n) task removal cost

CFScheduler

Best for:

  • Multi-user systems
  • Priority-aware workloads
  • Fair resource allocation requirements
  • Linux-compatible scheduling behavior

Limitations:

  • Higher computational overhead
  • More complex implementation
  • Memory overhead from priority tracking

Sources: src/fifo.rs(L14 - L22)  src/round_robin.rs(L46 - L56)  src/cfs.rs(L100 - L102) 

Testing Framework

Relevant source files

The testing framework provides a unified, macro-based testing infrastructure that ensures consistent validation and performance measurement across all scheduler implementations in the crate. The framework automatically generates comprehensive test suites for each scheduler type, including basic functionality verification and performance benchmarks.

For information about the specific scheduler implementations being tested, see Scheduler Implementations. For details about the development workflow and CI pipeline that executes these tests, see Development Guide.

Macro-Based Test Generation

The testing framework is built around the def_test_sched macro, which generates a complete test module for any given scheduler and task type combination. This approach ensures that all scheduler implementations are tested consistently with identical test logic.

Test Generation Architecture

flowchart TD
MacroDefinition["def_test_sched macro"]
MacroParams["Parameters: name, scheduler, task"]
ModuleGeneration["Generated Test Module"]
BasicTest["test_sched()"]
YieldBench["bench_yield()"]
RemoveBench["bench_remove()"]
MacroInvocations["Macro Invocations"]
FifoTests["fifo module"]
RRTests["rr module"]
CFSTests["cfs module"]
FifoScheduler["FifoScheduler"]
FifoTask["FifoTask"]
RRScheduler["RRScheduler"]
RRTask["RRTask"]
CFScheduler["CFScheduler"]
CFSTask["CFSTask"]

CFSTests --> CFSTask
CFSTests --> CFScheduler
FifoTests --> FifoScheduler
FifoTests --> FifoTask
MacroDefinition --> MacroParams
MacroInvocations --> CFSTests
MacroInvocations --> FifoTests
MacroInvocations --> RRTests
MacroParams --> ModuleGeneration
ModuleGeneration --> BasicTest
ModuleGeneration --> RemoveBench
ModuleGeneration --> YieldBench
RRTests --> RRScheduler
RRTests --> RRTask

Sources: src/tests.rs(L1 - L84) 

The macro accepts three parameters: a module name identifier, a scheduler type, and a task wrapper type. It then generates a complete test module containing three distinct test functions that exercise different aspects of scheduler behavior.

Test Types

The framework provides three categories of tests, each targeting specific scheduler capabilities and performance characteristics.

Basic Functionality Tests

The test_sched function verifies core scheduler operations using a controlled set of 11 tasks. This test validates the fundamental scheduler contract defined by the BaseScheduler trait.

Test Execution Flow

flowchart TD
TestStart["test_sched() begins"]
CreateScheduler["scheduler = Scheduler::new()"]
AddTasks["Add 11 tasks (i = 0..10)"]
SchedulingLoop["Execute 109 scheduling cycles"]
PickNext["pick_next_task()"]
ValidateTask["assert_eq!(*next.inner(), i % 11)"]
TaskTick["task_tick(&next)"]
PutPrev["put_prev_task(next, false)"]
NextIteration["i < 109?"]
DrainScheduler["Drain remaining tasks"]
CountTasks["Count remaining tasks"]
ValidateCount["assert_eq!(n, 11)"]
TestComplete["Test Complete"]

AddTasks --> SchedulingLoop
CountTasks --> ValidateCount
CreateScheduler --> AddTasks
DrainScheduler --> CountTasks
NextIteration --> DrainScheduler
NextIteration --> PickNext
PickNext --> ValidateTask
PutPrev --> NextIteration
SchedulingLoop --> PickNext
TaskTick --> PutPrev
TestStart --> CreateScheduler
ValidateCount --> TestComplete
ValidateTask --> TaskTick

Sources: src/tests.rs(L8 - L29) 

The test performs multiple scheduling rounds to verify that tasks are selected in the expected order based on each scheduler's algorithm. The task_tick call ensures proper time accounting for time-based schedulers like Round Robin and CFS.

Performance Benchmarks

The framework includes two performance benchmarks that measure different scheduler operations under load.

Yield Benchmark

The bench_yield function measures task switching performance by repeatedly picking and yielding tasks in a large task set.

ParameterValuePurpose
NUM_TASKS1,000,000Large task set for realistic load testing
COUNT3,000,000Number of yield operations (3x tasks)
MeasurementTime per yield operationScheduler switching overhead
flowchart TD
BenchSetup["Setup 1M tasks"]
BenchLoop["3M yield cycles"]
PickTask["pick_next_task()"]
PutTask["put_prev_task(task, false)"]
Measure["Measure elapsed time"]
Result["Time per yield operation"]

BenchLoop --> PickTask
BenchSetup --> BenchLoop
Measure --> Result
PickTask --> PutTask
PutTask --> Measure

Sources: src/tests.rs(L32 - L52) 

Remove Benchmark

The bench_remove function measures task removal performance by removing tasks from the scheduler in reverse order.

ParameterValuePurpose
NUM_TASKS10,000Moderate task set for removal testing
Removal OrderReverse (9999→0)Worst-case removal pattern
MeasurementTime per removalTask lookup and removal overhead

Sources: src/tests.rs(L54 - L77) 

Test Application Across Schedulers

The framework applies identical test logic to all three scheduler implementations through macro invocations, ensuring consistent validation while accommodating scheduler-specific type parameters.

Scheduler Test Instantiation

flowchart TD
MacroExpansion["def_test_sched macro expansion"]
TestModules["Generated Test Modules"]
FifoMod["mod fifo"]
RRMod["mod rr"]
CFSMod["mod cfs"]
FifoTypes["FifoScheduler, FifoTask"]
RRTypes["RRScheduler, RRTask"]
CFSTypes["CFScheduler, CFSTask"]
FifoTests["fifo::test_sched(), fifo::bench_yield(), fifo::bench_remove()"]
RRTests["rr::test_sched(), rr::bench_yield(), rr::bench_remove()"]
CFSTests["cfs::test_sched(), cfs::bench_yield(), cfs::bench_remove()"]

CFSMod --> CFSTypes
CFSTypes --> CFSTests
FifoMod --> FifoTypes
FifoTypes --> FifoTests
MacroExpansion --> TestModules
RRMod --> RRTypes
RRTypes --> RRTests
TestModules --> CFSMod
TestModules --> FifoMod
TestModules --> RRMod

Sources: src/tests.rs(L82 - L84) 

Each scheduler implementation receives type-appropriate parameters:

  • FIFO: Simple usize task payload with FifoScheduler and FifoTask
  • Round Robin: usize payload with time slice constant 5 for RRScheduler<usize, 5> and RRTask<usize, 5>
  • CFS: usize payload with CFScheduler and CFSTask for virtual runtime scheduling

Performance Measurement

The benchmarks provide quantitative performance data for comparing scheduler implementations. The framework measures operation latency using std::time::Instant and reports results in time per operation.

Benchmark Output Format

The benchmarks output performance metrics in a standardized format:

  • Yield Benchmark: "{scheduler_name}: task yield speed: {duration}/task"
  • Remove Benchmark: "{scheduler_name}: task remove speed: {duration}/task"

This consistent reporting enables direct performance comparison between scheduler algorithms and helps identify performance characteristics specific to each implementation.

Sources: src/tests.rs(L47 - L51)  src/tests.rs(L72 - L76) 

The testing framework ensures that all scheduler implementations meet the same functional requirements while providing objective performance data to guide scheduler selection for specific use cases.

Development Guide

Relevant source files

Purpose and Scope

This guide covers the development workflow, build system, and continuous integration pipeline for the scheduler crate. It provides instructions for contributors on setting up the development environment, running tests, and understanding the automated quality assurance processes.

For information about the testing framework and test structure, see Testing Framework. For details about the scheduler implementations themselves, see Scheduler Implementations.

Development Workflow

The scheduler crate follows a standard Rust development workflow with automated quality checks enforced through GitHub Actions.

Development Workflow Overview

flowchart TD
Developer["Developer"]
LocalDev["Local Development"]
CodeChanges["Code Changes"]
FormatCheck["cargo fmt --check"]
ClippyCheck["cargo clippy"]
LocalTest["cargo test"]
Commit["Git Commit"]
Push["Git Push"]
GitHub["GitHub Repository"]
CITrigger["CI Pipeline Triggered"]
MultiTarget["Multi-Target Build Matrix"]
FormatJob["Format Check Job"]
ClippyJob["Clippy Job"]
BuildJob["Build Job"]
TestJob["Unit Test Job"]
DocJob["Documentation Job"]
CIResult["CI Results"]
Success["Success: Deploy Docs"]
Failure["Failure: Fix Issues"]
Merge["Merge to Main"]

BuildJob --> CIResult
CIResult --> Failure
CIResult --> Success
CITrigger --> MultiTarget
ClippyCheck --> LocalTest
ClippyJob --> CIResult
CodeChanges --> FormatCheck
Commit --> Push
Developer --> LocalDev
DocJob --> CIResult
Failure --> CodeChanges
FormatCheck --> ClippyCheck
FormatJob --> CIResult
GitHub --> CITrigger
LocalDev --> CodeChanges
LocalTest --> Commit
MultiTarget --> BuildJob
MultiTarget --> ClippyJob
MultiTarget --> DocJob
MultiTarget --> FormatJob
MultiTarget --> TestJob
Push --> GitHub
Success --> Merge
TestJob --> CIResult

Sources: .github/workflows/ci.yml(L1 - L56) 

Local Development Setup

The project requires Rust nightly toolchain with specific components and target support:

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and code analysis
rustfmtCode formatting

Target architectures that must be installed locally:

  • x86_64-unknown-linux-gnu - Linux development and testing
  • x86_64-unknown-none - Bare metal x86_64
  • riscv64gc-unknown-none-elf - RISC-V bare metal
  • aarch64-unknown-none-softfloat - ARM64 bare metal

Sources: .github/workflows/ci.yml(L15 - L19) 

Continuous Integration Pipeline

The CI system uses GitHub Actions with a comprehensive test matrix covering multiple target architectures and quality checks.

CI Pipeline Architecture

flowchart TD
subgraph subGraph4["Documentation Pipeline"]
    DocBuild["cargo doc --no-deps --all-features"]
    IndexGeneration["Generate index.html redirect"]
    GitHubPages["Deploy to gh-pages branch"]
end
subgraph subGraph3["Quality Checks"]
    VersionCheck["rustc --version --verbose"]
    FormatCheck["cargo fmt --all -- --check"]
    ClippyLint["cargo clippy --target TARGET --all-features"]
    BuildStep["cargo build --target TARGET --all-features"]
    UnitTest["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph2["CI Job Matrix"]
    JobMatrix["CI Job Matrix"]
    RustNightly["rust-toolchain: nightly"]
    subgraph subGraph1["Target Architectures"]
        LinuxTarget["x86_64-unknown-linux-gnu"]
        BareMetalTarget["x86_64-unknown-none"]
        RiscvTarget["riscv64gc-unknown-none-elf"]
        ArmTarget["aarch64-unknown-none-softfloat"]
    end
end
subgraph subGraph0["GitHub Events"]
    PushEvent["push"]
    PREvent["pull_request"]
end

ArmTarget --> VersionCheck
BareMetalTarget --> VersionCheck
BuildStep --> UnitTest
ClippyLint --> BuildStep
DocBuild --> IndexGeneration
FormatCheck --> ClippyLint
IndexGeneration --> GitHubPages
JobMatrix --> DocBuild
JobMatrix --> RustNightly
LinuxTarget --> VersionCheck
PREvent --> JobMatrix
PushEvent --> JobMatrix
RiscvTarget --> VersionCheck
RustNightly --> ArmTarget
RustNightly --> BareMetalTarget
RustNightly --> LinuxTarget
RustNightly --> RiscvTarget
VersionCheck --> FormatCheck

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Configuration Details

The CI pipeline consists of two main jobs defined in .github/workflows/ci.yml(L6 - L56) :

Main CI Job (ci)

Documentation Job (doc)

Build System

Target Architecture Support

The scheduler crate supports four distinct target architectures, each serving different deployment scenarios:

TargetEnvironmentUse Case
x86_64-unknown-linux-gnuHosted LinuxDevelopment and testing
x86_64-unknown-noneBare metal x86_64ArceOS deployment
riscv64gc-unknown-none-elfBare metal RISC-VEmbedded ArceOS
aarch64-unknown-none-softfloatBare metal ARM64ARM-based systems

Sources: .github/workflows/ci.yml(L12) 

Build Process Flow

flowchart TD
subgraph subGraph2["Target Outputs"]
    LinuxBinary["Linux binary"]
    BareMetalLib["Bare metal library"]
    RiscvLib["RISC-V library"]
    ArmLib["ARM64 library"]
end
subgraph subGraph1["Build Process"]
    CargoCheck["cargo check"]
    CargoClippy["cargo clippy"]
    CargoBuild["cargo build"]
    CargoTest["cargo test"]
end
subgraph subGraph0["Source Code"]
    SourceFiles["src/ directory"]
    CargoToml["Cargo.toml"]
end
subgraph subGraph3["Excluded Files"]
    GitIgnore[".gitignore"]
    TargetDir["/target"]
    VSCodeDir["/.vscode"]
    CargoLock["Cargo.lock"]
end

CargoBuild --> ArmLib
CargoBuild --> BareMetalLib
CargoBuild --> CargoTest
CargoBuild --> LinuxBinary
CargoBuild --> RiscvLib
CargoCheck --> CargoClippy
CargoClippy --> CargoBuild
CargoToml --> CargoCheck
GitIgnore --> CargoLock
GitIgnore --> TargetDir
GitIgnore --> VSCodeDir
SourceFiles --> CargoCheck

Sources: .github/workflows/ci.yml(L25 - L27)  .gitignore(L1 - L4) 

Testing Procedures

Local Testing Commands

For comprehensive local testing, developers should run the following commands:

# Format check
cargo fmt --all -- --check

# Linting 
cargo clippy --all-features -- -A clippy::new_without_default

# Build for all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features

# Unit tests (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

Sources: .github/workflows/ci.yml(L23 - L30) 

Test Execution Strategy

The testing framework uses a conditional execution model:

  • Format and Linting: Applied to all targets
  • Build Verification: Cross-compiled for all supported architectures
  • Unit Test Execution: Limited to x86_64-unknown-linux-gnu target for practical execution

This approach ensures code quality across all platforms while maintaining efficient CI execution times.

Sources: .github/workflows/ci.yml(L29 - L30) 

Documentation System

Documentation Generation Process

flowchart TD
subgraph subGraph3["Quality Controls"]
    BrokenLinks["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links"]
    MissingDocs["RUSTDOCFLAGS: -D missing-docs"]
end
subgraph Deployment["Deployment"]
    GitHubPages["GitHub Pages"]
    GhPagesBranch["gh-pages branch"]
    MainBranchCheck["main branch check"]
end
subgraph subGraph1["Build Process"]
    CargoDoc["cargo doc --no-deps --all-features"]
    IndexGen["Generate index.html redirect"]
    TargetDoc["target/doc/ output"]
end
subgraph subGraph0["Documentation Sources"]
    RustSource["Rust source files"]
    DocComments["/// doc comments"]
    CargoToml["Cargo.toml metadata"]
end

BrokenLinks --> CargoDoc
CargoDoc --> IndexGen
CargoToml --> CargoDoc
DocComments --> CargoDoc
GhPagesBranch --> GitHubPages
IndexGen --> TargetDoc
MainBranchCheck --> GhPagesBranch
MissingDocs --> CargoDoc
RustSource --> CargoDoc
TargetDoc --> MainBranchCheck

Sources: .github/workflows/ci.yml(L40 - L55) 

Documentation Deployment

The documentation system enforces strict quality requirements through rustdoc flags:

Deployment occurs automatically when changes are pushed to the main branch, using the JamesIves/github-pages-deploy-action for single-commit deployment to the gh-pages branch .github/workflows/ci.yml(L51 - L55) 

Sources: .github/workflows/ci.yml(L32 - L55) 

Overview

Relevant source files

This document provides an introduction to the tuple_for_each crate, a Rust procedural macro library that generates iteration utilities for tuple structs. The crate enables developers to iterate over tuple struct fields in a type-safe manner through automatically generated macros and methods.

For implementation details, see Implementation Guide. For usage examples and getting started, see Getting Started. For complete API documentation, see API Reference.

Purpose and Core Functionality

The tuple_for_each crate addresses the limitation that Rust tuple structs cannot be iterated over directly since their fields may have different types. It provides a TupleForEach derive macro that automatically generates iteration utilities at compile time.

When applied to a tuple struct, the derive macro generates:

  • *_for_each! macro for field iteration
  • *_enumerate! macro for indexed field iteration
  • len() method returning the number of fields
  • is_empty() method checking if the tuple has fields

Core Architecture Overview

flowchart TD
subgraph Dependencies["Proc Macro Dependencies"]
    SynCrate["syn crate - AST parsing"]
    QuoteCrate["quote crate - code generation"]
    ProcMacro2["proc-macro2 - token streams"]
end
subgraph Generated["Generated Code"]
    ForEachMacro["*_for_each! macro"]
    EnumerateMacro["*_enumerate! macro"]
    LenMethod["len() method"]
    IsEmptyMethod["is_empty() method"]
end
UserTuple["User Tuple Struct with #[derive(TupleForEach)]"]
DeriveMacro["tuple_for_each() entry point"]
ImplForEach["impl_for_each() core logic"]

DeriveMacro --> ImplForEach
ImplForEach --> EnumerateMacro
ImplForEach --> ForEachMacro
ImplForEach --> IsEmptyMethod
ImplForEach --> LenMethod
ImplForEach --> ProcMacro2
ImplForEach --> QuoteCrate
ImplForEach --> SynCrate
UserTuple --> DeriveMacro

Sources: Cargo.toml(L1 - L21)  README.md(L1 - L40) 

Macro Processing Pipeline

The crate follows a standard procedural macro architecture where compile-time code generation produces runtime utilities. The process transforms user-defined tuple structs into enhanced types with iteration capabilities.

Compilation Flow

flowchart TD
subgraph ErrorHandling["Error Cases"]
    InvalidStruct["Invalid tuple struct"]
    CompileError["Compile-time error"]
end
Input["TokenStream input from #[derive(TupleForEach)]"]
ParseInput["syn::parse_macro_input!()"]
Validation["Validate tuple struct format"]
NameConversion["pascal_to_snake() conversion"]
FieldAccess["Generate field access patterns"]
MacroGeneration["Create macro_rules! definitions"]
QuoteExpansion["quote! macro expansion"]
Output["Generated TokenStream output"]

FieldAccess --> MacroGeneration
Input --> ParseInput
InvalidStruct --> CompileError
MacroGeneration --> QuoteExpansion
NameConversion --> FieldAccess
ParseInput --> Validation
QuoteExpansion --> Output
Validation --> InvalidStruct
Validation --> NameConversion

Sources: Cargo.toml(L14 - L17)  README.md(L20 - L39) 

Generated Code Structure

For each tuple struct, the macro generates a consistent set of utilities following a naming convention based on the struct name converted from PascalCase to snake_case.

Generated ItemPurposeExample forFooBarstruct
Iteration macroField-by-field iterationfoo_bar_for_each!(x in tuple { ... })
Enumeration macroIndexed field iterationfoo_bar_enumerate!((i, x) in tuple { ... })
Length methodField counttuple.len()
Empty check methodZero-field detectiontuple.is_empty()

The generated macros support both immutable and mutable access patterns, enabling flexible usage across different contexts.

Sources: README.md(L9 - L16)  README.md(L30 - L38) 

Target Use Cases

The crate is designed for systems programming contexts where tuple structs represent heterogeneous data collections that need iteration capabilities. The multi-target build configuration supports embedded and cross-platform development.

Supported Target Architectures:

  • x86_64-unknown-linux-gnu (full testing)
  • x86_64-unknown-none (build verification)
  • riscv64gc-unknown-none-elf (embedded RISC-V)
  • aarch64-unknown-none-softfloat (embedded ARM)

The no_std compatibility and embedded target support indicate the crate is suitable for resource-constrained environments where compile-time code generation provides runtime efficiency benefits.

Sources: Cargo.toml(L1 - L21)  README.md(L1 - L7) 

Dependencies and Ecosystem Integration

The crate follows standard Rust ecosystem patterns and integrates with the procedural macro infrastructure:

  • proc-macro2: Token stream manipulation and procedural macro utilities
  • quote: Template-based code generation with compile-time expansion
  • syn: Abstract syntax tree parsing and manipulation

The crate is configured as a procedural macro library through proc-macro = true in the [lib] section, making it available for use with #[derive()] attributes.

Sources: Cargo.toml(L14 - L21) 

Project Structure

Relevant source files

This document describes the architectural organization of the tuple_for_each crate, including its dependencies, build configuration, and development infrastructure. The project is a procedural macro library designed to generate iteration utilities for tuple structs, with particular emphasis on cross-platform compatibility for embedded systems development.

For details about the core macro implementation, see 3.1. For information about the CI/CD pipeline specifics, see 4.2.

Crate Architecture

The tuple_for_each crate follows a standard Rust procedural macro library structure, with the core implementation residing in src/lib.rs and supporting infrastructure for multi-platform builds and documentation.

Crate Configuration

flowchart TD
subgraph subGraph3["ArceOS Ecosystem"]
    HOMEPAGE["Homepage: arceos-org/arceos"]
    REPO["Repository: tuple_for_each"]
    DOCS["Documentation: docs.rs"]
end
subgraph Dependencies["Dependencies"]
    SYN["syn = '2.0'"]
    QUOTE["quote = '1.0'"]
    PM2["proc-macro2 = '1.0'"]
end
subgraph subGraph1["Crate Type"]
    PROC["proc-macro = true"]
    LIB["[lib] Configuration"]
end
subgraph subGraph0["Package Metadata"]
    PM["tuple_for_eachv0.1.0Edition 2021"]
    AUTH["Author: Yuekai Jia"]
    LIC["Triple License:GPL-3.0 | Apache-2.0 | MulanPSL-2.0"]
end

HOMEPAGE --> REPO
LIB --> PM2
LIB --> QUOTE
LIB --> SYN
PM --> HOMEPAGE
PM --> PROC
PROC --> LIB
REPO --> DOCS

Crate Configuration Details

The crate is configured as a procedural macro library through the proc-macro = true setting in Cargo.toml(L19 - L20)  This enables the crate to export procedural macros that operate at compile time. The package metadata indicates this is part of the ArceOS project ecosystem, focusing on systems programming and embedded development.

Sources: Cargo.toml(L1 - L21) 

Dependency Architecture

flowchart TD
subgraph subGraph2["Generated Artifacts"]
    MACROS["_for_each!_enumerate!"]
    METHODS["len()is_empty()"]
end
subgraph subGraph1["Procedural Macro Stack"]
    SYN_DEP["syn 2.0AST Parsing"]
    QUOTE_DEP["quote 1.0Code Generation"]
    PM2_DEP["proc-macro2 1.0Token Management"]
end
subgraph subGraph0["tuple_for_each Crate"]
    DERIVE["TupleForEachDerive Macro"]
    IMPL["impl_for_each()Core Logic"]
end

DERIVE --> PM2_DEP
DERIVE --> QUOTE_DEP
DERIVE --> SYN_DEP
IMPL --> QUOTE_DEP
IMPL --> SYN_DEP
PM2_DEP --> QUOTE_DEP
QUOTE_DEP --> MACROS
QUOTE_DEP --> METHODS
SYN_DEP --> IMPL

Dependency Roles

DependencyVersionPurpose
syn2.0Parsing Rust syntax trees from macro input tokens
quote1.0Generating Rust code from templates and interpolation
proc-macro21.0Low-level token stream manipulation and span handling

The dependency selection follows Rust procedural macro best practices, using the latest stable versions of the core macro development libraries.

Sources: Cargo.toml(L14 - L17) 

Build Matrix and Target Support

Multi-Platform Build Configuration

flowchart TD
subgraph subGraph2["Build Strategy"]
    FULL_TEST["Full CI PipelineFormat + Lint + Build + Test"]
    BUILD_ONLY["Build VerificationFormat + Lint + Build"]
end
subgraph subGraph1["Target Platforms"]
    X86_LINUX["x86_64-unknown-linux-gnuDevelopment & Testing"]
    X86_NONE["x86_64-unknown-noneBare Metal x86"]
    RISCV["riscv64gc-unknown-none-elfRISC-V Embedded"]
    ARM["aarch64-unknown-none-softfloatARM64 Embedded"]
end
subgraph subGraph0["Rust Toolchain"]
    NIGHTLY["nightlyRequired Toolchain"]
    COMPONENTS["Components:rust-src, clippy, rustfmt"]
end

ARM --> BUILD_ONLY
COMPONENTS --> ARM
COMPONENTS --> RISCV
COMPONENTS --> X86_LINUX
COMPONENTS --> X86_NONE
NIGHTLY --> COMPONENTS
RISCV --> BUILD_ONLY
X86_LINUX --> FULL_TEST
X86_NONE --> BUILD_ONLY

Target Platform Strategy

The build matrix demonstrates the crate's focus on embedded and systems programming:

  • Primary Development: x86_64-unknown-linux-gnu with full testing support
  • Bare Metal x86: x86_64-unknown-none for bootloader and kernel development
  • RISC-V Embedded: riscv64gc-unknown-none-elf for RISC-V microcontrollers
  • ARM64 Embedded: aarch64-unknown-none-softfloat for ARM embedded systems

Testing is restricted to the Linux target due to the embedded nature of other platforms, which typically lack standard library support required for test execution.

Sources: .github/workflows/ci.yml(L10 - L12)  .github/workflows/ci.yml(L28 - L30) 

Development Infrastructure

CI/CD Pipeline Architecture

flowchart TD
subgraph subGraph3["Documentation Pipeline"]
    DOC_BUILD["cargo doc --no-deps"]
    REDIRECT["Generate index.html redirect"]
    DEPLOY["GitHub Pages deployment"]
end
subgraph subGraph2["CI Pipeline Steps"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
    FORMAT["cargo fmt --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test(x86_64-linux only)"]
end
subgraph subGraph1["CI Job Matrix"]
    CI_JOB["ci jobMulti-target builds"]
    DOC_JOB["doc jobDocumentation"]
end
subgraph subGraph0["Trigger Events"]
    PUSH["git push"]
    PR["pull_request"]
end

BUILD --> TEST
CHECKOUT --> TOOLCHAIN
CI_JOB --> CHECKOUT
CLIPPY --> BUILD
DOC_BUILD --> REDIRECT
DOC_JOB --> DOC_BUILD
FORMAT --> CLIPPY
PR --> CI_JOB
PUSH --> CI_JOB
PUSH --> DOC_JOB
REDIRECT --> DEPLOY
TOOLCHAIN --> FORMAT

Quality Assurance Steps

The CI pipeline enforces code quality through multiple verification stages:

  1. Format Checking: cargo fmt --all -- --check ensures consistent code style
  2. Linting: cargo clippy with custom configuration excluding new_without_default warnings
  3. Multi-target Building: Verification across all supported platforms
  4. Testing: Unit tests executed only on x86_64-unknown-linux-gnu

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation Deployment

The documentation system automatically builds and deploys to GitHub Pages on pushes to the default branch. The deployment includes:

  • API Documentation: Generated via cargo doc --no-deps --all-features
  • Redirect Setup: Automatic index.html generation for seamless navigation
  • Single Commit Deployment: Clean deployment strategy to the gh-pages branch

Sources: .github/workflows/ci.yml(L44 - L55) 

Development Environment

Required Toolchain Components

flowchart TD
subgraph subGraph2["Target Installation"]
    TARGETS["rustup target addAll matrix targets"]
end
subgraph subGraph1["Required Components"]
    RUST_SRC["rust-srcFor no_std targets"]
    CLIPPY["clippyLinting"]
    RUSTFMT["rustfmtCode formatting"]
end
subgraph subGraph0["Rust Nightly Toolchain"]
    RUSTC["rustc nightly"]
    CARGO["cargo"]
end

CARGO --> CLIPPY
CARGO --> RUSTFMT
RUSTC --> RUST_SRC
RUST_SRC --> TARGETS

Development Setup Requirements

  • Rust Nightly: Required for advanced procedural macro features
  • rust-src Component: Essential for building no_std embedded targets
  • Cross-compilation Support: All target platforms must be installed via rustup

Sources: .github/workflows/ci.yml(L15 - L19) 

Project File Structure

File/DirectoryPurpose
Cargo.tomlPackage configuration and dependencies
src/lib.rsCore procedural macro implementation
tests/Integration tests for macro functionality
.github/workflows/CI/CD pipeline definitions
.gitignoreVersion control exclusions

The project follows standard Rust library conventions with emphasis on procedural macro development patterns.

Sources: .gitignore(L1 - L5) 

Getting Started

Relevant source files

This page provides a practical introduction to using the tuple_for_each crate in your Rust projects. It covers the essential steps needed to apply the TupleForEach derive macro to tuple structs and use the generated iteration functionality.

For detailed API documentation, see API Reference. For implementation details and advanced topics, see Implementation Guide.

Installation and Setup

Add tuple_for_each to your Cargo.toml dependencies:

[dependencies]
tuple_for_each = "0.1"

Import the derive macro in your Rust code:

use tuple_for_each::TupleForEach;

The crate requires no additional setup or configuration. All functionality is generated at compile time through the procedural macro system.

Sources: README.md(L3 - L4)  tests/test_tuple_for_each.rs(L1) 

Quick Start Workflow

Workflow: From Struct Definition to Iteration

flowchart TD
DEFINE["Define Tuple Struct"]
DERIVE["Add #[derive(TupleForEach)]"]
INSTANTIATE["Create Instance"]
METHODS["Use len() and is_empty()"]
FOR_EACH["Use *_for_each! macro"]
ENUMERATE["Use *_enumerate! macro"]
GENERATE["Generates: macros and methods"]

DEFINE --> DERIVE
DERIVE --> GENERATE
DERIVE --> INSTANTIATE
GENERATE --> ENUMERATE
GENERATE --> FOR_EACH
GENERATE --> METHODS
INSTANTIATE --> ENUMERATE
INSTANTIATE --> FOR_EACH
INSTANTIATE --> METHODS

Step 1: Define and Annotate Tuple Struct

Apply the #[derive(TupleForEach)] attribute to any tuple struct:

#[derive(TupleForEach)]
struct FooBar(u32, &'static str, bool);

Step 2: Create Instance and Access Metadata

let tup = FooBar(23, "hello", true);
assert_eq!(tup.len(), 3);
assert!(!tup.is_empty());

Step 3: Iterate Over Fields

Use the generated *_for_each! macro for field iteration:

foo_bar_for_each!(x in tup {
    println!("{}", x);
});

Use the generated *_enumerate! macro for indexed iteration:

foo_bar_enumerate!((i, x) in tup {
    println!("{}: {}", i, x);
});

Sources: README.md(L18 - L39)  tests/test_tuple_for_each.rs(L44 - L48) 

Generated Code Entities

Code Generation Mapping

flowchart TD
subgraph subGraph1["Generated Macros"]
    FOR_EACH["foo_bar_for_each!(x in tup { ... })"]
    ENUMERATE["foo_bar_enumerate!((i, x) in tup { ... })"]
end
subgraph subGraph0["Generated Methods"]
    LEN["len() -> usize"]
    EMPTY["is_empty() -> bool"]
end
STRUCT["Tuple Struct: FooBar(u32, &str, bool)"]
VARIANTS["Supports: immutable and mutable variants"]

ENUMERATE --> VARIANTS
FOR_EACH --> VARIANTS
STRUCT --> EMPTY
STRUCT --> ENUMERATE
STRUCT --> FOR_EACH
STRUCT --> LEN

The derive macro generates the following code entities for each annotated struct:

Generated EntityPurposeExample Usage
len()methodReturns field counttup.len()
is_empty()methodChecks if tuple has zero fieldstup.is_empty()
*_for_each!macroIterates over fieldsfoo_bar_for_each!(x in tup { ... })
*_enumerate!macroIterates with field indexfoo_bar_enumerate!((i, x) in tup { ... })

Naming Convention: Macro names are derived by converting the struct name from PascalCase to snake_case. FooBar becomes foo_bar_for_each! and foo_bar_enumerate!.

Sources: README.md(L9 - L16)  tests/test_tuple_for_each.rs(L52 - L61) 

Mutable and Immutable Variants

Both generated macros support mutable and immutable field access:

Immutable Access:

pair_for_each!(x in t {
    x.bar();  // Call immutable methods
});

Mutable Access:

tuple_for_each!(x in mut t {
    x.bar_mut();  // Call mutable methods
});

The same pattern applies to the enumerate macro:

pair_enumerate!((i, x) in mut t {
    x.bar_mut();
});

Sources: tests/test_tuple_for_each.rs(L65 - L76)  tests/test_tuple_for_each.rs(L94 - L106) 

Common Usage Patterns

Field Type Uniformity: The macros work with heterogeneous tuple fields. Each field can be a different type, as demonstrated in the test patterns where A, B, and C are distinct types implementing a common trait.

Trait-Based Operations: A common pattern is to define a trait that all tuple field types implement, then call trait methods within the iteration macros:

#![allow(unused)]
fn main() {
trait Base {
    fn foo(&self) -> Self::Item;
    fn bar(&self);
}
}

Index Tracking: The enumerate macro provides automatic index tracking, eliminating the need for manual counter variables.

Sources: tests/test_tuple_for_each.rs(L3 - L42)  tests/test_tuple_for_each.rs(L83 - L90) 

Next Steps

Sources: README.md(L1 - L40)  tests/test_tuple_for_each.rs(L1 - L107) 

Basic Usage

Relevant source files

This document covers the fundamental usage patterns for the tuple_for_each crate, demonstrating how to apply the TupleForEach derive macro to tuple structs and use the generated iteration functionality. This page focuses on simple, straightforward examples that illustrate the core workflow from struct definition to runtime usage.

For detailed information about the generated API surface and all available methods, see Generated Functionality. For comprehensive API reference documentation, see API Reference.

Applying the Derive Macro

The TupleForEach derive macro is applied to tuple structs to automatically generate iteration utilities. The basic pattern involves importing the trait and applying the derive attribute:

use tuple_for_each::TupleForEach;

#[derive(TupleForEach)]
struct FooBar(u32, &'static str, bool);

The derive macro analyzes the tuple struct at compile time and generates several pieces of functionality based on the struct's name and field structure.

Basic Derive Workflow

flowchart TD
A["User defines tuple struct"]
B["#[derive(TupleForEach)]"]
C["Compile-time processing"]
D["Generated macros"]
E["Generated methods"]
F["foo_bar_for_each!"]
G["foo_bar_enumerate!"]
H["len()"]
I["is_empty()"]
J["Runtime iteration"]
K["Runtime metadata"]

A --> B
B --> C
C --> D
C --> E
D --> F
D --> G
E --> H
E --> I
F --> J
G --> J
H --> K
I --> K

Sources: README.md(L20 - L28)  tests/test_tuple_for_each.rs(L44 - L48) 

Generated Methods

The derive macro automatically implements two utility methods on the tuple struct:

MethodReturn TypePurpose
len()usizeReturns the number of fields in the tuple
is_empty()boolReturnstrueif the tuple has no fields

These methods provide metadata about the tuple structure:

let tup = FooBar(23, "hello", true);
assert_eq!(tup.len(), 3);
assert!(!tup.is_empty());

Sources: README.md(L9 - L11)  README.md(L26 - L28)  tests/test_tuple_for_each.rs(L53)  tests/test_tuple_for_each.rs(L67) 

Generated For-Each Macros

The derive macro generates iteration macros that follow a naming convention based on the struct name converted from PascalCase to snake_case. For a struct named FooBar, the generated macro is foo_bar_for_each!.

For-Each Macro Usage

flowchart TD
A["FooBar struct"]
B["foo_bar_for_each! macro"]
C["Iterates over each field"]
D["Executes block for each field"]
E["Pair struct"]
F["pair_for_each! macro"]
G["Iterates over each field"]
H["Executes block for each field"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H

The macro syntax uses the pattern <struct_name>_for_each!(variable in tuple { block }):

// Immutable iteration
foo_bar_for_each!(x in tup {
    println!("{}", x);
});

// Mutable iteration
foo_bar_for_each!(x in mut tup {
    // Can modify x here
});

Sources: README.md(L30 - L33)  tests/test_tuple_for_each.rs(L56 - L61)  tests/test_tuple_for_each.rs(L70 - L75) 

Generated Enumerate Macros

The enumerate macros provide indexed iteration, giving access to both the field index and the field value. The macro follows the pattern <struct_name>_enumerate!((index, variable) in tuple { block }):

foo_bar_enumerate!((i, x) in tup {
    println!("{}: {}", i, x);
});

The index starts at 0 and increments for each field in the tuple. Like the for-each macros, enumerate macros support both immutable and mutable access patterns.

Runtime Iteration Patterns

flowchart TD
A["tuple_for_each!(x in tup { ... })"]
B["Immutable field access"]
C["tuple_for_each!(x in mut tup { ... })"]
D["Mutable field access"]
E["tuple_enumerate!((i, x) in tup { ... })"]
F["Indexed immutable access"]
G["tuple_enumerate!((i, x) in mut tup { ... })"]
H["Indexed mutable access"]
I["Read-only operations"]
J["Modify field values"]
K["Index-aware read operations"]
L["Index-aware modify operations"]

A --> B
B --> I
C --> D
D --> J
E --> F
F --> K
G --> H
H --> L

Sources: README.md(L35 - L38)  tests/test_tuple_for_each.rs(L84 - L90)  tests/test_tuple_for_each.rs(L99 - L105) 

Mutable Access Patterns

Both for-each and enumerate macros support mutable access by adding the mut keyword before the tuple variable. This allows the iteration block to modify the fields during iteration:

let mut t = Tuple(A, B, C);

// Mutable for-each
tuple_for_each!(x in mut t {
    x.bar_mut(); // Can call mutable methods
});

// Mutable enumerate
tuple_enumerate!((i, x) in mut t {
    x.bar_mut(); // Can call mutable methods with index
});

The mutable access pattern is particularly useful when the tuple fields implement traits that require mutable methods or when you need to modify the state of the contained values during iteration.

Sources: tests/test_tuple_for_each.rs(L65 - L76)  tests/test_tuple_for_each.rs(L94 - L106) 

Generated Functionality

Relevant source files

This page details the specific functionality generated when applying the #[derive(TupleForEach)] attribute to a tuple struct. It covers the generated methods, macros, and their syntax variants.

For basic usage examples, see Basic Usage. For implementation details of how this code is generated, see Code Generation Pipeline.

Overview

When the TupleForEach derive macro is applied to a tuple struct, it generates several pieces of functionality:

Generated ItemPurposeVariants
len()methodReturns the number of fields in the tupleConst method
is_empty()methodReturns true if tuple has zero fieldsConst method
_for_each!macroIterates over tuple fieldsImmutable, Mutable
_enumerate!macroIterates over fields with indicesImmutable, Mutable

Generated Functionality Mapping

flowchart TD
Input["TupleStruct(A, B, C)"]
Derive["#[derive(TupleForEach)]"]
Methods["impl TupleStruct"]
ForEach["tuple_struct_for_each!"]
Enumerate["tuple_struct_enumerate!"]
Len["len() -> usize"]
Empty["is_empty() -> bool"]
ForEachImm["field in tuple { ... }"]
ForEachMut["field in mut tuple { ... }"]
EnumImm["(i, field) in tuple { ... }"]
EnumMut["(i, field) in mut tuple { ... }"]

Derive --> Enumerate
Derive --> ForEach
Derive --> Methods
Enumerate --> EnumImm
Enumerate --> EnumMut
ForEach --> ForEachImm
ForEach --> ForEachMut
Input --> Derive
Methods --> Empty
Methods --> Len

Sources: src/lib.rs(L58 - L122) 

Generated Methods

The derive macro adds two constant methods to the tuple struct through an impl block:

len()Method

Returns the number of fields in the tuple as a compile-time constant.

pub const fn len(&self) -> usize

The implementation directly returns the field count determined at compile time.

is_empty()Method

Returns true if the tuple has zero fields, false otherwise.

pub const fn is_empty(&self) -> bool

The implementation compares self.len() == 0.

Sources: src/lib.rs(L88 - L98) 

Generated Macros

Two primary macros are generated, each with immutable and mutable variants.

Macro Naming Convention

The macro names are derived from the struct name using pascal_to_snake conversion:

Struct NameMacro Prefix
FooBarfoo_bar
MyTuplemy_tuple
HTTPResponseh_t_t_p_response

Naming Conversion Process

flowchart TD
Pascal["PascalCase"]
Snake["snake_case"]
Insert["Insert '_' before uppercase"]
Lower["Convert to lowercase"]
Example1["FooBar"]
Example1Out["foo_bar"]
Example2["MyTuple"]
Example2Out["my_tuple"]

Example1 --> Example1Out
Example2 --> Example2Out
Insert --> Lower
Lower --> Snake
Pascal --> Insert
Pascal --> Snake

Sources: src/lib.rs(L124 - L133)  src/lib.rs(L60) 

_for_each!Macro

Iterates over each field in the tuple, binding each field to a variable.

Syntax Variants:

  • Immutable: macro_name_for_each!(item in tuple { code })
  • Mutable: macro_name_for_each!(item in mut tuple { code })

Generated Code Structure:

flowchart TD
ForEach["_for_each! macro"]
ImmVariant["Immutable Variant"]
MutVariant["Mutable Variant"]
ImmGen["{ let $item = &$tuple.0; $code }{ let $item = &$tuple.1; $code }..."]
MutGen["{ let $item = &mut $tuple.0; $code }{ let $item = &mut $tuple.1; $code }..."]

ForEach --> ImmVariant
ForEach --> MutVariant
ImmVariant --> ImmGen
MutVariant --> MutGen

Sources: src/lib.rs(L100 - L109)  src/lib.rs(L71 - L72) 

_enumerate!Macro

Iterates over each field with its index, binding both the index and field to variables.

Syntax Variants:

  • Immutable: macro_name_enumerate!((idx, item) in tuple { code })
  • Mutable: macro_name_enumerate!((idx, item) in mut tuple { code })

The generated code provides both the field index and field reference for each iteration.

Sources: src/lib.rs(L111 - L120)  src/lib.rs(L73 - L82) 

Macro Implementation Details

Each macro variant generates a sequence of code blocks, one for each tuple field:

Field Access Pattern

flowchart TD
Tuple["Tuple(field0, field1, field2)"]
Gen["Code Generation"]
Block0["{ let item = &tuple.0; code }"]
Block1["{ let item = &tuple.1; code }"]
Block2["{ let item = &tuple.2; code }"]
Exec0["Execute user code with field0"]
Exec1["Execute user code with field1"]
Exec2["Execute user code with field2"]

Block0 --> Exec0
Block1 --> Exec1
Block2 --> Exec2
Gen --> Block0
Gen --> Block1
Gen --> Block2
Tuple --> Gen

The mutable variants follow the same pattern but use &mut instead of & for field references.

For enumerate macros, an additional index binding is generated: let $idx = #idx; where #idx is the literal field index (0, 1, 2, etc.).

Sources: src/lib.rs(L69 - L83) 

Documentation Generation

The generated macros include comprehensive documentation with usage examples. The documentation is generated dynamically based on the struct name and macro type using the gen_doc function.

Each generated macro receives structured documentation that includes:

  • Purpose description
  • Link back to the derive macro
  • Code examples with proper syntax

Sources: src/lib.rs(L26 - L56)  src/lib.rs(L85 - L86) 

Implementation Guide

Relevant source files

This document provides a detailed technical guide to the internal implementation of the tuple_for_each derive macro. It covers the macro processing pipeline, code generation strategies, and the architectural decisions behind the crate's design.

This guide focuses on how the derive macro transforms user code at compile time. For basic usage examples, see Getting Started. For API reference documentation, see API Reference.

Macro Processing Architecture

The tuple_for_each derive macro follows a standard procedural macro architecture with clear separation between parsing, validation, and code generation phases.

Processing Pipeline

flowchart TD
Input["TokenStream Input"]
Entry["tuple_for_each()"]
Parse["syn::parse_macro_input!()"]
Validate["Validation Logic"]
Valid["Valid Tuple Struct?"]
Error["Error::new_spanned()"]
Generate["impl_for_each()"]
NameConv["pascal_to_snake()"]
FieldIter["Field Iteration Logic"]
DocGen["gen_doc()"]
CodeGen["quote! Macro Generation"]
MacroNames["Macro Name Generation"]
ForEachGen["for_each Variants"]
EnumerateGen["enumerate Variants"]
Documentation["Inline Documentation"]
Methods["len() & is_empty() Methods"]
Macros["Generated macro_rules!"]
Output["proc_macro2::TokenStream"]
ErrorOutput["Compile Error"]

CodeGen --> Macros
CodeGen --> Methods
DocGen --> Documentation
Documentation --> CodeGen
Entry --> Parse
EnumerateGen --> CodeGen
Error --> ErrorOutput
FieldIter --> EnumerateGen
FieldIter --> ForEachGen
ForEachGen --> CodeGen
Generate --> CodeGen
Generate --> DocGen
Generate --> FieldIter
Generate --> NameConv
Input --> Entry
MacroNames --> CodeGen
Macros --> Output
Methods --> Output
NameConv --> MacroNames
Parse --> Validate
Valid --> Error
Valid --> Generate
Validate --> Valid

The pipeline starts with the tuple_for_each function as the entry point and flows through validation, name conversion, and code generation phases before producing the final token stream.

Sources: src/lib.rs(L10 - L24)  src/lib.rs(L58 - L122) 

Entry Point and Validation

The derive macro entry point performs essential validation to ensure the macro is only applied to tuple structs:

flowchart TD
TokenStream["TokenStream"]
ParseInput["syn::parse_macro_input!"]
AST["DeriveInput AST"]
CheckData["Check ast.data"]
IsStruct["Data::Struct?"]
CompileError["Compile Error"]
CheckFields["Check strct.fields"]
IsUnnamed["Fields::Unnamed?"]
CallImpl["impl_for_each()"]
ErrorToken["Error TokenStream"]
GeneratedCode["Generated TokenStream"]

AST --> CheckData
CallImpl --> GeneratedCode
CheckData --> IsStruct
CheckFields --> IsUnnamed
CompileError --> ErrorToken
IsStruct --> CheckFields
IsStruct --> CompileError
IsUnnamed --> CallImpl
IsUnnamed --> CompileError
ParseInput --> AST
TokenStream --> ParseInput

The validation logic specifically checks for Data::Struct with Fields::Unnamed, ensuring the derive macro only works on tuple structs like struct MyTuple(A, B, C).

Sources: src/lib.rs(L11 - L24) 

Code Generation Pipeline

The core code generation happens in the impl_for_each function, which orchestrates the creation of all generated functionality.

Generation Strategy

flowchart TD
subgraph subGraph4["Final Assembly"]
    ImplBlock["impl block"]
    MacroRules["macro_rules! definitions"]
    QuoteOutput["quote! expansion"]
end
subgraph Documentation["Documentation"]
    DocGeneration["gen_doc()"]
    ForEachDoc["macro_for_each_doc"]
    EnumerateDoc["macro_enumerate_doc"]
end
subgraph subGraph2["Code Vectors"]
    ForEachVec["for_each Vec"]
    ForEachMutVec["for_each_mut Vec"]
    EnumerateVec["enumerate Vec"]
    EnumerateMutVec["enumerate_mut Vec"]
end
subgraph subGraph1["Name Generation"]
    PascalToSnake["pascal_to_snake()"]
    MacroPrefix["macro_name"]
    ForEachMacro["macro_for_each"]
    EnumerateMacro["macro_enumerate"]
end
subgraph subGraph0["Input Processing"]
    ASTInput["DeriveInput & DataStruct"]
    StructName["ast.ident"]
    FieldCount["strct.fields.len()"]
end

ASTInput --> FieldCount
ASTInput --> StructName
DocGeneration --> EnumerateDoc
DocGeneration --> ForEachDoc
EnumerateDoc --> MacroRules
EnumerateMutVec --> MacroRules
EnumerateVec --> MacroRules
FieldCount --> EnumerateMutVec
FieldCount --> EnumerateVec
FieldCount --> ForEachMutVec
FieldCount --> ForEachVec
ForEachDoc --> MacroRules
ForEachMutVec --> ImplBlock
ForEachVec --> ImplBlock
ImplBlock --> QuoteOutput
MacroPrefix --> DocGeneration
MacroPrefix --> EnumerateMacro
MacroPrefix --> ForEachMacro
MacroRules --> QuoteOutput
PascalToSnake --> MacroPrefix
StructName --> DocGeneration
StructName --> PascalToSnake

The generation process creates separate code vectors for each variant (immutable/mutable × for_each/enumerate) and assembles them into the final token stream.

Sources: src/lib.rs(L58 - L122) 

Field Iteration Logic

The macro generates field access code by iterating over field indices and creating appropriate access patterns:

VariantAccess PatternGenerated Code Template
for_each&$tuple.#idx{ let $item = &$tuple.#idx; $code }
for_each_mut&mut $tuple.#idx{ let $item = &mut $tuple.#idx; $code }
enumerate&$tuple.#idxwith index{ let $idx = #idx; let $item = &$tuple.#idx; $code }
enumerate_mut&mut $tuple.#idxwith index{ let $idx = #idx; let $item = &mut $tuple.#idx; $code }

The field iteration uses syn::Index to generate numeric field accessors for tuple struct fields.

Sources: src/lib.rs(L64 - L83) 

Generated Code Structure

The macro produces a comprehensive set of functionality for each tuple struct, including both methods and macros.

Output Components

flowchart TD
subgraph Documentation["Documentation"]
    MacroDoc["Generated macro documentation"]
    Examples["Usage examples"]
end
subgraph subGraph2["Macro Variants"]
    ForEachImmut["($item:ident in $tuple:ident $code:block)"]
    ForEachMut["($item:ident in mut $tuple:ident $code:block)"]
    EnumImmut["(($idx:ident, $item:ident) in $tuple:ident $code:block)"]
    EnumMut["(($idx:ident, $item:ident) in mut $tuple:ident $code:block)"]
end
subgraph subGraph1["Generated Macros"]
    ImplBlock["impl StructName"]
    LenMethod["len() method"]
    IsEmptyMethod["is_empty() method"]
    ForEachMacro["struct_name_for_each!"]
    EnumerateMacro["struct_name_enumerate!"]
end
subgraph subGraph0["Generated Implementation"]
    ImplBlock["impl StructName"]
    LenMethod["len() method"]
    IsEmptyMethod["is_empty() method"]
end

EnumerateMacro --> EnumImmut
EnumerateMacro --> EnumMut
EnumerateMacro --> MacroDoc
ForEachMacro --> ForEachImmut
ForEachMacro --> ForEachMut
ForEachMacro --> MacroDoc
ImplBlock --> IsEmptyMethod
ImplBlock --> LenMethod
MacroDoc --> Examples

Each component serves a specific purpose in providing iteration capabilities for tuple structs.

Sources: src/lib.rs(L87 - L121) 

Method Generation

The implementation block includes utility methods that provide metadata about the tuple:

MethodReturn TypeImplementationPurpose
len()usize#field_num(const)Returns field count
is_empty()boolself.len() == 0Checks if tuple has fields

Both methods are const fn, allowing compile-time evaluation when possible.

Sources: src/lib.rs(L88 - L98) 

Helper Utilities

The implementation includes several utility functions that support the code generation process.

Name Conversion

The pascal_to_snake function converts Pascal case struct names to snake case for macro naming:

flowchart TD
Input["PascalCase String"]
Iterator["Character Iterator"]
Check["Check Character"]
IsUpper["is_ascii_uppercase()?"]
AddUnderscore["Add '_' if not empty"]
AddChar["Add character"]
ToLower["to_ascii_lowercase()"]
Result["snake_case String"]

AddChar --> ToLower
AddUnderscore --> ToLower
Check --> IsUpper
Input --> Iterator
IsUpper --> AddChar
IsUpper --> AddUnderscore
Iterator --> Check
ToLower --> Result

This conversion ensures that MyTupleStruct becomes my_tuple_struct_for_each! for the generated macro names.

Sources: src/lib.rs(L124 - L133) 

Documentation Generation

The gen_doc function creates inline documentation for generated macros:

Documentation TypeTemplateGenerated Content
"for_each"Field iterationUsage examples and description for*_for_each!
"enumerate"Indexed iterationUsage examples and description for*_enumerate!

The documentation includes proper cross-references to the derive macro and usage examples in ignore blocks.

Sources: src/lib.rs(L26 - L56) 

Token Stream Assembly

The final assembly uses the quote! macro to combine all generated components into a single proc_macro2::TokenStream. The macro leverages token interpolation to insert:

  • Field counts as literal numbers
  • Generated code vectors using #(#vector)* expansion
  • Dynamically created identifiers via format_ident!
  • Documentation strings through #doc attributes

Sources: src/lib.rs(L87 - L122) 

Derive Macro Processing

Relevant source files

This document explains the entry point processing for the TupleForEach derive macro, covering input parsing, validation, and error handling. This covers the initial phase where user-provided tuple struct definitions are parsed and validated before code generation begins. For information about the actual code generation logic, see Code Generation Pipeline.

Entry Point Function

The derive macro processing begins with the tuple_for_each() function, which serves as the procedural macro entry point. This function is marked with the #[proc_macro_derive(TupleForEach)] attribute and handles the initial processing of user code.

flowchart TD
UserCode["User Code: #[derive(TupleForEach)] struct MyTuple(A, B);"]
EntryPoint["tuple_for_each()"]
ParseInput["syn::parse_macro_input!()"]
Validation["Tuple Struct Validation"]
Success["impl_for_each()"]
Error["Compile Error"]

EntryPoint --> ParseInput
ParseInput --> Validation
UserCode --> EntryPoint
Validation --> Error
Validation --> Success

Entry Point Processing Flow

The tuple_for_each() function receives a TokenStream containing the user's struct definition and immediately delegates parsing to the syn crate. The function signature and initial parsing occur at src/lib.rs(L11 - L12) 

Sources: src/lib.rs(L10 - L24) 

Input Parsing with syn

The macro uses the syn crate to parse the incoming TokenStream into a structured Abstract Syntax Tree (AST). This parsing transforms the raw token stream into typed Rust syntax structures that can be programmatically analyzed.

flowchart TD
subgraph subGraph1["Validation Targets"]
    StructData["Data::Struct"]
    UnnamedFields["Fields::Unnamed"]
end
subgraph subGraph0["syn Parsing Components"]
    TokenStream["proc_macro::TokenStream"]
    MacroInput["syn::parse_macro_input!"]
    DeriveInput["syn::DeriveInput"]
    Data["syn::Data"]
    DataStruct["syn::DataStruct"]
    Fields["syn::Fields"]
end

Data --> DataStruct
Data --> StructData
DataStruct --> Fields
DeriveInput --> Data
Fields --> UnnamedFields
MacroInput --> DeriveInput
TokenStream --> MacroInput

syn AST Structure and Validation Points

The parsing process extracts key structural information:

ComponentTypePurposeValidation
astDeriveInputRoot AST node containing struct metadataContains struct name and data
ast.dataDataDiscriminates between struct, enum, unionMust beData::Struct
strctDataStructStruct-specific dataExtracted fromData::Struct
strct.fieldsFieldsField informationMust beFields::Unnamedfor tuple structs

The parsing occurs with explicit type annotation at src/lib.rs(L12)  ensuring the input conforms to the expected DeriveInput structure.

Sources: src/lib.rs(L11 - L14) 

Tuple Struct Validation

The validation logic implements a two-stage check to ensure the derive macro is only applied to valid tuple structs. This prevents compilation errors and provides clear error messages for invalid usage.

flowchart TD
AST["DeriveInput AST"]
CheckData["Check ast.data"]
IsStruct["Data::Struct?"]
CheckFields["Check strct.fields"]
IsUnnamed["Fields::Unnamed?"]
ValidTuple["Valid Tuple Struct"]
StructError["Not a struct"]
FieldError["Not a tuple struct"]
CompileError["Compile Error"]

AST --> CheckData
CheckData --> IsStruct
CheckFields --> IsUnnamed
FieldError --> CompileError
IsStruct --> CheckFields
IsStruct --> StructError
IsUnnamed --> FieldError
IsUnnamed --> ValidTuple
StructError --> CompileError

Validation Decision Tree

The validation process implements nested pattern matching:

  1. Struct Type Check: Verifies ast.data matches Data::Struct(strct) pattern at src/lib.rs(L13) 
  2. Field Type Check: Verifies strct.fields matches Fields::Unnamed(_) pattern at src/lib.rs(L14) 
  3. Success Path: If both checks pass, control flows to impl_for_each(&ast, strct) at src/lib.rs(L15) 

The validation logic excludes:

  • Named field structs (struct Foo { a: i32 })
  • Unit structs (struct Foo;)
  • Enums and unions
  • Any non-struct types

Sources: src/lib.rs(L13 - L17) 

Error Handling

When validation fails, the macro generates compile-time errors using syn::Error to provide meaningful feedback to users. The error handling system ensures that invalid usage is caught early with descriptive messages.

flowchart TD
subgraph subGraph1["Error Context"]
    AST["ast (full input)"]
    Span["Source span information"]
end
subgraph subGraph0["Error Generation"]
    ErrorNew["syn::Error::new_spanned()"]
    ErrorMsg["\attribute Unsupported markdown: codespan can only be attached to tuple structs\"]
    CompileError["to_compile_error()"]
    TokenStream["TokenStream"]
end

AST --> ErrorNew
CompileError --> TokenStream
ErrorMsg --> ErrorNew
ErrorNew --> CompileError
Span --> ErrorNew

Error Generation Process

The error handling mechanism provides:

  • Precise Source Location: Uses new_spanned(ast, ...) to attach error to the original input location at src/lib.rs(L18 - L19) 
  • Clear Error Message: Explains the constraint that only tuple structs are supported at src/lib.rs(L20) 
  • Compile-Time Failure: Converts error to token stream that causes compilation to fail at src/lib.rs(L22 - L23) 

This approach ensures users receive actionable feedback when they attempt to apply the derive macro to incompatible types.

Sources: src/lib.rs(L18 - L24) 

Integration with Code Generation

Upon successful validation, the entry point function delegates to the core code generation system. This handoff represents the transition from input processing to actual macro expansion.

sequenceDiagram
    participant UserCode as "User Code"
    participant tuple_for_each as "tuple_for_each()"
    participant synparse_macro_input as "syn::parse_macro_input"
    participant ValidationLogic as "Validation Logic"
    participant impl_for_each as "impl_for_each()"
    participant GeneratedTokenStream as "Generated TokenStream"

    UserCode ->> tuple_for_each: "
    tuple_for_each ->> synparse_macro_input: Parse TokenStream
    synparse_macro_input -->> tuple_for_each: DeriveInput AST
    tuple_for_each ->> ValidationLogic: Check struct type & fields
    ValidationLogic -->> tuple_for_each: Valid tuple struct
    tuple_for_each ->> impl_for_each: Call impl_for_each(&ast, &strct)
    impl_for_each -->> tuple_for_each: proc_macro2::TokenStream
    tuple_for_each -->> UserCode: Generated code TokenStream

Processing Handoff Sequence

The successful path through validation culminates in the call to impl_for_each(&ast, strct) at src/lib.rs(L15)  This function receives:

  • &ast: Reference to the complete DeriveInput containing struct metadata
  • strct: Reference to the DataStruct containing field information

The return value is converted from proc_macro2::TokenStream to proc_macro::TokenStream using .into() for compatibility with the procedural macro interface.

Sources: src/lib.rs(L15)  src/lib.rs(L58) 

Code Generation Pipeline

Relevant source files

This document details the core code generation logic within the tuple_for_each crate, specifically focusing on the impl_for_each function and its supporting components. This covers the transformation process from parsed AST input to generated Rust code output, including field iteration patterns, macro template creation, and documentation generation.

For information about the initial parsing and validation phase, see Derive Macro Processing. For details about the generated API surface, see Generated Functionality.

Pipeline Overview

The code generation pipeline transforms a validated tuple struct AST into executable Rust code through a series of well-defined steps. The process centers around the impl_for_each function, which orchestrates the entire generation workflow.

Code Generation Flow

flowchart TD
A["tuple_for_each()"]
B["impl_for_each()"]
C["pascal_to_snake()"]
D["Field Access Generation"]
E["Macro Template Creation"]
F["Documentation Generation"]
G["macro_name: String"]
H["for_each: Vec"]
I["for_each_mut: Vec"]
J["enumerate: Vec"]
K["enumerate_mut: Vec"]
L["macro_for_each!"]
M["macro_enumerate!"]
N["len() method"]
O["is_empty() method"]
P["macro_for_each_doc"]
Q["macro_enumerate_doc"]
R["Final TokenStream"]

A --> B
B --> C
B --> D
B --> E
B --> F
C --> G
D --> H
D --> I
D --> J
D --> K
E --> L
E --> M
E --> N
E --> O
F --> P
F --> Q
G --> L
G --> M
H --> L
I --> L
J --> M
K --> M
L --> R
M --> R
N --> R
O --> R
P --> L
Q --> M

Sources: src/lib.rs(L58 - L122) 

Name Conversion Process

The pipeline begins by converting the tuple struct's PascalCase name to snake_case for macro naming. This conversion is handled by the pascal_to_snake utility function.

Conversion Algorithm

Input (PascalCase)Output (snake_case)Generated Macro Names
MyTuplemy_tuplemy_tuple_for_each!,my_tuple_enumerate!
HTTPResponseh_t_t_p_responseh_t_t_p_response_for_each!,h_t_t_p_response_enumerate!
SimpleStructsimple_structsimple_struct_for_each!,simple_struct_enumerate!

The conversion process inserts underscores before uppercase characters (except the first character) and converts all characters to lowercase:

flowchart TD
A["tuple_name.to_string()"]
B["pascal_to_snake()"]
C["macro_name: String"]
D["format_ident!('{}_for_each', macro_name)"]
E["format_ident!('{}_enumerate', macro_name)"]
F["macro_for_each: Ident"]
G["macro_enumerate: Ident"]

A --> B
B --> C
C --> D
C --> E
D --> F
E --> G

Sources: src/lib.rs(L60 - L62)  src/lib.rs(L124 - L133) 

Field Access Code Generation

The core of the pipeline generates field access patterns for each tuple field. This process creates four distinct code patterns to handle different iteration scenarios.

Field Iteration Logic

flowchart TD
A["field_num = strct.fields.len()"]
B["for i in 0..field_num"]
C["idx = Index::from(i)"]
D["for_each.push()"]
E["for_each_mut.push()"]
F["enumerate.push()"]
G["enumerate_mut.push()"]
H["&$tuple.#idx"]
I["&mut $tuple.#idx"]
J["($idx = #idx, &$tuple.#idx)"]
K["($idx = #idx, &mut $tuple.#idx)"]

A --> B
B --> C
C --> D
C --> E
C --> F
C --> G
D --> H
E --> I
F --> J
G --> K

Each field access pattern is generated using the quote! macro to create TokenStream fragments:

Pattern TypeCode TemplateVariable Binding
for_eachlet $item = &$tuple.#idx; $codeImmutable reference
for_each_mutlet $item = &mut $tuple.#idx; $codeMutable reference
enumeratelet $idx = #idx; let $item = &$tuple.#idx; $codeIndex + immutable reference
enumerate_mutlet $idx = #idx; let $item = &mut $tuple.#idx; $codeIndex + mutable reference

Sources: src/lib.rs(L64 - L83) 

Macro Template Creation

The generated field access patterns are assembled into complete macro definitions using Rust's macro_rules! system. Each macro supports both immutable and mutable variants through pattern matching.

Macro Structure Assembly

flowchart TD
A["for_each patterns"]
B["macro_for_each definition"]
C["enumerate patterns"]
D["macro_enumerate definition"]
E["($item:ident in $tuple:ident $code:block)"]
F["($item:ident in mut $tuple:ident $code:block)"]
G["(($idx:ident, $item:ident) in $tuple:ident $code:block)"]
H["(($idx:ident, $item:ident) in mut $tuple:ident $code:block)"]
I["#(#for_each)*"]
J["#(#for_each_mut)*"]
K["#(#enumerate)*"]
L["#(#enumerate_mut)*"]

A --> B
B --> E
B --> F
C --> D
D --> G
D --> H
E --> I
F --> J
G --> K
H --> L

The macro definitions use repetition syntax (#()*) to expand the vector of field access patterns into sequential code blocks. This creates the illusion of iteration over heterogeneous tuple fields at compile time.

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

Documentation Generation

The pipeline includes automated documentation generation for the created macros. The gen_doc function creates formatted documentation strings with usage examples.

Documentation Template System

Documentation TypeTemplate FunctionGenerated Content
for_eachgen_doc("for_each", tuple_name, macro_name)Usage examples with field iteration
enumerategen_doc("enumerate", tuple_name, macro_name)Usage examples with index + field iteration

The documentation templates include:

  • Description of macro purpose
  • Reference to the derive macro that generated it
  • Code examples showing proper usage syntax
  • Placeholder substitution for tuple and macro names
flowchart TD
A["tuple_name.to_string()"]
B["gen_doc()"]
C["macro_name"]
D["macro_for_each_doc"]
E["macro_enumerate_doc"]
F["#[doc = #macro_for_each_doc]"]
G["#[doc = #macro_enumerate_doc]"]

A --> B
B --> D
B --> E
C --> B
D --> F
E --> G

Sources: src/lib.rs(L26 - L56)  src/lib.rs(L85 - L86) 

Final Assembly

The pipeline concludes by assembling all generated components into a single TokenStream using the quote! macro. This creates the complete implementation that will be inserted into the user's code.

Component Integration

flowchart TD
subgraph subGraph2["Supporting Elements"]
    E["Documentation strings"]
    F["#[macro_export] attributes"]
end
subgraph subGraph1["Generated Macros"]
    C["macro_for_each!"]
    D["macro_enumerate!"]
end
subgraph subGraph0["Generated Methods"]
    A["len() method"]
    B["is_empty() method"]
end
G["impl #tuple_name block"]
H["Final TokenStream"]

A --> G
B --> G
C --> H
D --> H
E --> H
F --> H
G --> H

The final assembly includes:

  • An impl block for the original tuple struct containing len() and is_empty() methods
  • Two exported macros with complete documentation
  • All necessary attributes for proper macro visibility

The entire generated code block is wrapped in a single quote! invocation that produces the final proc_macro2::TokenStream returned to the Rust compiler.

Sources: src/lib.rs(L87 - L122) 

Development

Relevant source files

This page provides comprehensive guidance for contributors to the tuple_for_each crate, covering development workflow, testing strategies, and quality assurance processes. It focuses on the practical aspects of building, testing, and maintaining the codebase across multiple target architectures.

For detailed information about testing patterns and integration test structure, see Testing. For CI/CD pipeline configuration and deployment processes, see CI/CD Pipeline.

Development Environment Setup

The tuple_for_each crate requires a Rust nightly toolchain with specific components for cross-platform development and quality checks.

Required Toolchain Components

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and static analysis
rustfmtCode formatting

Supported Target Architectures

The project supports multiple target architectures to ensure compatibility across embedded and systems programming environments:

flowchart TD
subgraph subGraph2["Testing Strategy"]
    FULL_TEST["Full test suite"]
    BUILD_ONLY["Build verification only"]
end
subgraph subGraph1["Target Architectures"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
end
subgraph Toolchain["Toolchain"]
    NIGHTLY["nightly toolchain"]
end

ARM --> BUILD_ONLY
NIGHTLY --> ARM
NIGHTLY --> RISCV
NIGHTLY --> X86_LINUX
NIGHTLY --> X86_NONE
RISCV --> BUILD_ONLY
X86_LINUX --> FULL_TEST
X86_NONE --> BUILD_ONLY

Target Architecture Support Matrix

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L29 - L30) 

Development Workflow

Quality Gates Pipeline

The development process enforces multiple quality gates before code integration:

flowchart TD
subgraph subGraph0["Parallel Execution"]
    TARGET1["x86_64-unknown-linux-gnu"]
    TARGET2["x86_64-unknown-none"]
    TARGET3["riscv64gc-unknown-none-elf"]
    TARGET4["aarch64-unknown-none-softfloat"]
end
COMMIT["Code Commit"]
FMT_CHECK["cargo fmt --all -- --check"]
CLIPPY_CHECK["cargo clippy --target TARGET --all-features"]
BUILD["cargo build --target TARGET --all-features"]
UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"]
SUCCESS["Integration Complete"]

BUILD --> TARGET1
BUILD --> TARGET2
BUILD --> TARGET3
BUILD --> TARGET4
BUILD --> UNIT_TEST
CLIPPY_CHECK --> BUILD
COMMIT --> FMT_CHECK
FMT_CHECK --> CLIPPY_CHECK
TARGET1 --> SUCCESS
TARGET2 --> SUCCESS
TARGET3 --> SUCCESS
TARGET4 --> SUCCESS
UNIT_TEST --> SUCCESS

Development Quality Pipeline

Sources: .github/workflows/ci.yml(L22 - L30) 

Local Development Commands

CommandPurpose
cargo fmt --all -- --checkVerify code formatting
cargo clippy --all-featuresRun linting checks
cargo build --all-featuresBuild for host target
cargo test -- --nocaptureRun integration tests

Testing Architecture

The testing strategy focuses on integration tests that verify the generated macro functionality across different usage patterns.

Test Structure Overview

flowchart TD
subgraph subGraph3["Test Module Structure"]
    TEST_FILE["tests/test_tuple_for_each.rs"]
    subgraph subGraph2["Test Functions"]
        TEST_FOR_EACH["test_for_each()"]
        TEST_FOR_EACH_MUT["test_for_each_mut()"]
        TEST_ENUMERATE["test_enumerate()"]
        TEST_ENUMERATE_MUT["test_enumerate_mut()"]
    end
    subgraph subGraph1["Test Targets"]
        PAIR_STRUCT["Pair(A, B)"]
        TUPLE_STRUCT["Tuple(A, B, C)"]
    end
    subgraph subGraph0["Test Infrastructure"]
        BASE_TRAIT["Base trait"]
        IMPL_A["A impl Base"]
        IMPL_B["B impl Base"]
        IMPL_C["C impl Base"]
    end
end

BASE_TRAIT --> IMPL_A
BASE_TRAIT --> IMPL_B
BASE_TRAIT --> IMPL_C
IMPL_A --> PAIR_STRUCT
IMPL_B --> PAIR_STRUCT
IMPL_B --> TUPLE_STRUCT
IMPL_C --> TUPLE_STRUCT
PAIR_STRUCT --> TEST_ENUMERATE_MUT
PAIR_STRUCT --> TEST_FOR_EACH
TEST_FILE --> BASE_TRAIT
TUPLE_STRUCT --> TEST_ENUMERATE
TUPLE_STRUCT --> TEST_FOR_EACH_MUT

Integration Test Architecture

Test Function Coverage

Test FunctionTarget StructGenerated MacroValidation
test_for_eachPairpair_for_each!Iteration count, method calls
test_for_each_mutTupletuple_for_each!Mutable iteration
test_enumerateTupletuple_enumerate!Index validation
test_enumerate_mutPairpair_enumerate!Mutable enumeration

Sources: tests/test_tuple_for_each.rs(L50 - L106) 

CI/CD Infrastructure

Workflow Jobs Architecture

flowchart TD
subgraph subGraph3["Documentation Job"]
    DOC_JOB["doc job"]
    BUILD_DOCS["cargo doc --no-deps --all-features"]
    DEPLOY_PAGES["Deploy to GitHub Pages"]
end
subgraph subGraph2["CI Job Matrix"]
    CI_JOB["ci job"]
    subgraph subGraph1["Matrix Strategy"]
        RUST_NIGHTLY["rust-toolchain: nightly"]
        TARGETS_MATRIX["targets: [4 architectures]"]
    end
end
subgraph subGraph0["GitHub Actions Triggers"]
    PUSH["push event"]
    PR["pull_request event"]
end

BUILD_DOCS --> DEPLOY_PAGES
CI_JOB --> RUST_NIGHTLY
CI_JOB --> TARGETS_MATRIX
DOC_JOB --> BUILD_DOCS
PR --> CI_JOB
PUSH --> CI_JOB
PUSH --> DOC_JOB

CI/CD Job Architecture

Documentation Deployment Process

The documentation pipeline includes automated deployment to GitHub Pages with strict quality requirements:

Environment VariablePurpose
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docsEnforce complete documentation
default-branchControl deployment target

Sources: .github/workflows/ci.yml(L32 - L55)  .github/workflows/ci.yml(L40) 

Multi-Target Build Strategy

Cross-Compilation Support

The crate supports embedded and systems programming environments through comprehensive cross-compilation testing:

flowchart TD
subgraph subGraph2["Build Verification"]
    FORMAT_CHECK["Format verification"]
    LINT_CHECK["Clippy linting"]
    BUILD_CHECK["Compilation check"]
    UNIT_TESTING["Unit test execution"]
end
subgraph subGraph1["Target Platforms"]
    LINUX_GNU["x86_64-unknown-linux-gnuStandard Linux"]
    BARE_METAL_X86["x86_64-unknown-noneBare metal x86"]
    RISCV_EMBEDDED["riscv64gc-unknown-none-elfRISC-V embedded"]
    ARM_EMBEDDED["aarch64-unknown-none-softfloatARM embedded"]
end
subgraph subGraph0["Host Environment"]
    UBUNTU["ubuntu-latest runner"]
    NIGHTLY_TOOLCHAIN["Rust nightly toolchain"]
end

ARM_EMBEDDED --> BUILD_CHECK
ARM_EMBEDDED --> FORMAT_CHECK
ARM_EMBEDDED --> LINT_CHECK
BARE_METAL_X86 --> BUILD_CHECK
BARE_METAL_X86 --> FORMAT_CHECK
BARE_METAL_X86 --> LINT_CHECK
LINUX_GNU --> BUILD_CHECK
LINUX_GNU --> FORMAT_CHECK
LINUX_GNU --> LINT_CHECK
LINUX_GNU --> UNIT_TESTING
NIGHTLY_TOOLCHAIN --> ARM_EMBEDDED
NIGHTLY_TOOLCHAIN --> BARE_METAL_X86
NIGHTLY_TOOLCHAIN --> LINUX_GNU
NIGHTLY_TOOLCHAIN --> RISCV_EMBEDDED
RISCV_EMBEDDED --> BUILD_CHECK
RISCV_EMBEDDED --> FORMAT_CHECK
RISCV_EMBEDDED --> LINT_CHECK
UBUNTU --> NIGHTLY_TOOLCHAIN

Multi-Target Build Matrix

Testing Limitations by Target

Only the x86_64-unknown-linux-gnu target supports full test execution due to standard library dependencies in the test environment. Embedded targets (*-none-*) undergo build verification to ensure compilation compatibility without runtime testing.

Sources: .github/workflows/ci.yml(L8 - L30) 

Testing

Relevant source files

This document covers the testing strategy and test patterns for the tuple_for_each crate. It explains how to verify that the derive macro correctly generates iteration utilities and provides guidance for testing procedural macro functionality. For information about the CI/CD pipeline and automated testing infrastructure, see CI/CD Pipeline.

Test Structure Overview

The tuple_for_each crate uses integration tests to validate macro functionality. All tests are located in tests/test_tuple_for_each.rs(L1 - L107)  and follow a pattern-based approach to verify that the TupleForEach derive macro generates correct code for different tuple struct configurations.

Test Foundation Components

The tests establish a common foundation using a trait-based approach:

ComponentPurposeLines
BasetraitProvides common interface for test typestests/test_tuple_for_each.rs3-8
Test types (A,B,C)Concrete implementations with different associated typestests/test_tuple_for_each.rs10-42
Test tuples (Pair,Tuple)Tuple structs with varying field countstests/test_tuple_for_each.rs44-48

The Base trait defines methods that return different types (u32, f32, &'static str) to verify that the generated macros handle heterogeneous tuple fields correctly.

Sources: tests/test_tuple_for_each.rs(L1 - L48) 

Integration Test Patterns

Test Function Structure

Each test function follows a consistent pattern that validates both the generated macros and utility methods:

flowchart TD
subgraph subGraph0["Generated Macro Types"]
    G["*_for_each! (immutable)"]
    H["*_for_each! (mutable)"]
    I["*_enumerate! (immutable)"]
    J["*_enumerate! (mutable)"]
end
A["Create tuple instance"]
B["Verify len() method"]
C["Initialize counter/validator"]
D["Execute generated macro"]
E["Perform operations on each field"]
F["Verify counter/index correctness"]

A --> B
B --> C
C --> D
D --> E
D --> G
D --> H
D --> I
D --> J
E --> F

Test Pattern Validation Flow

Sources: tests/test_tuple_for_each.rs(L50 - L106) 

Macro Functionality Coverage

The test suite covers four distinct macro generation scenarios:

Test FunctionMacro TestedMutabilityEnumerationTuple Type
test_for_eachpair_for_each!ImmutableNoPair(A, B)
test_for_each_muttuple_for_each!MutableNoTuple(A, B, C)
test_enumeratetuple_enumerate!ImmutableYesTuple(A, B, C)
test_enumerate_mutpair_enumerate!MutableYesPair(A, B)

Each test validates:

  • Generated method functionality: len() returns correct field count
  • Macro syntax: Proper expansion of macro rules
  • Field access: Ability to call methods on tuple fields
  • Iteration count: Verification that all fields are processed
  • Index accuracy: For enumerate variants, index values match expected sequence

Sources: tests/test_tuple_for_each.rs(L50 - L106) 

Test Execution Validation

Immutable Iteration Testing

The test_for_each function tests/test_tuple_for_each.rs(L50 - L62)  validates immutable field access:

  • Creates Pair(A, B) instance
  • Verifies len() returns 2
  • Uses pair_for_each! macro to iterate over fields
  • Calls foo() and bar() methods on each field
  • Confirms iteration count matches field count

Mutable Iteration Testing

The test_for_each_mut function tests/test_tuple_for_each.rs(L64 - L76)  validates mutable field access:

  • Creates Tuple(A, B, C) instance
  • Uses tuple_for_each! with mut keyword
  • Calls bar_mut() method requiring mutable access
  • Verifies all three fields are processed

Enumeration Testing

The enumeration tests tests/test_tuple_for_each.rs(L78 - L106)  verify index generation:

  • test_enumerate: Tests tuple_enumerate! with immutable access
  • test_enumerate_mut: Tests pair_enumerate! with mutable access
  • Both validate that indices start at 0 and increment sequentially
  • Confirm index values match expected position in tuple

Sources: tests/test_tuple_for_each.rs(L50 - L106) 

Code Generation Verification

flowchart TD
subgraph subGraph2["Test Validation Points"]
    V1["Macro syntax correctness"]
    V2["Field access patterns"]
    V3["Mutability handling"]
    V4["Index generation"]
    V5["Method implementation"]
end
subgraph subGraph1["Generated Artifacts"]
    PFE["pair_for_each! macro"]
    PE["pair_enumerate! macro"]
    TFE["tuple_for_each! macro"]
    TE["tuple_enumerate! macro"]
    LEN["len() method"]
    EMPTY["is_empty() method"]
end
subgraph subGraph0["Source Code Input"]
    DS["#[derive(TupleForEach)]struct Pair(A, B)"]
    DT["#[derive(TupleForEach)]struct Tuple(A, B, C)"]
end

DS --> EMPTY
DS --> LEN
DS --> PE
DS --> PFE
DT --> EMPTY
DT --> LEN
DT --> TE
DT --> TFE
LEN --> V5
PE --> V4
PFE --> V1
PFE --> V2
TE --> V4
TFE --> V3

Generated Code Validation Matrix

Sources: tests/test_tuple_for_each.rs(L44 - L48)  tests/test_tuple_for_each.rs(L50 - L106) 

Running Tests

Tests are executed using standard Cargo commands:

cargo test                    # Run all tests
cargo test test_for_each      # Run specific test function
cargo test --verbose          # Run with detailed output

The tests validate macro functionality at compile time (macro expansion correctness) and runtime (iteration behavior). Since this is a procedural macro crate, successful compilation indicates that the macro generates syntactically correct Rust code, while test execution verifies semantic correctness.

Sources: tests/test_tuple_for_each.rs(L1 - L107) 

CI/CD Pipeline

Relevant source files

This document covers the continuous integration and continuous deployment (CI/CD) infrastructure for the tuple_for_each crate. The pipeline is implemented using GitHub Actions and provides automated quality assurance, multi-target compilation, and documentation deployment.

The CI/CD system ensures code quality across multiple target architectures and automatically publishes documentation. For information about local development testing, see Testing.

Pipeline Overview

The CI/CD pipeline consists of two primary jobs defined in the GitHub Actions workflow: the ci job for quality assurance and multi-target builds, and the doc job for documentation generation and deployment.

CI/CD Architecture

flowchart TD
Trigger["GitHub Event Triggerpush | pull_request"]
Pipeline["GitHub Actions Workflow.github/workflows/ci.yml"]
CIJob["ci JobQuality Assurance"]
DocJob["doc JobDocumentation"]
Matrix["Matrix Strategyrust-toolchain: nightlytargets: 4 platforms"]
Target1["x86_64-unknown-linux-gnuFull Testing"]
Target2["x86_64-unknown-noneBuild Only"]
Target3["riscv64gc-unknown-none-elfBuild Only"]
Target4["aarch64-unknown-none-softfloatBuild Only"]
DocBuild["cargo doc --no-deps --all-features"]
Deploy["GitHub Pages Deploymentgh-pages branch"]
QualityGates["Quality GatesFormat | Clippy | Build | Test"]
BuildOnly["Build GateFormat | Clippy | Build"]

CIJob --> Matrix
DocJob --> Deploy
DocJob --> DocBuild
Matrix --> Target1
Matrix --> Target2
Matrix --> Target3
Matrix --> Target4
Pipeline --> CIJob
Pipeline --> DocJob
Target1 --> QualityGates
Target2 --> BuildOnly
Target3 --> BuildOnly
Target4 --> BuildOnly
Trigger --> Pipeline

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Job Workflow

The ci job implements a comprehensive quality assurance pipeline that runs across multiple target architectures using a matrix strategy.

Matrix Build Strategy

The CI job uses a fail-fast strategy disabled to ensure all target builds are attempted even if one fails:

ConfigurationValue
Runnerubuntu-latest
Rust Toolchainnightly
Target Architectures4 platforms
Fail Fastfalse

The target matrix includes both hosted and embedded platforms:

flowchart TD
Nightly["rust-toolchain: nightlywith components:rust-src, clippy, rustfmt"]
Targets["Target Matrix"]
Linux["x86_64-unknown-linux-gnuStandard Linux TargetFull Testing Enabled"]
BareMetal["x86_64-unknown-noneBare Metal x86_64Build Only"]
RISCV["riscv64gc-unknown-none-elfRISC-V 64-bitBuild Only"]
ARM["aarch64-unknown-none-softfloatARM64 Bare MetalBuild Only"]
FullPipeline["Format Check → Clippy → Build → Test"]
BuildPipeline["Format Check → Clippy → Build"]

ARM --> BuildPipeline
BareMetal --> BuildPipeline
Linux --> FullPipeline
Nightly --> Targets
RISCV --> BuildPipeline
Targets --> ARM
Targets --> BareMetal
Targets --> Linux
Targets --> RISCV

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L15 - L19) 

Quality Gates

The CI pipeline implements several sequential quality gates:

1. Code Format Verification

cargo fmt --all -- --check

Ensures all code follows consistent formatting standards using rustfmt.

2. Linting with Clippy

cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default

Performs static analysis with the new_without_default warning specifically allowed.

3. Compilation

cargo build --target ${{ matrix.targets }} --all-features

Validates successful compilation for each target architecture.

4. Unit Testing

cargo test --target ${{ matrix.targets }} -- --nocapture

Executes the test suite, but only for the x86_64-unknown-linux-gnu target due to testing infrastructure limitations on bare-metal targets.

CI Job Steps Flow

sequenceDiagram
    participant GitHubActions as "GitHub Actions"
    participant ubuntulatestRunner as "ubuntu-latest Runner"
    participant RustToolchain as "Rust Toolchain"
    participant SourceCode as "Source Code"

    GitHubActions ->> ubuntulatestRunner: "actions/checkout@v4"
    ubuntulatestRunner ->> SourceCode: Clone repository
    GitHubActions ->> ubuntulatestRunner: "dtolnay/rust-toolchain@nightly"
    ubuntulatestRunner ->> RustToolchain: Install nightly + components + targets
    ubuntulatestRunner ->> RustToolchain: "rustc --version --verbose"
    RustToolchain -->> ubuntulatestRunner: Version info
    ubuntulatestRunner ->> SourceCode: "cargo fmt --all -- --check"
    SourceCode -->> ubuntulatestRunner: Format validation result
    ubuntulatestRunner ->> SourceCode: "cargo clippy --target TARGET"
    SourceCode -->> ubuntulatestRunner: Lint analysis result
    ubuntulatestRunner ->> SourceCode: "cargo build --target TARGET"
    SourceCode -->> ubuntulatestRunner: Build result
    alt TARGET == x86_64-unknown-linux-gnu
        ubuntulatestRunner ->> SourceCode: "cargo test --target TARGET"
        SourceCode -->> ubuntulatestRunner: Test results
    end

Sources: .github/workflows/ci.yml(L13 - L30) 

Documentation Job

The doc job handles automated documentation generation and deployment to GitHub Pages.

Documentation Build Process

The documentation job runs independently of the CI matrix and focuses on generating comprehensive API documentation:

flowchart TD
DocTrigger["GitHub Event Trigger"]
DocJob["doc Jobubuntu-latest"]
Checkout["actions/checkout@v4"]
Toolchain["dtolnay/rust-toolchain@nightly"]
DocBuild["cargo doc --no-deps --all-featuresRUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"]
IndexGen["Generate redirect index.htmlcargo tree | head -1 | cut -d' ' -f1"]
Condition["github.ref == default-branchAND not continue-on-error"]
Deploy["JamesIves/github-pages-deploy-action@v4Branch: gh-pagesFolder: target/docsingle-commit: true"]
Skip["Skip deployment"]
Pages["GitHub PagesDocumentation Site"]

Checkout --> DocBuild
Condition --> Deploy
Condition --> Skip
Deploy --> Pages
DocBuild --> IndexGen
DocJob --> Checkout
DocJob --> Toolchain
DocTrigger --> DocJob
IndexGen --> Condition

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Configuration

The documentation build enforces strict standards through RUSTDOCFLAGS:

FlagPurpose
-D rustdoc::broken_intra_doc_linksTreat broken documentation links as errors
-D missing-docsRequire documentation for all public items

The build generates a redirect index page using project metadata:

printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Deployment Strategy

Documentation deployment occurs only on the default branch using the JamesIves/github-pages-deploy-action@v4 action with single-commit mode to maintain a clean gh-pages branch history.

Sources: .github/workflows/ci.yml(L36 - L55) 

Multi-Target Architecture Support

The pipeline supports diverse target architectures to ensure compatibility across different deployment environments:

Target Architecture Matrix

TargetArchitectureEnvironmentTesting Level
x86_64-unknown-linux-gnux86_64Linux with libcFull (build + test)
x86_64-unknown-nonex86_64Bare metalBuild only
riscv64gc-unknown-none-elfRISC-V 64-bitBare metal ELFBuild only
aarch64-unknown-none-softfloatARM64Bare metal soft-floatBuild only

The restricted testing on embedded targets reflects the procedural macro nature of the crate - the generated code needs to compile for embedded targets, but the macro itself only executes during compilation on the host.

flowchart TD
Source["Source CodeProcedural Macro"]
CompileTime["Compile TimeHost: x86_64-linux"]
HostTest["Host Testingx86_64-unknown-linux-gnuFull test suite"]
EmbeddedValidation["Embedded Validation"]
BareMetal["x86_64-unknown-noneBuild validation"]
RISCV["riscv64gc-unknown-none-elfBuild validation"]
ARM["aarch64-unknown-none-softfloatBuild validation"]
Runtime["RuntimeGenerated macros executeon target platforms"]

ARM --> Runtime
BareMetal --> Runtime
CompileTime --> EmbeddedValidation
CompileTime --> HostTest
EmbeddedValidation --> ARM
EmbeddedValidation --> BareMetal
EmbeddedValidation --> RISCV
HostTest --> Runtime
RISCV --> Runtime
Source --> CompileTime

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L29 - L30) 

API Reference

Relevant source files

This document provides comprehensive reference documentation for all public APIs and generated functionality provided by the tuple_for_each crate. It covers the TupleForEach derive macro and all code it generates, including macros and methods added to tuple structs.

For implementation details about how the derive macro works internally, see Implementation Guide. For basic usage examples and getting started information, see Getting Started.

API Surface Overview

The tuple_for_each crate provides a single derive macro that generates multiple APIs for tuple structs. The following diagram shows the complete API surface generated by the derive macro:

flowchart TD
subgraph subGraph2["Macro Variants"]
    ForEachImmutable["for_each!(x in tuple { ... })"]
    ForEachMutable["for_each!(x in mut tuple { ... })"]
    EnumerateImmutable["enumerate!((i, x) in tuple { ... })"]
    EnumerateMutable["enumerate!((i, x) in mut tuple { ... })"]
end
subgraph subGraph1["Generated APIs"]
    LenMethod["len() -> usize method"]
    IsEmptyMethod["is_empty() -> bool method"]
    ForEachMacro["*_for_each! macro"]
    EnumerateMacro["*_enumerate! macro"]
end
subgraph subGraph0["User Input"]
    TupleStruct["Tuple Struct with #[derive(TupleForEach)]"]
end

EnumerateMacro --> EnumerateImmutable
EnumerateMacro --> EnumerateMutable
ForEachMacro --> ForEachImmutable
ForEachMacro --> ForEachMutable
TupleStruct --> EnumerateMacro
TupleStruct --> ForEachMacro
TupleStruct --> IsEmptyMethod
TupleStruct --> LenMethod

Sources: src/lib.rs(L10 - L24)  src/lib.rs(L88 - L121) 

TupleForEach Derive Macro

The TupleForEach derive macro is the primary entry point of the crate, implemented as a procedural macro that transforms tuple struct definitions at compile time.

AttributeValue
Macro Type#[proc_macro_derive]
TargetTuple structs with unnamed fields only
Entry Pointtuple_for_each()function
Locationsrc/lib.rs10-24

Validation Rules

The derive macro validates input and only accepts tuple structs:

  • Accepted: struct MyTuple(Type1, Type2, Type3);
  • Rejected: Named struct fields, unit structs, enums
  • Error Message: "attribute 'tuple_for_each' can only be attached to tuple structs"

The validation logic checks for Data::Struct with Fields::Unnamed at src/lib.rs(L13 - L14) 

Sources: src/lib.rs(L10 - L24) 

Generated API Components

When applied to a tuple struct, the derive macro generates four distinct API components. The following diagram shows the code generation pipeline and relationships:

flowchart TD
subgraph subGraph0["Field Iteration Logic"]
    ForEachGen["*_for_each! macro generation"]
    EnumGen["*_enumerate! macro generation"]
    ForEachLoop["for i in 0..field_num"]
    EnumLoop["for i in 0..field_num"]
    FieldAccess["$tuple.#idx access"]
    IndexedAccess["#idx, $tuple.#idx access"]
end
Input["TupleStruct(Field1, Field2, ...)"]
EntryPoint["tuple_for_each()"]
Parser["syn::parse_macro_input!"]
CoreLogic["impl_for_each()"]
NameGen["pascal_to_snake()"]
MacroNames["macro_for_each, macro_enumerate"]
MethodGen["len() & is_empty() methods"]
ConstMethods["const fn len(), const fn is_empty()"]
MacroRules["macro_rules! expansion"]
Output["Generated TokenStream"]

CoreLogic --> EnumGen
CoreLogic --> ForEachGen
CoreLogic --> MethodGen
CoreLogic --> NameGen
EntryPoint --> Parser
EnumGen --> EnumLoop
EnumLoop --> IndexedAccess
FieldAccess --> MacroRules
ForEachGen --> ForEachLoop
ForEachLoop --> FieldAccess
IndexedAccess --> MacroRules
Input --> EntryPoint
MacroRules --> Output
MethodGen --> ConstMethods
NameGen --> MacroNames
Parser --> CoreLogic

Sources: src/lib.rs(L58 - L122)  src/lib.rs(L124 - L133) 

Methods Added to Tuple Structs

Two constant methods are added to every tuple struct via an impl block:

MethodSignatureDescriptionImplementation
len()pub const fn len(&self) -> usizeReturns the number of fieldssrc/lib.rs90-92
is_empty()pub const fn is_empty(&self) -> boolReturnstrueif no fieldssrc/lib.rs95-97

Both methods are const fn, allowing compile-time evaluation. The len() method returns the literal field count (#field_num), while is_empty() compares against zero.

Generated Iteration Macros

Two macro families are generated, each with immutable and mutable variants:

*_for_each! Macro

<snake_case_name>_for_each!(item in tuple { code_block })
<snake_case_name>_for_each!(item in mut tuple { code_block })
  • Pattern: $item:ident in $tuple:ident $code:block
  • Mutable Pattern: $item:ident in mut $tuple:ident $code:block
  • Implementation: src/lib.rs(L102 - L109) 

*_enumerate! Macro

<snake_case_name>_enumerate!((index, item) in tuple { code_block })
<snake_case_name>_enumerate!((index, item) in mut tuple { code_block })
  • Pattern: ($idx:ident, $item:ident) in $tuple:ident $code:block
  • Mutable Pattern: ($idx:ident, $item:ident) in mut $tuple:ident $code:block
  • Implementation: src/lib.rs(L113 - L120) 

Sources: src/lib.rs(L100 - L121) 

Name Generation Strategy

The macro names are derived from the tuple struct name using a pascal_to_snake conversion:

flowchart TD
PascalCase["MyTupleStruct"]
Conversion["pascal_to_snake()"]
SnakeCase["my_tuple_struct"]
ForEachName["my_tuple_struct_for_each!"]
EnumName["my_tuple_struct_enumerate!"]

Conversion --> SnakeCase
PascalCase --> Conversion
SnakeCase --> EnumName
SnakeCase --> ForEachName

The conversion algorithm at src/lib.rs(L124 - L133)  processes each character, inserting underscores before uppercase letters (except the first) and converting to lowercase.

Sources: src/lib.rs(L60 - L62)  src/lib.rs(L124 - L133) 

Documentation Generation

The generated macros include automatically generated documentation using the gen_doc() function at src/lib.rs(L26 - L56)  Documentation templates provide usage examples and link back to the derive macro.

Documentation Template Variables

VariablePurposeExample
tuple_nameOriginal struct name"MyTuple"
macro_nameSnake case name"my_tuple"
kindMacro type"for_each"or"enumerate"

Sources: src/lib.rs(L26 - L56)  src/lib.rs(L85 - L86) 

Complete API Reference

For detailed information about specific components:

Each subsection provides comprehensive documentation, usage patterns, and implementation details for the respective API components.

Sources: src/lib.rs(L1 - L134)  README.md(L1 - L40) 

TupleForEach Derive Macro

Relevant source files

This document provides comprehensive reference documentation for the TupleForEach derive macro, which is the core procedural macro that generates iteration utilities for tuple structs. For information about the generated macros themselves, see Generated Macros. For information about the generated methods, see Generated Methods.

Purpose and Scope

The TupleForEach derive macro is a procedural macro that automatically generates iteration utilities for tuple structs at compile time. When applied to a tuple struct, it creates field iteration macros and utility methods that enable ergonomic access to tuple fields without manual indexing.

Macro Declaration

The derive macro is declared as a procedural macro using the #[proc_macro_derive] attribute:

#![allow(unused)]
fn main() {
#[proc_macro_derive(TupleForEach)]
pub fn tuple_for_each(item: TokenStream) -> TokenStream
}

Entry Point Flow

flowchart TD
Input["TokenStream input"]
Parse["syn::parse_macro_input!(item as DeriveInput)"]
Check["ast.data matches Data::Struct?"]
Error1["Compile Error"]
Fields["strct.fields matches Fields::Unnamed?"]
Error2["Compile Error: 'can only be attached to tuple structs'"]
Generate["impl_for_each(&ast, strct)"]
Output["Generated TokenStream"]
CompileError["Error::new_spanned().to_compile_error()"]

Check --> Error1
Check --> Fields
Error1 --> CompileError
Error2 --> CompileError
Fields --> Error2
Fields --> Generate
Generate --> Output
Input --> Parse
Parse --> Check

Sources: src/lib.rs(L11 - L24) 

Requirements and Constraints

Valid Input Types

The derive macro can only be applied to tuple structs - structs with unnamed fields. The validation logic enforces two conditions:

ConditionRequirementError if Failed
Struct TypeMust beData::StructvariantCompile error with span
Field TypeMust haveFields::Unnamed"attributetuple_for_eachcan only be attached to tuple structs"

Supported Tuple Struct Examples

// ✅ Valid - basic tuple struct
#[derive(TupleForEach)]
struct Point(i32, i32);

// ✅ Valid - mixed types
#[derive(TupleForEach)]
struct Mixed(String, i32, bool);

// ✅ Valid - generic tuple struct
#[derive(TupleForEach)]
struct Generic<T, U>(T, U);

// ❌ Invalid - named struct
#[derive(TupleForEach)]
struct Named { x: i32, y: i32 }

// ❌ Invalid - unit struct
#[derive(TupleForEach)]
struct Unit;

Validation Logic Flow

flowchart TD
ast["DeriveInput AST"]
data_check["ast.data == Data::Struct?"]
error_span["Error::new_spanned(ast, message)"]
strct["Extract DataStruct"]
fields_check["strct.fields == Fields::Unnamed?"]
error_msg["'can only be attached to tuple structs'"]
impl_call["impl_for_each(&ast, strct)"]
compile_error["to_compile_error().into()"]
success["Generated Code TokenStream"]

ast --> data_check
data_check --> error_span
data_check --> strct
error_msg --> compile_error
error_span --> compile_error
fields_check --> error_msg
fields_check --> impl_call
impl_call --> success
strct --> fields_check

Sources: src/lib.rs(L13 - L23) 

Code Generation Process

Name Resolution

The macro converts tuple struct names from PascalCase to snake_case using the pascal_to_snake function to create macro names:

Tuple Struct NameGenerated Macro PrefixFor Each MacroEnumerate Macro
Pointpointpoint_for_each!point_enumerate!
MyTuplemy_tuplemy_tuple_for_each!my_tuple_enumerate!
HttpResponsehttp_responsehttp_response_for_each!http_response_enumerate!

Generated Code Structure

For each tuple struct, the macro generates:

  1. Utility Methods: len() and is_empty() implementations
  2. For Each Macro: Field iteration with immutable and mutable variants
  3. Enumerate Macro: Indexed field iteration with immutable and mutable variants

Code Generation Pipeline

flowchart TD
ast_input["DeriveInput & DataStruct"]
name_conv["pascal_to_snake(tuple_name)"]
macro_names["format_ident! for macro names"]
field_count["strct.fields.len()"]
iter_gen["Generate field iteration code"]
for_each_imm["for_each: &$tuple.#idx"]
for_each_mut["for_each_mut: &mut $tuple.#idx"]
enum_imm["enumerate: (#idx, &$tuple.#idx)"]
enum_mut["enumerate_mut: (#idx, &mut $tuple.#idx)"]
macro_def["macro_rules! definitions"]
methods["impl block with len() & is_empty()"]
quote_expand["quote! macro expansion"]
token_stream["Final TokenStream"]

ast_input --> name_conv
enum_imm --> macro_def
enum_mut --> macro_def
field_count --> iter_gen
field_count --> methods
for_each_imm --> macro_def
for_each_mut --> macro_def
iter_gen --> enum_imm
iter_gen --> enum_mut
iter_gen --> for_each_imm
iter_gen --> for_each_mut
macro_def --> quote_expand
macro_names --> field_count
methods --> quote_expand
name_conv --> macro_names
quote_expand --> token_stream

Sources: src/lib.rs(L58 - L122) 

Field Access Pattern Generation

The macro generates field access code for each tuple field using zero-based indexing:

// For a 3-field tuple struct, generates:
// Field 0: &$tuple.0, &mut $tuple.0
// Field 1: &$tuple.1, &mut $tuple.1  
// Field 2: &$tuple.2, &mut $tuple.2

The field iteration logic creates four variants of access patterns:

Pattern TypeMutabilityGenerated Code Template
for_eachImmutable{ let $item = &$tuple.#idx; $code }
for_each_mutMutable{ let $item = &mut $tuple.#idx; $code }
enumerateImmutable{ let $idx = #idx; let $item = &$tuple.#idx; $code }
enumerate_mutMutable{ let $idx = #idx; let $item = &mut $tuple.#idx; $code }

Sources: src/lib.rs(L64 - L83) 

Error Handling

The derive macro implements compile-time validation with descriptive error messages:

Error Cases

  1. Non-Struct Types: Applied to enums, unions, or other non-struct types
  2. Named Structs: Applied to structs with named fields instead of tuple structs
  3. Unit Structs: Applied to unit structs (no fields)

Error Message Generation

Error::new_spanned(
    ast,
    "attribute `tuple_for_each` can only be attached to tuple structs",
)
.to_compile_error()
.into()

The error uses syn::Error::new_spanned to provide precise source location information and converts to a TokenStream containing the compile error.

Error Handling Flow

flowchart TD
invalid_input["Invalid Input Structure"]
new_spanned["Error::new_spanned(ast, message)"]
error_message["'attribute tuple_for_each can only be attached to tuple structs'"]
to_compile_error["to_compile_error()"]
into_token_stream["into()"]
compiler_error["Compiler displays error at source location"]

error_message --> to_compile_error
into_token_stream --> compiler_error
invalid_input --> new_spanned
new_spanned --> error_message
to_compile_error --> into_token_stream

Sources: src/lib.rs(L18 - L23) 

Implementation Dependencies

The derive macro relies on several key crates for parsing and code generation:

DependencyPurposeUsage in Code
synAST parsingsyn::parse_macro_input!,DeriveInput,DataStruct
quoteCode generationquote!macro,format_ident!
proc_macro2Token stream handlingReturn type forimpl_for_each
proc_macroProcedural macro interfaceTokenStreaminput/output

Sources: src/lib.rs(L3 - L5) 

Generated Macros

Relevant source files

This page documents the iteration macros automatically generated by the TupleForEach derive macro. These macros provide a convenient way to iterate over tuple struct fields at compile time with type safety.

For information about the derive macro itself, see TupleForEach Derive Macro. For the generated methods (len() and is_empty()), see Generated Methods.

Overview

When you apply #[derive(TupleForEach)] to a tuple struct, the macro generates two types of iteration macros:

Macro TypePurposeNaming Pattern
*_for_each!Iterate over fields{snake_case_name}_for_each!
*_enumerate!Iterate with field indices{snake_case_name}_enumerate!

Each macro type supports both immutable and mutable variants, allowing you to read from or modify tuple fields during iteration.

Macro Name Generation

flowchart TD
PascalCase["PascalCase Struct Namee.g., 'MyTuple'"]
pascal_to_snake["pascal_to_snake()Function"]
SnakeCase["snake_case Namee.g., 'my_tuple'"]
ForEach["my_tuple_for_each!Macro"]
Enumerate["my_tuple_enumerate!Macro"]

PascalCase --> pascal_to_snake
SnakeCase --> Enumerate
SnakeCase --> ForEach
pascal_to_snake --> SnakeCase

Sources: src/lib.rs(L124 - L133)  src/lib.rs(L60 - L62) 

For Each Macros

The *_for_each! macros iterate over tuple fields sequentially, providing access to each field value.

Syntax

{name}_for_each!(field_var in tuple_instance {
    // code block using field_var
});

{name}_for_each!(field_var in mut tuple_instance {
    // code block with mutable access to field_var  
});

Generated Implementation

Macro Structure and Field Access Pattern

flowchart TD
MacroCall["macro_for_each!(item in tuple { code })"]
ImmutablePath["Immutable Variant"]
MutablePath["macro_for_each!(item in mut tuple { code })"]
ImmutableExpansion["{ let $item = &$tuple.0; $code }{ let $item = &$tuple.1; $code }{ let $item = &$tuple.2; $code }"]
MutableExpansion["{ let $item = &mut $tuple.0; $code }{ let $item = &mut $tuple.1; $code }{ let $item = &mut $tuple.2; $code }"]

ImmutablePath --> ImmutableExpansion
MacroCall --> ImmutablePath
MacroCall --> MutablePath
MutablePath --> MutableExpansion

The macro generates sequential code blocks for each tuple field, where:

  • $item becomes the field variable name you specify
  • $tuple becomes the tuple instance name you provide
  • $code becomes your code block
  • Field access uses numeric indices (.0, .1, .2, etc.)

Sources: src/lib.rs(L102 - L109)  src/lib.rs(L71 - L72) 

Usage Examples

From the test suite, here are practical examples:

// Immutable iteration over Pair(A, B)
pair_for_each!(x in t {
    println!("for_each {}: {}", i, x.foo());
    x.bar();
    i += 1;
});

// Mutable iteration over Tuple(A, B, C)  
tuple_for_each!(x in mut t {
    println!("for_each_mut {}: {}", i, x.foo());
    x.bar_mut();
    i += 1;
});

Sources: tests/test_tuple_for_each.rs(L56 - L61)  tests/test_tuple_for_each.rs(L70 - L75) 

Enumerate Macros

The *_enumerate! macros provide both field index and field value during iteration, similar to iterator enumerate functionality.

Syntax

{name}_enumerate!((index_var, field_var) in tuple_instance {
    // code block using index_var and field_var
});

{name}_enumerate!((index_var, field_var) in mut tuple_instance {
    // code block with mutable access to field_var
});

Generated Implementation

Enumerate Macro Expansion Pattern

flowchart TD
EnumCall["macro_enumerate!((idx, item) in tuple { code })"]
ImmEnum["Immutable Enumerate"]
MutEnum["Mutable Enumerate"]
ImmEnumExp["{ let $idx = 0; let $item = &$tuple.0; $code }{ let $idx = 1; let $item = &$tuple.1; $code }{ let $idx = 2; let $item = &$tuple.2; $code }"]
MutEnumExp["{ let $idx = 0; let $item = &mut $tuple.0; $code }{ let $idx = 1; let $item = &mut $tuple.1; $code }{ let $idx = 2; let $item = &mut $tuple.2; $code }"]

EnumCall --> ImmEnum
EnumCall --> MutEnum
ImmEnum --> ImmEnumExp
MutEnum --> MutEnumExp

The enumerate macros generate code blocks that provide:

  • $idx - the field index as a literal value (0, 1, 2, etc.)
  • $item - reference to the field value (immutable or mutable)
  • Sequential execution for each tuple field

Sources: src/lib.rs(L113 - L120)  src/lib.rs(L73 - L82) 

Usage Examples

// Immutable enumeration over Tuple(A, B, C)
tuple_enumerate!((i, x) in t {
    println!("enumerate {}: {}", i, x.foo());
    x.bar();
    assert_eq!(i, real_idx);
    real_idx += 1;
});

// Mutable enumeration over Pair(A, B)
pair_enumerate!((i, x) in mut t {
    println!("enumerate_mut {}: {}", i, x.foo());
    x.bar_mut();
    assert_eq!(i, real_idx);
    real_idx += 1;
});

Sources: tests/test_tuple_for_each.rs(L84 - L90)  tests/test_tuple_for_each.rs(L99 - L105) 

Implementation Details

Macro Export and Documentation

Both macro types are exported using #[macro_export] and include generated documentation with usage examples specific to the tuple struct name.

Code Generation Pipeline for Macros

flowchart TD
StructName["Tuple Struct Name"]
pascal_to_snake["pascal_to_snake()"]
MacroNames["Macro Identifiers"]
FieldCount["Field Count"]
FieldLoops["Field Generation Loops"]
ForEachVecs["for_each & for_each_mut Vectors"]
EnumVecs["enumerate & enumerate_mut Vectors"]
Documentation["gen_doc() Function"]
DocStrings["Generated Doc Strings"]
QuoteExpansion["quote! Macro"]
FinalTokens["Generated macro_rules! Definitions"]

DocStrings --> QuoteExpansion
Documentation --> DocStrings
EnumVecs --> QuoteExpansion
FieldCount --> FieldLoops
FieldLoops --> EnumVecs
FieldLoops --> ForEachVecs
ForEachVecs --> QuoteExpansion
MacroNames --> Documentation
MacroNames --> QuoteExpansion
QuoteExpansion --> FinalTokens
StructName --> pascal_to_snake
pascal_to_snake --> MacroNames

The generation process involves:

  1. Converting struct name from PascalCase to snake_case
  2. Creating macro identifiers with _for_each and _enumerate suffixes
  3. Generating field access code for each tuple position
  4. Creating documentation strings with examples
  5. Assembling everything into macro_rules! definitions using quote!

Sources: src/lib.rs(L58 - L122)  src/lib.rs(L26 - L56) 

Field Access Pattern

All generated macros use numeric field access (.0, .1, .2) since tuple structs have unnamed fields. The field index is determined by the position in the tuple struct definition.

Field PositionAccess PatternGenerated Index
First field$tuple.00(for enumerate)
Second field$tuple.11(for enumerate)
Third field$tuple.22(for enumerate)

Sources: src/lib.rs(L69 - L83) 

Generated Methods

Relevant source files

This page documents the instance methods that are automatically generated when applying the #[derive(TupleForEach)] attribute to a tuple struct. These methods provide metadata and utility functions for working with tuple instances.

For information about the generated iteration macros, see Generated Macros. For details about the derive macro itself, see TupleForEach Derive Macro.

Overview

The TupleForEach derive macro generates exactly two methods on the target tuple struct, both providing metadata about the tuple's structure:

MethodReturn TypePurpose
len()usizeReturns the number of fields in the tuple
is_empty()boolReturns whether the tuple contains zero fields

Both methods are implemented as const fn, making them available for compile-time evaluation.

Generated Method Implementation Pattern

flowchart TD
subgraph subGraph0["Generated Methods"]
    LM["len() method generation"]
    EM["is_empty() method generation"]
end
DS["DeriveInput (tuple struct)"]
IF["impl_for_each()"]
FN["field_num calculation"]
IG["impl block for tuple struct"]
TS["Generated TokenStream"]
CC["Rust Compiler"]
UM["User methods available"]

CC --> UM
DS --> IF
EM --> IG
FN --> EM
FN --> LM
IF --> FN
IG --> TS
LM --> IG
TS --> CC

Sources: src/lib.rs(L58 - L122) 

Thelen()Method

Signature

pub const fn len(&self) -> usize

Behavior

Returns the compile-time constant representing the number of fields in the tuple struct. This value is determined during macro expansion by counting the fields in the DataStruct.

Implementation Details

The method is generated with the field count as a literal value, making it a true compile-time constant:

pub const fn len(&self) -> usize {
    #field_num  // Literal field count
}

Usage Characteristics

  • Compile-time evaluation: Can be used in const contexts
  • Zero-cost: Resolves to a literal integer at compile time
  • No runtime computation: Field count is baked into the binary

Sources: src/lib.rs(L89 - L92) 

Theis_empty()Method

Signature

pub const fn is_empty(&self) -> bool

Behavior

Returns true if the tuple has zero fields, false otherwise. The implementation delegates to the len() method for consistency.

Implementation Details

pub const fn is_empty(&self) -> bool {
    self.len() == 0
}

Edge Cases

For tuple structs with zero fields (unit-like structs), this method returns true. However, such structs are uncommon in practice since they're typically represented as unit structs rather than tuple structs.

Sources: src/lib.rs(L94 - L97) 

Method Generation Process

Code Generation Pipeline

flowchart TD
subgraph subGraph0["Template Generation"]
    QLA["quote! len() method"]
    QIE["quote! is_empty() method"]
end
AST["syn::DeriveInput"]
VF["validate Fields::Unnamed"]
IE["impl_for_each() entry"]
FC["strct.fields.len()"]
FN["field_num variable"]
IB["impl block assembly"]
TS["proc_macro2::TokenStream"]
CO["Compiler output"]

AST --> VF
FC --> FN
FN --> QIE
FN --> QLA
IB --> TS
IE --> FC
QIE --> IB
QLA --> IB
TS --> CO
VF --> IE

Sources: src/lib.rs(L58 - L98) 

Usage Examples

Basic Metadata Access

use tuple_for_each::TupleForEach;

#[derive(TupleForEach)]
struct Triple(i32, String, bool);

let data = Triple(42, "hello".to_string(), true);

// Compile-time accessible
const TRIPLE_SIZE: usize = Triple(0, String::new(), false).len();

// Runtime usage
assert_eq!(data.len(), 3);
assert!(!data.is_empty());

Integration with Generic Code

#![allow(unused)]
fn main() {
fn process_tuple<T>(tuple: T) 
where 
    T: /* has len() and is_empty() methods */
{
    if !tuple.is_empty() {
        println!("Processing tuple with {} fields", tuple.len());
        // Process non-empty tuple
    }
}
}

Conditional Compilation

#[derive(TupleForEach)]
struct Config(u32, String);

const CONFIG_FIELDS: usize = Config(0, String::new()).len();

#[cfg(const_eval)]
const _: () = {
    assert!(CONFIG_FIELDS > 0);
};

Sources: README.md(L20 - L28) 

Implementation Architecture

Method Generation Context

flowchart TD
subgraph subGraph3["Output Assembly"]
    QT["quote! impl block"]
    TS["TokenStream output"]
end
subgraph subGraph2["Method Generation"]
    LG["len() generation"]
    IG["is_empty() generation"]
end
subgraph subGraph1["Field Analysis"]
    FL["strct.fields.len()"]
    FN["field_num: usize"]
end
subgraph subGraph0["Input Processing"]
    TN["tuple_name: &Ident"]
    ST["strct: &DataStruct"]
end

FL --> FN
FN --> IG
FN --> LG
IG --> QT
LG --> QT
QT --> TS
ST --> FL
TN --> QT

The methods are generated within the same impl block that contains the iteration macros, ensuring they're part of a cohesive API surface for tuple manipulation.

Sources: src/lib.rs(L64 - L98) 

Performance Characteristics

Compile-Time Properties

  • Zero runtime cost: Both methods resolve to compile-time constants
  • No memory overhead: No additional storage required for metadata
  • Inlining friendly: Simple constant returns are trivially inlined

Runtime Behavior

  • len(): Single instruction returning immediate value
  • is_empty(): Single comparison against zero, often optimized away

Const Evaluation

Both methods are const fn, enabling their use in:

  • Const contexts and const generics
  • Array size specifications
  • Compile-time assertions
  • Static initialization

Sources: src/lib.rs(L89 - L97) 

Overview

Relevant source files

Purpose and Scope

The allocator crate provides a unified interface for various memory allocation algorithms in Rust. It abstracts different allocation strategies behind common traits, allowing users to select the most appropriate allocator for their use case through feature flags. The crate serves as a wrapper around specialized external allocator implementations, providing consistent APIs and error handling across all variants.

This document covers the core architecture, trait system, and high-level design of the allocator crate. For detailed information about specific allocator implementations, see Allocator Implementations. For usage examples and configuration guidance, see Usage and Configuration.

Core Architecture

The allocator crate is built around a trait-based architecture that defines three primary allocator categories, each serving different memory management needs:

Primary Allocator Traits

flowchart TD
BaseAllocator["BaseAllocator• init(start, size)• add_memory(start, size)"]
ByteAllocator["ByteAllocator• alloc(layout)• dealloc(pos, layout)• total_bytes()• used_bytes()"]
PageAllocator["PageAllocator• PAGE_SIZE: usize• alloc_pages(num_pages, align_pow2)• dealloc_pages(pos, num_pages)• total_pages()"]
IdAllocator["IdAllocator• alloc_id(count, align_pow2)• dealloc_id(start_id, count)• is_allocated(id)• alloc_fixed_id(id)"]

BaseAllocator --> ByteAllocator
BaseAllocator --> IdAllocator
BaseAllocator --> PageAllocator

Sources: src/lib.rs(L54 - L131) 

Concrete Implementation Mapping

flowchart TD
subgraph subGraph2["External Dependencies"]
    buddy_system_allocator["buddy_system_allocator"]
    slab_allocator["slab_allocator"]
    rlsf["rlsf"]
    bitmap_allocator["bitmap-allocator"]
end
subgraph subGraph1["Implementation Layer"]
    BuddyByteAllocator["BuddyByteAllocatorfeature: buddy"]
    SlabByteAllocator["SlabByteAllocatorfeature: slab"]
    TlsfByteAllocator["TlsfByteAllocatorfeature: tlsf"]
    BitmapPageAllocator["BitmapPageAllocatorfeature: bitmap"]
end
subgraph subGraph0["Trait Layer"]
    ByteAllocator_trait["ByteAllocator"]
    PageAllocator_trait["PageAllocator"]
end

BitmapPageAllocator --> bitmap_allocator
BuddyByteAllocator --> buddy_system_allocator
ByteAllocator_trait --> BuddyByteAllocator
ByteAllocator_trait --> SlabByteAllocator
ByteAllocator_trait --> TlsfByteAllocator
PageAllocator_trait --> BitmapPageAllocator
SlabByteAllocator --> slab_allocator
TlsfByteAllocator --> rlsf

Sources: src/lib.rs(L14 - L32)  Cargo.toml(L16 - L34) 

Feature-Gated Compilation System

The crate uses Cargo features to enable selective compilation of allocator implementations, minimizing binary size when only specific allocators are needed:

Feature Configuration

FeaturePurposeExternal Dependency
bitmapEnableBitmapPageAllocatorbitmap-allocator v0.2
buddyEnableBuddyByteAllocatorbuddy_system_allocator v0.10
slabEnableSlabByteAllocatorslab_allocator v0.3.1
tlsfEnableTlsfByteAllocatorrlsf v0.2
allocator_apiEnableAllocatorRcwrapperNone (stdlib integration)
page-alloc-*Configure page allocator size limitsNone

Sources: Cargo.toml(L12 - L27) 

Conditional Compilation Flow


Sources: src/lib.rs(L14 - L32)  src/lib.rs(L151 - L196) 

Error Handling System

The crate defines a unified error handling approach through the AllocError enum and AllocResult type alias:

// Core error types from the codebase
pub enum AllocError {
    InvalidParam,    // Invalid size or align_pow2
    MemoryOverlap,   // Memory regions overlap
    NoMemory,        // Insufficient memory
    NotAllocated,    // Deallocation of unallocated region
}

pub type AllocResult<T = ()> = Result<T, AllocError>;

Sources: src/lib.rs(L37 - L51) 

Standard Library Integration

When the allocator_api feature is enabled, the crate provides AllocatorRc<A>, which wraps any ByteAllocator implementation in Rc<RefCell<A>> and implements the standard library's core::alloc::Allocator trait:

flowchart TD
ByteAllocator_impl["ByteAllocator Implementation(BuddyByteAllocator, etc.)"]
AllocatorRc["AllocatorRc<A>Rc<RefCell<A>>"]
StdAllocator["core::alloc::AllocatorStandard Library Trait"]

AllocatorRc --> StdAllocator
ByteAllocator_impl --> AllocatorRc

Sources: src/lib.rs(L151 - L196) 

Key Components Summary

ComponentFilePurpose
Core traitssrc/lib.rs54-131Define allocator interfaces
Error typessrc/lib.rs37-51Unified error handling
Alignment utilitiessrc/lib.rs133-149Memory alignment helpers
Standard library bridgesrc/lib.rs151-196Integration withcore::alloc::Allocator
Feature configurationCargo.toml12-34Conditional compilation setup

The crate architecture emphasizes modularity through feature gates, consistency through shared traits, and flexibility through multiple allocation strategies while maintaining a minimal footprint when only specific allocators are needed.

Sources: Cargo.toml(L1 - L44)  src/lib.rs(L1 - L197) 

Architecture and Design

Relevant source files

This page documents the core architectural design of the allocator crate, including its trait-based interface system, error handling mechanisms, and feature-gated compilation model. The design provides a unified API for different memory allocation algorithms while maintaining modularity and minimal dependencies.

For implementation details of specific allocators, see Allocator Implementations. For practical usage guidance, see Usage and Configuration.

Core Trait Hierarchy

The allocator crate employs a trait-based architecture that separates allocation concerns by granularity and use case. All allocators inherit from a common BaseAllocator trait and specialize into three distinct categories.

Trait System Overview

flowchart TD
subgraph Methods["Methods"]
    BaseAllocator["BaseAllocator"]
    ByteAllocator["ByteAllocator"]
    PageAllocator["PageAllocator"]
    IdAllocator["IdAllocator"]
    init["init(start, size)"]
    add_memory["add_memory(start, size)"]
    alloc["alloc(layout)"]
    dealloc["dealloc(pos, layout)"]
    total_bytes["total_bytes()"]
    used_bytes["used_bytes()"]
    available_bytes["available_bytes()"]
    alloc_pages["alloc_pages(num_pages, align_pow2)"]
    dealloc_pages["dealloc_pages(pos, num_pages)"]
    total_pages["total_pages()"]
    alloc_id["alloc_id(count, align_pow2)"]
    dealloc_id["dealloc_id(start_id, count)"]
    is_allocated["is_allocated(id)"]
end
BuddyByteAllocator["BuddyByteAllocator"]
SlabByteAllocator["SlabByteAllocator"]
TlsfByteAllocator["TlsfByteAllocator"]
BitmapPageAllocator["BitmapPageAllocator"]

BaseAllocator --> ByteAllocator
BaseAllocator --> IdAllocator
BaseAllocator --> PageAllocator
BaseAllocator --> add_memory
BaseAllocator --> init
ByteAllocator --> BuddyByteAllocator
ByteAllocator --> SlabByteAllocator
ByteAllocator --> TlsfByteAllocator
ByteAllocator --> alloc
ByteAllocator --> available_bytes
ByteAllocator --> dealloc
ByteAllocator --> total_bytes
ByteAllocator --> used_bytes
IdAllocator --> alloc_id
IdAllocator --> dealloc_id
IdAllocator --> is_allocated
PageAllocator --> BitmapPageAllocator
PageAllocator --> alloc_pages
PageAllocator --> dealloc_pages
PageAllocator --> total_pages

Sources: src/lib.rs(L54 - L131) 

BaseAllocator Trait

The BaseAllocator trait provides fundamental memory management operations shared by all allocator types:

MethodPurposeParameters
init()Initialize allocator with initial memory regionstart: usize, size: usize
add_memory()Add additional memory regionsstart: usize, size: usize→AllocResult

Sources: src/lib.rs(L54 - L60) 

ByteAllocator Trait

The ByteAllocator trait handles byte-granularity allocation using Rust's Layout type for size and alignment specification:

MethodPurposeReturn Type
alloc()Allocate memory with layout constraintsAllocResult<NonNull>
dealloc()Deallocate previously allocated memory()
total_bytes()Query total memory pool sizeusize
used_bytes()Query currently allocated bytesusize
available_bytes()Query remaining available bytesusize

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

PageAllocator Trait

The PageAllocator trait manages memory at page granularity with configurable page sizes:

MethodPurposeParameters
alloc_pages()Allocate contiguous pagesnum_pages: usize, align_pow2: usize
alloc_pages_at()Allocate pages at specific addressbase: usize, num_pages: usize, align_pow2: usize
dealloc_pages()Deallocate page rangepos: usize, num_pages: usize

Sources: src/lib.rs(L80 - L107) 

IdAllocator Trait

The IdAllocator trait provides unique identifier allocation with support for fixed ID assignment:

MethodPurposeReturn Type
alloc_id()Allocate contiguous ID rangeAllocResult
alloc_fixed_id()Reserve specific IDAllocResult
is_allocated()Check ID allocation statusbool

Sources: src/lib.rs(L109 - L131) 

Error Handling System

The crate implements a comprehensive error handling system using custom error types that map to common allocation failure scenarios.

AllocError Enumeration

flowchart TD
AllocResult["AllocResult<T>"]
Ok["Ok(T)"]
Err["Err(AllocError)"]
InvalidParam["InvalidParam"]
MemoryOverlap["MemoryOverlap"]
NoMemory["NoMemory"]
NotAllocated["NotAllocated"]
InvalidDesc["Invalid size or align_pow2"]
OverlapDesc["Memory regions overlap"]
NoMemDesc["Insufficient free memory"]
NotAllocDesc["Deallocating unallocated region"]

AllocResult --> Err
AllocResult --> Ok
Err --> InvalidParam
Err --> MemoryOverlap
Err --> NoMemory
Err --> NotAllocated
InvalidParam --> InvalidDesc
MemoryOverlap --> OverlapDesc
NoMemory --> NoMemDesc
NotAllocated --> NotAllocDesc

Sources: src/lib.rs(L37 - L51) 

Error Categories

Error VariantTrigger ConditionUsage Context
InvalidParamInvalid size or alignment parametersInput validation
MemoryOverlapOverlapping memory regions inadd_memory()Memory pool expansion
NoMemoryInsufficient free memory for allocationRuntime allocation
NotAllocatedDeallocation of unallocated memoryMemory safety

The AllocResult<T> type alias simplifies error handling throughout the API by defaulting to Result<T, AllocError>.

Sources: src/lib.rs(L37 - L51) 

Feature-Gated Architecture

The crate uses Cargo features to enable conditional compilation of allocator implementations, minimizing binary size and dependencies for applications that only need specific allocation algorithms.

Feature-Based Compilation


Sources: src/lib.rs(L14 - L32)  src/lib.rs(L151 - L196) 

Conditional Compilation Strategy

The feature system works through conditional compilation attributes:

  • Each allocator implementation resides in a separate module
  • Module inclusion controlled by #[cfg(feature = "name")] attributes
  • Public re-exports controlled by the same feature flags
  • External dependencies only included when corresponding features are enabled

This design allows applications to include only required allocators, reducing binary size and compilation time.

Sources: src/lib.rs(L14 - L32) 

Standard Library Integration

The optional allocator_api feature provides integration with Rust's standard library allocator infrastructure through the AllocatorRc wrapper.

AllocatorRc Implementation

flowchart TD
subgraph subGraph1["Error Mapping"]
    AllocError_internal["super::AllocError"]
    AllocError_std["core::alloc::AllocError"]
end
subgraph subGraph0["Trait Implementations"]
    AllocatorRc["AllocatorRc<A: ByteAllocator>"]
    allocate_impl["allocate(layout: Layout)"]
    deallocate_impl["deallocate(ptr: NonNull<u8>, layout: Layout)"]
    Allocator["core::alloc::Allocator"]
    Clone["Clone"]
end
RcRefCell["Rc<RefCell<A>>"]
new["new(inner: A, pool: &mut [u8])"]

AllocError_internal --> AllocError_std
AllocatorRc --> Clone
AllocatorRc --> RcRefCell
AllocatorRc --> allocate_impl
AllocatorRc --> deallocate_impl
AllocatorRc --> new
allocate_impl --> AllocError_internal
allocate_impl --> Allocator
deallocate_impl --> Allocator

Sources: src/lib.rs(L151 - L196) 

Integration Mechanics

The AllocatorRc wrapper provides:

ComponentPurposeImplementation
Rc<RefCell>Shared ownership and interior mutabilityEnables cloning while maintaining mutable access
AllocatortraitStandard library compatibilityMapsallocate()/deallocate()to trait methods
Error conversionError type compatibilityConvertsAllocErrortocore::alloc::AllocError
Zero-size handlingStandard complianceReturns dangling pointer for zero-size allocations

Sources: src/lib.rs(L162 - L186) 

Utility Functions

The crate provides alignment utility functions that support the core allocation operations:

Alignment Operations

flowchart TD
subgraph subGraph0["Bit Operations"]
    down_result["pos & !(align - 1)"]
    up_result["(pos + align - 1) & !(align - 1)"]
    aligned_result["base_addr & (align - 1) == 0"]
    mask_clear["Clear low bits"]
    round_up["Round up then clear"]
    check_remainder["Check remainder is zero"]
end
input_addr["Input Address"]
align_down["align_down(pos, align)"]
align_up["align_up(pos, align)"]
is_aligned["is_aligned(base_addr, align)"]

align_down --> down_result
align_up --> up_result
aligned_result --> check_remainder
down_result --> mask_clear
input_addr --> align_down
input_addr --> align_up
input_addr --> is_aligned
is_aligned --> aligned_result
up_result --> round_up

Sources: src/lib.rs(L133 - L149) 

Function Specifications

FunctionPurposeAlgorithmConstraints
align_down()Round address down to alignment boundarypos & !(align - 1)alignmust be power of two
align_up()Round address up to alignment boundary(pos + align - 1) & !(align - 1)alignmust be power of two
is_aligned()Check if address meets alignment requirementbase_addr & (align - 1) == 0alignmust be power of two

These functions are marked const fn to enable compile-time evaluation and are used throughout the allocator implementations for address calculations.

Sources: src/lib.rs(L133 - L149) 

Allocator Implementations

Relevant source files

This document provides an overview of the different memory allocator implementations available in the allocator crate. It explains how each implementation relates to the trait system and their intended use cases within the unified allocator interface.

For detailed information about the core trait architecture that these implementations use, see Architecture and Design. For usage guidance and feature configuration, see Usage and Configuration.

Implementation Overview

The allocator crate provides four distinct allocator implementations, each optimized for different allocation patterns and granularities. These implementations are feature-gated, allowing users to compile only the allocators they need.

Implementation Categories

The implementations are organized according to the three main allocator trait categories:

ImplementationTrait CategoryGranularityFeature GateUse Case
BitmapPageAllocatorPageAllocatorPage-levelbitmapLarge contiguous allocations
BuddyByteAllocatorByteAllocatorByte-levelbuddyGeneral-purpose with low fragmentation
SlabByteAllocatorByteAllocatorByte-levelslabFixed-size object allocation
TlsfByteAllocatorByteAllocatorByte-leveltlsfReal-time systems requiring deterministic allocation

Implementation Architecture

flowchart TD
subgraph subGraph3["Standard Library Integration"]
    AllocatorRc["AllocatorRc[lib.rs:162]feature: allocator_api"]
    StdAllocator["core::alloc::AllocatorStandard Library Trait"]
end
subgraph subGraph2["Page-Level Implementations"]
    BitmapPageAllocator["BitmapPageAllocator[bitmap.rs]feature: bitmap"]
end
subgraph subGraph1["Byte-Level Implementations"]
    BuddyByteAllocator["BuddyByteAllocator[buddy.rs]feature: buddy"]
    SlabByteAllocator["SlabByteAllocator[slab.rs]feature: slab"]
    TlsfByteAllocator["TlsfByteAllocator[tlsf.rs]feature: tlsf"]
end
subgraph subGraph0["Core Traits [lib.rs:54-131]"]
    BaseAllocator["BaseAllocator• init(start, size)• add_memory(start, size)"]
    ByteAllocator["ByteAllocator• alloc(layout)• dealloc(pos, layout)• memory statistics"]
    PageAllocator["PageAllocator• alloc_pages(num, align)• dealloc_pages(pos, num)• page statistics"]
    IdAllocator["IdAllocator• alloc_id(count, align)• dealloc_id(start, count)• id management"]
end

AllocatorRc --> StdAllocator
BaseAllocator --> ByteAllocator
BaseAllocator --> IdAllocator
BaseAllocator --> PageAllocator
ByteAllocator --> AllocatorRc
ByteAllocator --> BuddyByteAllocator
ByteAllocator --> SlabByteAllocator
ByteAllocator --> TlsfByteAllocator
PageAllocator --> BitmapPageAllocator

Sources: src/lib.rs(L1 - L197) 

Feature-Gated Compilation


Sources: src/lib.rs(L14 - L32)  src/lib.rs(L151 - L196) 

Allocator Characteristics

Memory Granularity Comparison

The implementations operate at different levels of granularity, affecting their suitability for different use cases:

Byte-Level Allocators

  • BuddyByteAllocator: Uses buddy system algorithm for efficient memory management with power-of-two sized blocks
  • SlabByteAllocator: Optimized for allocating many objects of the same size with minimal overhead
  • TlsfByteAllocator: Two-Level Segregated Fit algorithm providing constant-time allocation suitable for real-time systems

Page-Level Allocators

  • BitmapPageAllocator: Manages memory at page granularity using bitmap tracking for large contiguous allocations

Error Handling

All implementations use the unified error handling system defined in the core traits:

flowchart TD
AllocResult["AllocResult[lib.rs:51]"]
AllocError["AllocError[lib.rs:38-48]"]
InvalidParam["InvalidParamInvalid size/alignment"]
MemoryOverlap["MemoryOverlapOverlapped memory regions"]
NoMemory["NoMemoryInsufficient memory"]
NotAllocated["NotAllocatedInvalid deallocation"]

AllocError --> InvalidParam
AllocError --> MemoryOverlap
AllocError --> NoMemory
AllocError --> NotAllocated
AllocResult --> AllocError

Sources: src/lib.rs(L37 - L51) 

Implementation Selection Guide

Performance Characteristics

AllocatorAllocation SpeedDeallocation SpeedMemory OverheadFragmentation Resistance
BuddyByteAllocatorO(log n)O(log n)ModerateGood
SlabByteAllocatorO(1)O(1)LowExcellent (same-size)
TlsfByteAllocatorO(1)O(1)LowGood
BitmapPageAllocatorO(n)O(1)Very lowExcellent

Use Case Recommendations

  • General-purpose applications: BuddyByteAllocator provides good balance of performance and memory efficiency
  • Object-oriented systems with many same-sized allocations: SlabByteAllocator excels at allocating uniform objects
  • Real-time systems: TlsfByteAllocator offers deterministic allocation times
  • Systems requiring large contiguous memory blocks: BitmapPageAllocator efficiently manages page-sized allocations

Standard Library Integration

The AllocatorRc wrapper enables integration with Rust's standard library allocator API when the allocator_api feature is enabled. This allows byte allocators to be used with standard collections and memory management primitives.

The wrapper implements the core::alloc::Allocator trait by wrapping any ByteAllocator implementation in Rc<RefCell<_>> for shared access patterns typical in standard library usage.

Sources: src/lib.rs(L151 - L196) 

Implementation Details

For detailed documentation of each allocator implementation, including algorithm specifics, configuration options, and usage examples:

Bitmap Page Allocator

Relevant source files

This document covers the BitmapPageAllocator implementation, which provides page-granularity memory allocation using a bitmap-based approach. The allocator manages memory in fixed-size pages and uses an external bitmap data structure to track allocation status.

For byte-granularity allocation algorithms, see Buddy System Allocator, Slab Allocator, and TLSF Allocator. For broader architectural concepts and trait definitions, see Architecture and Design.

Overview and Purpose

The BitmapPageAllocator is a page-granularity allocator that internally uses a bitmap where each bit indicates whether a page has been allocated. It wraps the external bitmap_allocator crate and implements the PageAllocator and BaseAllocator traits defined in the core library.

Core Allocator Structure

classDiagram
class BitmapPageAllocator {
    +PAGE_SIZE: usize
    -base: usize
    -total_pages: usize
    -used_pages: usize
    -inner: BitAllocUsed
    +new() BitmapPageAllocator
    +alloc_pages(num_pages, align_pow2) AllocResult~usize~
    +alloc_pages_at(base, num_pages, align_pow2) AllocResult~usize~
    +dealloc_pages(pos, num_pages)
    +total_pages() usize
    +used_pages() usize
    +available_pages() usize
}

class BaseAllocator {
    <<interface>>
    
    +init(start, size)
    +add_memory(start, size) AllocResult
}

class PageAllocator {
    <<interface>>
    +PAGE_SIZE: usize
    +alloc_pages(num_pages, align_pow2) AllocResult~usize~
    +dealloc_pages(pos, num_pages)
    +total_pages() usize
    +used_pages() usize
    +available_pages() usize
}

class BitAllocUsed {
    <<external>>
    
    +alloc() Option~usize~
    +alloc_contiguous(start, num, align_log2) Option~usize~
    +dealloc(idx) bool
    +dealloc_contiguous(start, num) bool
    +insert(range)
}

PageAllocator  --|>  BaseAllocator
BitmapPageAllocator  --|>  PageAllocator
BitmapPageAllocator  *--  BitAllocUsed

Sources: src/bitmap.rs(L34 - L50)  src/bitmap.rs(L53 - L75)  src/bitmap.rs(L77 - L160) 

Memory Size Configuration

The allocator uses feature flags to configure the maximum memory size it can manage. Different BitAlloc implementations from the external bitmap_allocator crate are selected based on compilation features.

Feature-Based BitAlloc Selection

flowchart TD
subgraph subGraph2["Memory Capacity"]
    CAP_4G["4GB Memory(Testing)"]
    CAP_1T["1TB Memory"]
    CAP_64G["64GB Memory"]
    CAP_256M["256MB Memory(Default)"]
end
subgraph subGraph1["BitAlloc Types"]
    BITALLOC_1M["BitAlloc1M1M pages = 4GB max"]
    BITALLOC_256M["BitAlloc256M256M pages = 1TB max"]
    BITALLOC_16M["BitAlloc16M16M pages = 64GB max"]
    BITALLOC_64K["BitAlloc64K64K pages = 256MB max"]
end
subgraph subGraph0["Feature Flags"]
    TEST["cfg(test)"]
    FEAT_1T["page-alloc-1t"]
    FEAT_64G["page-alloc-64g"]
    FEAT_4G["page-alloc-4g"]
    DEFAULT["default/page-alloc-256m"]
end

BITALLOC_16M --> CAP_64G
BITALLOC_1M --> CAP_4G
BITALLOC_256M --> CAP_1T
BITALLOC_64K --> CAP_256M
DEFAULT --> BITALLOC_64K
FEAT_1T --> BITALLOC_256M
FEAT_4G --> BITALLOC_1M
FEAT_64G --> BITALLOC_16M
TEST --> BITALLOC_1M

Sources: src/bitmap.rs(L9 - L26) 

The configuration table shows the relationship between features and memory limits:

Feature FlagBitAlloc TypeMax PagesMax Memory (4KB pages)
testBitAlloc1M1,048,5764GB
page-alloc-1tBitAlloc256M268,435,4561TB
page-alloc-64gBitAlloc16M16,777,21664GB
page-alloc-4gBitAlloc1M1,048,5764GB
page-alloc-256m(default)BitAlloc64K65,536256MB

Core Implementation Details

Memory Layout and Base Address Calculation

The allocator manages memory regions by calculating a base address aligned to 1GB boundaries and tracking page indices relative to this base.

flowchart TD
subgraph subGraph2["Bitmap Initialization"]
    INSERT_RANGE["inner.insert(start_idx..start_idx + total_pages)"]
end
subgraph subGraph1["Base Address Calculation"]
    CALC_BASE["base = align_down(start, MAX_ALIGN_1GB)"]
    REL_START["relative_start = start - base"]
    START_IDX["start_idx = relative_start / PAGE_SIZE"]
end
subgraph subGraph0["Memory Region Setup"]
    INPUT_START["Input: start, size"]
    ALIGN_START["align_up(start, PAGE_SIZE)"]
    ALIGN_END["align_down(start + size, PAGE_SIZE)"]
    CALC_TOTAL["total_pages = (end - start) / PAGE_SIZE"]
end

ALIGN_END --> CALC_TOTAL
ALIGN_START --> CALC_BASE
ALIGN_START --> CALC_TOTAL
CALC_BASE --> REL_START
CALC_TOTAL --> INSERT_RANGE
INPUT_START --> ALIGN_END
INPUT_START --> ALIGN_START
REL_START --> START_IDX
START_IDX --> INSERT_RANGE

Sources: src/bitmap.rs(L54 - L70)  src/bitmap.rs(L7) 

Page Allocation Process

The allocator provides two allocation methods: general allocation with alignment requirements and allocation at specific addresses.

sequenceDiagram
    participant Client as Client
    participant BitmapPageAllocator as BitmapPageAllocator
    participant BitAllocUsed as BitAllocUsed

    Note over Client,BitAllocUsed: General Page Allocation
    Client ->> BitmapPageAllocator: alloc_pages(num_pages, align_pow2)
    BitmapPageAllocator ->> BitmapPageAllocator: Validate alignment parameters
    BitmapPageAllocator ->> BitmapPageAllocator: Convert align_pow2 to page units
    alt num_pages == 1
        BitmapPageAllocator ->> BitAllocUsed: alloc()
    else num_pages > 1
        BitmapPageAllocator ->> BitAllocUsed: alloc_contiguous(None, num_pages, align_log2)
    end
    BitAllocUsed -->> BitmapPageAllocator: Option<page_idx>
    BitmapPageAllocator ->> BitmapPageAllocator: Convert to address: idx * PAGE_SIZE + base
    BitmapPageAllocator ->> BitmapPageAllocator: Update used_pages count
    BitmapPageAllocator -->> Client: AllocResult<usize>
    Note over Client,BitAllocUsed: Specific Address Allocation
    Client ->> BitmapPageAllocator: alloc_pages_at(base, num_pages, align_pow2)
    BitmapPageAllocator ->> BitmapPageAllocator: Validate alignment and base address
    BitmapPageAllocator ->> BitmapPageAllocator: Calculate target page index
    BitmapPageAllocator ->> BitAllocUsed: alloc_contiguous(Some(idx), num_pages, align_log2)
    BitAllocUsed -->> BitmapPageAllocator: Option<page_idx>
    BitmapPageAllocator -->> Client: AllocResult<usize>

Sources: src/bitmap.rs(L80 - L100)  src/bitmap.rs(L103 - L131) 

Allocation Constraints and Validation

The allocator enforces several constraints to ensure correct operation:

Alignment Validation Process

flowchart TD
subgraph Results["Results"]
    VALID["Valid Parameters"]
    INVALID["AllocError::InvalidParam"]
end
subgraph subGraph2["Parameter Validation"]
    CHECK_NUM_PAGES["num_pages > 0"]
    CHECK_PAGE_SIZE["PAGE_SIZE.is_power_of_two()"]
end
subgraph subGraph1["Address Validation (alloc_pages_at)"]
    CHECK_BASE_ALIGN["is_aligned(base, align_pow2)"]
end
subgraph subGraph0["Alignment Checks"]
    CHECK_MAX["align_pow2 <= MAX_ALIGN_1GB"]
    CHECK_PAGE_ALIGN["is_aligned(align_pow2, PAGE_SIZE)"]
    CHECK_POW2["(align_pow2 / PAGE_SIZE).is_power_of_two()"]
end

CHECK_BASE_ALIGN --> INVALID
CHECK_BASE_ALIGN --> VALID
CHECK_MAX --> INVALID
CHECK_MAX --> VALID
CHECK_NUM_PAGES --> INVALID
CHECK_NUM_PAGES --> VALID
CHECK_PAGE_ALIGN --> INVALID
CHECK_PAGE_ALIGN --> VALID
CHECK_PAGE_SIZE --> VALID
CHECK_POW2 --> INVALID
CHECK_POW2 --> VALID

Sources: src/bitmap.rs(L82 - L88)  src/bitmap.rs(L111 - L121)  src/bitmap.rs(L55) 

Key constraints include:

  • PAGE_SIZE must be a power of two
  • Maximum alignment is 1GB (MAX_ALIGN_1GB = 0x4000_0000)
  • Alignment must be a multiple of PAGE_SIZE
  • Alignment (in page units) must be a power of two
  • For alloc_pages_at, the base address must be aligned to the requested alignment

Memory Management Operations

Deallocation Process

flowchart TD
subgraph subGraph1["Deallocation Flow"]
    INPUT["dealloc_pages(pos, num_pages)"]
    ASSERT_ALIGN["assert: pos aligned to PAGE_SIZE"]
    CALC_IDX["idx = (pos - base) / PAGE_SIZE"]
    UPDATE_COUNT["used_pages -= num_pages"]
    subgraph subGraph0["Bitmap Update"]
        SINGLE["num_pages == 1:inner.dealloc(idx)"]
        MULTI["num_pages > 1:inner.dealloc_contiguous(idx, num_pages)"]
    end
end

ASSERT_ALIGN --> CALC_IDX
CALC_IDX --> MULTI
CALC_IDX --> SINGLE
INPUT --> ASSERT_ALIGN
MULTI --> UPDATE_COUNT
SINGLE --> UPDATE_COUNT

Sources: src/bitmap.rs(L133 - L147) 

Statistics Tracking

The allocator maintains three key statistics accessible through the PageAllocator interface:

MethodDescriptionImplementation
total_pages()Total pages managedReturnsself.total_pages
used_pages()Currently allocated pagesReturnsself.used_pages
available_pages()Free pages remainingReturnstotal_pages - used_pages

Sources: src/bitmap.rs(L149 - L159) 

Usage Patterns and Testing

The test suite demonstrates typical usage patterns and validates the allocator's behavior across different scenarios.

Single Page Allocation Example

sequenceDiagram
    participant TestCode as Test Code
    participant BitmapPageAllocator as BitmapPageAllocator

    TestCode ->> BitmapPageAllocator: new()
    TestCode ->> BitmapPageAllocator: init(PAGE_SIZE, PAGE_SIZE)
    Note over BitmapPageAllocator: total_pages=1, used_pages=0
    TestCode ->> BitmapPageAllocator: alloc_pages(1, PAGE_SIZE)
    BitmapPageAllocator -->> TestCode: addr = 0x1000
    Note over BitmapPageAllocator: used_pages=1, available_pages=0
    TestCode ->> BitmapPageAllocator: dealloc_pages(0x1000, 1)
    Note over BitmapPageAllocator: used_pages=0, available_pages=1
    TestCode ->> BitmapPageAllocator: alloc_pages(1, PAGE_SIZE)
    BitmapPageAllocator -->> TestCode: addr = 0x1000 (reused)

Sources: src/bitmap.rs(L168 - L190) 

Large Memory Region Testing

The test suite validates operation with large memory regions (2GB) and various allocation patterns:

  • Sequential allocation/deallocation of 1, 10, 100, 1000 pages
  • Alignment testing with powers of 2 from PAGE_SIZE to MAX_ALIGN_1GB
  • Multiple concurrent allocations with different alignments
  • Specific address allocation using alloc_pages_at

Sources: src/bitmap.rs(L193 - L327) 

The allocator demonstrates robust behavior across different memory sizes and allocation patterns while maintaining efficient bitmap-based tracking of page allocation status.

Buddy System Allocator

Relevant source files

This document covers the BuddyByteAllocator implementation, which provides byte-granularity memory allocation using the buddy system algorithm. This allocator is one of several byte-level allocators available in the crate and is enabled through the buddy feature flag.

For information about the overall trait architecture and how allocators integrate with the system, see Architecture and Design. For other byte-level allocator implementations, see Slab Allocator and TLSF Allocator.

Implementation Overview

The BuddyByteAllocator serves as a wrapper around the external buddy_system_allocator::Heap crate, adapting it to the crate's trait-based interface. The implementation focuses on providing a consistent API while leveraging the performance characteristics of the buddy system algorithm.

Core Structure

flowchart TD
subgraph subGraph3["Key Methods"]
    INIT["init(start, size)"]
    ADD["add_memory(start, size)"]
    ALLOC["alloc(layout)"]
    DEALLOC["dealloc(pos, layout)"]
    STATS["total_bytes(), used_bytes(), available_bytes()"]
end
subgraph subGraph2["External Dependency"]
    EXT["buddy_system_allocator::Heap<32>"]
end
subgraph subGraph1["Trait Implementations"]
    BASE["BaseAllocator"]
    BYTE["ByteAllocator"]
end
subgraph subGraph0["BuddyByteAllocator Structure"]
    BUDDY["BuddyByteAllocator"]
    INNER["inner: Heap<32>"]
end

BASE --> ADD
BASE --> INIT
BUDDY --> BASE
BUDDY --> BYTE
BUDDY --> INNER
BYTE --> ALLOC
BYTE --> DEALLOC
BYTE --> STATS
INNER --> EXT

Sources: src/buddy.rs(L14 - L25) 

The allocator uses a fixed const generic parameter of 32, which determines the maximum order of buddy blocks that can be allocated from the underlying Heap<32> implementation.

ComponentTypePurpose
BuddyByteAllocatorStructMain allocator wrapper
innerHeap<32>Underlying buddy system implementation
Generic Parameter32Maximum buddy block order

Trait Implementation Details

BaseAllocator Implementation

The BaseAllocator trait provides fundamental memory management operations for initializing and expanding the allocator's memory pool.

flowchart TD
subgraph subGraph2["Safety Considerations"]
    UNSAFE_INIT["unsafe { inner.init() }"]
    UNSAFE_ADD["unsafe { inner.add_to_heap() }"]
end
subgraph subGraph1["Internal Operations"]
    HEAP_INIT["inner.init(start, size)"]
    HEAP_ADD["inner.add_to_heap(start, start + size)"]
end
subgraph subGraph0["BaseAllocator Methods"]
    INIT_CALL["init(start: usize, size: usize)"]
    ADD_CALL["add_memory(start: usize, size: usize)"]
end

ADD_CALL --> HEAP_ADD
HEAP_ADD --> UNSAFE_ADD
HEAP_INIT --> UNSAFE_INIT
INIT_CALL --> HEAP_INIT

Sources: src/buddy.rs(L27 - L36) 

The init method performs initial setup of the memory region, while add_memory allows dynamic expansion of the allocator's available memory pool. Both operations delegate to the underlying Heap implementation using unsafe blocks.

ByteAllocator Implementation

The ByteAllocator trait provides the core allocation and deallocation interface along with memory usage statistics.

flowchart TD
subgraph Statistics["Statistics"]
    TOTAL["total_bytes()"]
    STATS_TOTAL["inner.stats_total_bytes()"]
    USED["used_bytes()"]
    STATS_ACTUAL["inner.stats_alloc_actual()"]
    AVAILABLE["available_bytes()"]
    CALC_AVAIL["total - actual"]
    DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"]
    HEAP_DEALLOC["inner.dealloc(pos, layout)"]
    ALLOC_REQ["alloc(layout: Layout)"]
    HEAP_ALLOC["inner.alloc(layout)"]
end
subgraph subGraph0["Allocation Flow"]
    ERROR_MAP["map_err(|_| AllocError::NoMemory)"]
    RESULT["AllocResult>"]
    subgraph subGraph1["Deallocation Flow"]
        TOTAL["total_bytes()"]
        STATS_TOTAL["inner.stats_total_bytes()"]
        DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"]
        HEAP_DEALLOC["inner.dealloc(pos, layout)"]
        ALLOC_REQ["alloc(layout: Layout)"]
        HEAP_ALLOC["inner.alloc(layout)"]
    end
end

ALLOC_REQ --> HEAP_ALLOC
AVAILABLE --> CALC_AVAIL
DEALLOC_REQ --> HEAP_DEALLOC
ERROR_MAP --> RESULT
HEAP_ALLOC --> ERROR_MAP
TOTAL --> STATS_TOTAL
USED --> STATS_ACTUAL

Sources: src/buddy.rs(L38 - L58) 

MethodReturn TypeDescription
allocAllocResult<NonNull>Allocates memory according to layout requirements
dealloc()Deallocates previously allocated memory
total_bytesusizeTotal memory managed by allocator
used_bytesusizeCurrently allocated memory
available_bytesusizeCalculated as total minus used bytes

Memory Management Characteristics

Buddy System Algorithm

The buddy system algorithm manages memory by maintaining blocks in powers of two sizes. When a block is split, it creates two "buddy" blocks that can be efficiently merged when both become free.

Error Handling

The allocator implements a simple error mapping strategy where allocation failures from the underlying Heap are converted to AllocError::NoMemory. This provides a consistent error interface across all allocator implementations in the crate.

Memory Statistics

The implementation provides three key statistics through delegation to the underlying heap:

  • Total bytes: Complete memory pool size managed by the allocator
  • Used bytes: Currently allocated memory tracked by the heap
  • Available bytes: Computed difference representing allocatable memory

Sources: src/buddy.rs(L47 - L57) 

Integration with Feature System

The BuddyByteAllocator is conditionally compiled based on the buddy feature flag defined in the crate's feature system. When enabled, it provides byte-granularity allocation as an alternative to page-based or other allocation strategies.

The allocator integrates with the crate's unified interface through the trait implementations, allowing it to be used interchangeably with other ByteAllocator implementations depending on the application's requirements.

Sources: src/buddy.rs(L1 - L59) 

Slab Allocator

Relevant source files

This document covers the SlabByteAllocator implementation, which provides byte-granularity memory allocation using the slab allocation algorithm. The slab allocator is designed for efficient allocation and deallocation of fixed-size objects by pre-allocating chunks of memory organized into "slabs."

For information about other byte-level allocators, see Buddy System Allocator and TLSF Allocator. For page-granularity allocation, see Bitmap Page Allocator. For the overall trait architecture that this allocator implements, see Architecture and Design.

Architecture and Design

Trait Implementation Hierarchy

The SlabByteAllocator implements both core allocator traits defined in the crate's trait system:

flowchart TD
BaseAlloc["BaseAllocator"]
ByteAlloc["ByteAllocator"]
SlabImpl["SlabByteAllocator"]
HeapWrapper["slab_allocator::Heap"]
BaseInit["init(start, size)"]
BaseAdd["add_memory(start, size)"]
ByteAllocMethod["alloc(layout)"]
ByteDealloc["dealloc(pos, layout)"]
ByteStats["total_bytes(), used_bytes(), available_bytes()"]
SlabNew["new()"]
SlabInner["inner_mut(), inner()"]

BaseAlloc --> BaseAdd
BaseAlloc --> BaseInit
BaseAlloc --> SlabImpl
ByteAlloc --> ByteAllocMethod
ByteAlloc --> ByteDealloc
ByteAlloc --> ByteStats
ByteAlloc --> SlabImpl
SlabImpl --> HeapWrapper
SlabImpl --> SlabInner
SlabImpl --> SlabNew

Sources: src/slab.rs(L5 - L68) 

External Dependency Integration

The SlabByteAllocator serves as a wrapper around the external slab_allocator crate, specifically the Heap type:

flowchart TD
UserCode["User Code"]
SlabWrapper["SlabByteAllocator"]
InternalHeap["Option"]
ExternalCrate["slab_allocator v0.3.1"]
TraitMethods["BaseAllocator + ByteAllocator methods"]
HeapMethods["Heap::new(), allocate(), deallocate()"]

InternalHeap --> ExternalCrate
InternalHeap --> HeapMethods
SlabWrapper --> InternalHeap
SlabWrapper --> TraitMethods
UserCode --> SlabWrapper

Sources: src/slab.rs(L8)  src/slab.rs(L14) 

Implementation Details

Struct Definition and State Management

The SlabByteAllocator maintains minimal internal state, delegating the actual allocation logic to the wrapped Heap:

ComponentTypePurpose
innerOptionWrapped slab allocator instance

The Option wrapper allows for lazy initialization through the BaseAllocator::init method.

Sources: src/slab.rs(L13 - L15) 

Construction and Initialization

sequenceDiagram
    participant User as User
    participant SlabByteAllocator as SlabByteAllocator
    participant Heap as Heap

    User ->> SlabByteAllocator: new()
    Note over Heap,SlabByteAllocator: inner = None
    User ->> SlabByteAllocator: init(start, size)
    SlabByteAllocator ->> Heap: unsafe Heap::new(start, size)
    Heap -->> SlabByteAllocator: heap instance
    Note over Heap,SlabByteAllocator: inner = Some(heap)

The allocator follows a two-phase initialization pattern:

  1. new() creates an uninitialized allocator with inner = None
  2. init(start, size) creates the underlying Heap instance

Sources: src/slab.rs(L18 - L21)  src/slab.rs(L33 - L35) 

Memory Management Operations

The allocator provides internal accessor methods for safe access to the initialized heap:

flowchart TD
AccessMethods["Accessor Methods"]
InnerMut["inner_mut() -> &mut Heap"]
Inner["inner() -> &Heap"]
Unwrap["Option::as_mut().unwrap()"]
MutOperations["alloc(), dealloc(), add_memory()"]
ReadOperations["total_bytes(), used_bytes(), available_bytes()"]

AccessMethods --> Inner
AccessMethods --> InnerMut
Inner --> ReadOperations
Inner --> Unwrap
InnerMut --> MutOperations
InnerMut --> Unwrap

Sources: src/slab.rs(L23 - L29) 

Memory Allocation API

BaseAllocator Implementation

The BaseAllocator trait provides fundamental memory region management:

MethodParametersBehavior
initstart: usize, size: usizeCreates newHeapinstance for memory region
add_memorystart: usize, size: usizeAdds additional memory region to existing heap

Both methods work with raw memory addresses and sizes in bytes.

Sources: src/slab.rs(L32 - L43) 

ByteAllocator Implementation

The ByteAllocator trait provides fine-grained allocation operations:

flowchart TD
ByteAllocator["ByteAllocator Trait"]
AllocMethod["alloc(layout: Layout)"]
DeallocMethod["dealloc(pos: NonNull, layout: Layout)"]
StatsGroup["Memory Statistics"]
AllocResult["AllocResult>"]
ErrorMap["map_err(|_| AllocError::NoMemory)"]
UnsafeDealloc["unsafe deallocate(ptr, layout)"]
TotalBytes["total_bytes()"]
UsedBytes["used_bytes()"]
AvailableBytes["available_bytes()"]

AllocMethod --> AllocResult
AllocMethod --> ErrorMap
ByteAllocator --> AllocMethod
ByteAllocator --> DeallocMethod
ByteAllocator --> StatsGroup
DeallocMethod --> UnsafeDealloc
StatsGroup --> AvailableBytes
StatsGroup --> TotalBytes
StatsGroup --> UsedBytes

Error Handling

The allocation method converts internal allocation failures to the standardized AllocError::NoMemory variant, providing consistent error semantics across all allocator implementations in the crate.

Sources: src/slab.rs(L45 - L68) 

Memory Statistics

The allocator exposes three memory statistics through delegation to the underlying Heap:

StatisticDescription
total_bytes()Total memory managed by allocator
used_bytes()Currently allocated memory
available_bytes()Free memory available for allocation

Sources: src/slab.rs(L57 - L67) 

Slab Allocation Characteristics

The slab allocator is particularly efficient for workloads involving:

  • Frequent allocation and deallocation of similar-sized objects
  • Scenarios where memory fragmentation needs to be minimized
  • Applications requiring predictable allocation performance

The underlying slab_allocator::Heap implementation handles the complexity of slab management, chunk organization, and free list maintenance, while the wrapper provides integration with the crate's trait system and error handling conventions.

Sources: src/slab.rs(L1 - L3)  src/slab.rs(L10 - L12) 

TLSF Allocator

Relevant source files

This document covers the TLSF (Two-Level Segregated Fit) byte allocator implementation in the allocator crate. The TlsfByteAllocator provides real-time memory allocation with O(1) time complexity for both allocation and deallocation operations.

For information about other byte-level allocators, see Buddy System Allocator and Slab Allocator. For page-level allocation, see Bitmap Page Allocator.

TLSF Algorithm Overview

The Two-Level Segregated Fit (TLSF) algorithm is a real-time memory allocator that maintains free memory blocks in a two-level segregated list structure. It achieves deterministic O(1) allocation and deallocation performance by organizing free blocks into size classes using a first-level index (FLI) and second-level index (SLI).

Algorithm Structure

flowchart TD
subgraph subGraph0["TLSF Algorithm Organization"]
    SLI1["Second Level Index 0"]
    SLI2["Second Level Index 1"]
    SLI3["Second Level Index N"]
    BLOCK1["Free Block 1"]
    BLOCK2["Free Block 2"]
    BLOCK3["Free Block 3"]
    BLOCKN["Free Block N"]
    subgraph subGraph1["Size Classification"]
        SMALL["Small blocks: 2^i to 2^(i+1)-1"]
        MEDIUM["Medium blocks: subdivided by SLI"]
        LARGE["Large blocks: power-of-2 ranges"]
        FLI["First Level Index (FLI)"]
    end
end

FLI --> SLI1
FLI --> SLI2
FLI --> SLI3
SLI1 --> BLOCK1
SLI1 --> BLOCK2
SLI2 --> BLOCK3
SLI3 --> BLOCKN

Sources: src/tlsf.rs(L1 - L18) 

Implementation Structure

The TlsfByteAllocator is implemented as a wrapper around the rlsf::Tlsf external crate with specific configuration parameters optimized for the allocator crate's use cases.

Core Components

flowchart TD
subgraph subGraph2["Trait Implementation"]
    BASE["BaseAllocator"]
    BYTE["ByteAllocator"]
    INIT["init()"]
    ADD["add_memory()"]
    ALLOC["alloc()"]
    DEALLOC["dealloc()"]
end
subgraph subGraph1["External Dependency"]
    RLSF["rlsf::Tlsf"]
    PARAMS["Generic Parameters"]
    FLLEN["FLLEN = 28"]
    SLLEN["SLLEN = 32"]
    MAXSIZE["Max Pool Size = 8GB"]
end
subgraph subGraph0["TlsfByteAllocator Structure"]
    WRAPPER["TlsfByteAllocator"]
    INNER["inner: Tlsf<'static, u32, u32, 28, 32>"]
    TOTAL["total_bytes: usize"]
    USED["used_bytes: usize"]
end

BASE --> ADD
BASE --> INIT
BYTE --> ALLOC
BYTE --> DEALLOC
INNER --> RLSF
PARAMS --> FLLEN
PARAMS --> MAXSIZE
PARAMS --> SLLEN
RLSF --> PARAMS
WRAPPER --> BASE
WRAPPER --> BYTE
WRAPPER --> INNER
WRAPPER --> TOTAL
WRAPPER --> USED

Sources: src/tlsf.rs(L10 - L18)  src/tlsf.rs(L31 - L77) 

Configuration Parameters

The implementation uses fixed configuration parameters that define the allocator's capabilities:

ParameterValuePurpose
FLLEN28First-level index length, supporting up to 2^28 byte blocks
SLLEN32Second-level index length for fine-grained size classification
Max Pool Size8GBTheoretical maximum: 32 * 2^28 bytes
Generic Typesu32, u32Index types for FLI and SLI bitmaps

Sources: src/tlsf.rs(L15) 

BaseAllocator Implementation

The TlsfByteAllocator implements the BaseAllocator trait to provide memory pool initialization and management capabilities.

Memory Pool Management

flowchart TD
subgraph subGraph0["Memory Pool Operations"]
    INIT["init(start: usize, size: usize)"]
    RAW_PTR["Raw Memory Pointer"]
    ADD["add_memory(start: usize, size: usize)"]
    SLICE["core::slice::from_raw_parts_mut()"]
    NONNULL["NonNull<[u8]>"]
    INSERT["inner.insert_free_block_ptr()"]
    TOTAL_UPDATE["total_bytes += size"]
    ERROR_HANDLE["AllocError::InvalidParam"]
end

ADD --> RAW_PTR
INIT --> RAW_PTR
INSERT --> ERROR_HANDLE
INSERT --> TOTAL_UPDATE
NONNULL --> INSERT
RAW_PTR --> SLICE
SLICE --> NONNULL

The implementation converts raw memory addresses into safe slice references before inserting them into the TLSF data structure. Both init() and add_memory() follow the same pattern but with different error handling strategies.

Sources: src/tlsf.rs(L31 - L51) 

ByteAllocator Implementation

The ByteAllocator trait implementation provides the core allocation and deallocation functionality with automatic memory usage tracking.

Allocation Flow

flowchart TD
subgraph subGraph2["Memory Statistics"]
    TOTAL_BYTES["total_bytes()"]
    USED_BYTES["used_bytes()"]
    AVAIL_BYTES["available_bytes() = total - used"]
    DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"]
    ALLOC_REQ["alloc(layout: Layout)"]
end
subgraph subGraph0["Allocation Process"]
    USED_BYTES["used_bytes()"]
    NO_MEMORY["Err(AllocError::NoMemory)"]
    RETURN_PTR["Ok(NonNull)"]
    subgraph subGraph1["Deallocation Process"]
        TOTAL_BYTES["total_bytes()"]
        DEALLOC_REQ["dealloc(pos: NonNull, layout: Layout)"]
        TLSF_DEALLOC["inner.deallocate(pos, layout.align())"]
        DECREASE_USED["used_bytes -= layout.size()"]
        ALLOC_REQ["alloc(layout: Layout)"]
        TLSF_ALLOC["inner.allocate(layout)"]
        PTR_CHECK["Result, ()>"]
        UPDATE_USED["used_bytes += layout.size()"]
    end
end

ALLOC_REQ --> TLSF_ALLOC
DEALLOC_REQ --> TLSF_DEALLOC
PTR_CHECK --> NO_MEMORY
PTR_CHECK --> UPDATE_USED
TLSF_ALLOC --> PTR_CHECK
TLSF_DEALLOC --> DECREASE_USED
UPDATE_USED --> RETURN_PTR

Sources: src/tlsf.rs(L54 - L77) 

Memory Usage Tracking

The allocator maintains accurate memory usage statistics by tracking:

  • total_bytes: Total memory pool size across all added memory regions
  • used_bytes: Currently allocated memory, updated on each alloc/dealloc operation
  • available_bytes: Computed as total_bytes - used_bytes

This tracking is performed at the wrapper level rather than delegating to the underlying rlsf implementation.

Sources: src/tlsf.rs(L66 - L77) 

Performance Characteristics

The TLSF allocator provides several performance advantages:

Time Complexity

OperationComplexityDescription
AllocationO(1)Constant time regardless of pool size
DeallocationO(1)Constant time with immediate coalescing
Memory AdditionO(1)Adding new memory pools
StatisticsO(1)Memory usage queries

Memory Efficiency

The two-level segregated fit approach minimizes fragmentation by:

  • Maintaining precise size classes for small allocations
  • Using power-of-2 ranges for larger allocations
  • Immediate coalescing of adjacent free blocks
  • Good-fit allocation strategy to reduce waste

Sources: src/tlsf.rs(L1 - L3) 

Configuration and Limitations

Size Limitations

The current configuration imposes several limits:

  • Maximum single allocation: Limited by FLLEN = 28, supporting up to 2^28 bytes (256MB)
  • Maximum total pool size: 8GB theoretical limit (32 * 2^28)
  • Minimum allocation: Determined by underlying rlsf implementation constraints

Integration Requirements

The allocator requires:

  • Feature gate tlsf to be enabled in Cargo.toml
  • External dependency on rlsf crate version 0.2
  • Unsafe operations for memory pool management
  • 'static lifetime for the internal TLSF structure

Sources: src/tlsf.rs(L15)  src/tlsf.rs(L32 - L51) 

Usage and Configuration

Relevant source files

This document provides a comprehensive guide for integrating and configuring the allocator crate in your projects. It covers dependency management, feature selection, basic usage patterns, and integration with Rust's standard library allocator API.

For detailed information about specific allocator implementations and their algorithms, see Allocator Implementations. For testing and performance evaluation, see Testing and Benchmarks.

Adding the Dependency

The allocator crate is designed as a feature-gated library that allows you to compile only the allocator implementations you need. Add it to your Cargo.toml dependencies section:

[dependencies]
allocator = "0.1.1"

By default, this enables only the bitmap page allocator with 256MB memory space support through the page-alloc-256m feature as defined in Cargo.toml(L13) 

Feature Configuration

The crate provides a flexible feature system that allows selective compilation of allocator types and memory space configurations.

Feature Selection Overview

flowchart TD
subgraph subGraph4["API Integration"]
    ALLOC_API["allocator_api"]
end
subgraph subGraph3["Page Size Features"]
    SIZE_1T["page-alloc-1t"]
    SIZE_64G["page-alloc-64g"]
    SIZE_4G["page-alloc-4g"]
    SIZE_256M["page-alloc-256m"]
end
subgraph subGraph2["Allocator Features"]
    BITMAP["bitmap"]
    TLSF["tlsf"]
    SLAB["slab"]
    BUDDY["buddy"]
end
subgraph subGraph1["Preset Features"]
    DEFAULT["default"]
    FULL["full"]
end
subgraph subGraph0["Feature Categories"]
    PRESET["Preset Configurations"]
    ALLOCATORS["Allocator Types"]
    PAGE_SIZES["Page Allocator Sizes"]
    INTEGRATION["Standard Library Integration"]
end

ALLOCATORS --> BITMAP
ALLOCATORS --> BUDDY
ALLOCATORS --> SLAB
ALLOCATORS --> TLSF
DEFAULT --> SIZE_256M
FULL --> ALLOC_API
FULL --> BITMAP
FULL --> BUDDY
FULL --> SIZE_256M
FULL --> SLAB
FULL --> TLSF
INTEGRATION --> ALLOC_API
PAGE_SIZES --> SIZE_1T
PAGE_SIZES --> SIZE_256M
PAGE_SIZES --> SIZE_4G
PAGE_SIZES --> SIZE_64G
PRESET --> DEFAULT
PRESET --> FULL

Sources: Cargo.toml(L12 - L27) 

Available Features

FeaturePurposeDependencies
defaultEnables bitmap page allocator with 256MB supportpage-alloc-256m
fullEnables all allocator types and API integrationAll features
bitmapBitmap-based page allocatorbitmap-allocator v0.2
tlsfTwo-Level Segregated Fit byte allocatorrlsf v0.2
slabSlab-based byte allocatorslab_allocator v0.3.1
buddyBuddy system byte allocatorbuddy_system_allocator v0.10
allocator_apiStandard library allocator trait integrationNone
page-alloc-1t1TB memory space supportNone
page-alloc-64g64GB memory space supportNone
page-alloc-4g4GB memory space supportNone
page-alloc-256m256MB memory space supportNone

Common Configuration Patterns

Minimal Configuration (Default):

[dependencies]
allocator = "0.1.1"

All Allocators Enabled:

[dependencies]
allocator = { version = "0.1.1", features = ["full"] }

Specific Allocator Selection:

[dependencies]
allocator = { version = "0.1.1", features = ["buddy", "tlsf", "allocator_api"] }

Large Memory Space Configuration:

[dependencies]
allocator = { version = "0.1.1", features = ["bitmap", "page-alloc-1t"] }

Sources: Cargo.toml(L12 - L34) 

Basic Usage Patterns

The crate provides a trait-based interface that allows uniform interaction with different allocator implementations. The core traits are defined in src/lib.rs and enable polymorphic usage of allocators.

Allocator Type Selection

flowchart TD
subgraph subGraph3["Traits Used"]
    BYTE_TRAIT["ByteAllocator trait"]
    PAGE_TRAIT["PageAllocator trait"]
    BASE_TRAIT["BaseAllocator trait"]
end
subgraph subGraph2["Page Allocators"]
    BITMAP_PAGE["BitmapPageAllocator"]
end
subgraph subGraph1["Byte Allocators"]
    BUDDY_BYTE["BuddyByteAllocator"]
    SLAB_BYTE["SlabByteAllocator"]
    TLSF_BYTE["TlsfByteAllocator"]
end
subgraph subGraph0["Allocation Granularity"]
    BYTE_LEVEL["Byte-level Allocation"]
    PAGE_LEVEL["Page-level Allocation"]
    ID_LEVEL["ID-based Allocation"]
end
USE_CASE["Use Case Requirements"]

BITMAP_PAGE --> PAGE_TRAIT
BUDDY_BYTE --> BYTE_TRAIT
BYTE_LEVEL --> BUDDY_BYTE
BYTE_LEVEL --> SLAB_BYTE
BYTE_LEVEL --> TLSF_BYTE
BYTE_TRAIT --> BASE_TRAIT
PAGE_LEVEL --> BITMAP_PAGE
PAGE_TRAIT --> BASE_TRAIT
SLAB_BYTE --> BYTE_TRAIT
TLSF_BYTE --> BYTE_TRAIT
USE_CASE --> BYTE_LEVEL
USE_CASE --> ID_LEVEL
USE_CASE --> PAGE_LEVEL

Sources: src/lib.rs src/bitmap.rs src/buddy.rs src/slab.rs src/tlsf.rs

Integration Patterns

The allocators follow a consistent initialization and usage pattern:

  1. Initialization: Call init() with memory region parameters
  2. Memory Addition: Use add_memory() to register additional memory regions
  3. Allocation: Use type-specific allocation methods (alloc, alloc_pages, etc.)
  4. Deallocation: Use corresponding deallocation methods
  5. Statistics: Query memory usage through built-in statistics methods

Error Handling

All allocator operations return AllocResult<T>, which is a type alias for Result<T, AllocError>. The error types include:

  • InvalidParam: Invalid parameters passed to allocator functions
  • MemoryOverlap: Attempt to add overlapping memory regions
  • NoMemory: Insufficient memory for allocation request
  • NotAllocated: Attempt to deallocate memory that wasn't allocated

Sources: src/lib.rs

Standard Library Integration

When the allocator_api feature is enabled, the crate provides integration with Rust's standard library allocator interface through the AllocatorRc wrapper.

AllocatorRc Usage

flowchart TD
subgraph subGraph3["Standard Library"]
    CORE_ALLOC["core::alloc::Allocator"]
end
subgraph subGraph2["Core Allocators"]
    BYTE_ALLOC["ByteAllocator Implementation"]
end
subgraph subGraph1["Allocator Integration"]
    ALLOC_RC["AllocatorRc"]
    RC_REFCELL["Rc>"]
end
subgraph subGraph0["Application Code"]
    APP["User Application"]
    STD_COLLECTIONS["std::collections"]
end

ALLOC_RC --> CORE_ALLOC
ALLOC_RC --> RC_REFCELL
APP --> STD_COLLECTIONS
RC_REFCELL --> BYTE_ALLOC
STD_COLLECTIONS --> ALLOC_RC

The AllocatorRc provides:

  • Reference-counted access to byte allocators
  • Implementation of core::alloc::Allocator trait
  • Thread-safe usage through RefCell interior mutability
  • Compatibility with standard library collections

Sources: src/lib.rs Cargo.toml(L22) 

Memory Space Configuration

The page allocator supports different maximum memory space configurations through mutually exclusive features:

FeatureMaximum MemoryUse Case
page-alloc-256m256 MBSmall embedded systems, testing
page-alloc-4g4 GBDesktop applications, moderate memory
page-alloc-64g64 GBServer applications, large memory
page-alloc-1t1 TBHigh-performance computing, very large memory

These features control the underlying bitmap allocator size and should be selected based on your application's maximum memory requirements. Using a smaller configuration reduces memory overhead of the allocator itself.

Sources: Cargo.toml(L24 - L27) 

Development Dependencies

When developing with or testing the allocator crate, additional dependencies are available:

  • Testing: The full feature is automatically enabled for development builds
  • Benchmarking: criterion with HTML reports for performance testing
  • Randomization: rand with small_rng feature for test data generation

These are configured as dev-dependencies and don't affect production builds.

Sources: Cargo.toml(L36 - L43) 

Testing and Benchmarks

Relevant source files

This document covers the testing infrastructure and performance evaluation framework for the allocator crate. The testing system validates correctness and measures performance across all supported allocator implementations through comprehensive integration tests and benchmarks.

For information about specific allocator implementations being tested, see Allocator Implementations. For details on individual test scenarios, see Integration Tests and Performance Benchmarks.

Testing Infrastructure Overview

The allocator crate provides a comprehensive testing framework that validates both correctness and performance across all allocator implementations. The testing infrastructure consists of integration tests and performance benchmarks that exercise the allocators through realistic usage patterns.

Testing Architecture

flowchart TD
subgraph subGraph3["Test Execution"]
    RUN_TEST["run_test()"]
    MEMORY_POOL["MemoryPool"]
    CRITERION["criterion_benchmark()"]
end
subgraph subGraph2["Allocator Implementations Under Test"]
    SYS_ALLOC["std::alloc::System"]
    BUDDY_RC["AllocatorRc"]
    SLAB_RC["AllocatorRc"]
    TLSF_RC["AllocatorRc"]
end
subgraph subGraph1["Test Functions"]
    TEST_VEC["test_vec()"]
    TEST_VEC2["test_vec2()"]
    TEST_BTREE["test_btree_map()"]
    TEST_ALIGN["test_alignment()"]
    BENCH_VEC_PUSH["vec_push()"]
    BENCH_VEC_RAND["vec_rand_free()"]
    BENCH_BTREE["btree_map()"]
end
subgraph subGraph0["Test Infrastructure"]
    INTEGRATION["tests/allocator.rs"]
    BENCHMARKS["benches/collections.rs"]
    UTILS["benches/utils/mod.rs"]
end

BENCHMARKS --> BENCH_BTREE
BENCHMARKS --> BENCH_VEC_PUSH
BENCHMARKS --> BENCH_VEC_RAND
BENCHMARKS --> CRITERION
BENCHMARKS --> MEMORY_POOL
INTEGRATION --> TEST_ALIGN
INTEGRATION --> TEST_BTREE
INTEGRATION --> TEST_VEC
INTEGRATION --> TEST_VEC2
RUN_TEST --> MEMORY_POOL
TEST_VEC --> BUDDY_RC
TEST_VEC --> SLAB_RC
TEST_VEC --> SYS_ALLOC
TEST_VEC --> TLSF_RC
TEST_VEC2 --> BUDDY_RC
TEST_VEC2 --> SLAB_RC
TEST_VEC2 --> SYS_ALLOC
TEST_VEC2 --> TLSF_RC
UTILS --> MEMORY_POOL

The testing architecture separates integration testing from performance benchmarking while using consistent test scenarios across both. Each allocator implementation is tested through the same set of operations to ensure behavioral consistency and enable performance comparison.

Sources: tests/allocator.rs(L1 - L144)  benches/collections.rs(L1 - L102) 

Test Environment Configuration

The testing framework uses a standardized memory pool configuration to ensure consistent and reproducible results across all test runs.

ConfigurationValuePurpose
POOL_SIZE128 MBStandard memory pool size for custom allocators
Memory Layout4096-byte alignedEnsures proper page alignment for testing
Random Seed0xdead_beefDeterministic randomization for benchmarks
Sample Size10 iterationsBenchmark measurement accuracy
flowchart TD
subgraph subGraph1["Allocator Initialization"]
    BUDDY_INIT["BuddyByteAllocator::new()"]
    SLAB_INIT["SlabByteAllocator::new()"]
    TLSF_INIT["TlsfByteAllocator::new()"]
    RC_WRAPPER["AllocatorRc::new(allocator, pool)"]
end
subgraph subGraph0["Memory Pool Setup"]
    ALLOC_LAYOUT["Layout::from_size_align(POOL_SIZE, 4096)"]
    ALLOC_PTR["std::alloc::alloc_zeroed()"]
    SLICE_POOL["core::slice::from_raw_parts_mut()"]
end

ALLOC_LAYOUT --> ALLOC_PTR
ALLOC_PTR --> SLICE_POOL
BUDDY_INIT --> RC_WRAPPER
SLAB_INIT --> RC_WRAPPER
SLICE_POOL --> RC_WRAPPER
TLSF_INIT --> RC_WRAPPER

Sources: tests/allocator.rs(L11)  tests/allocator.rs(L87 - L95)  benches/collections.rs(L16) 

Test Scenarios and Methodology

The testing framework implements several key scenarios that exercise different allocation patterns and stress test the allocators under various conditions.

Core Test Scenarios

Test FunctionPurposeParametersStress Pattern
test_vecVector growth and sorting3M elementsSequential allocation
test_vec2Fragmentation testing30K/7.5K blocksRandom deallocation
test_btree_mapComplex data structures50K operationsMixed insert/remove
test_alignmentAlignment validation50 iterationsVariable size/alignment

Vector Operation Testing

The test_vec function validates basic allocation behavior through vector operations that require continuous memory growth and reallocation.

sequenceDiagram
    participant test_vec as "test_vec()"
    participant Vecu32 as "Vec<u32>"
    participant Allocator as "Allocator"

    test_vec ->> Vecu32: "Vec::with_capacity_in(n, alloc)"
    loop "n iterations"
        test_vec ->> Vecu32: "push(random_u32)"
        Vecu32 ->> Allocator: "allocate/reallocate"
    end
    test_vec ->> Vecu32: "sort()"
    test_vec ->> test_vec: "verify sorted order"
    test_vec ->> Vecu32: "drop()"
    Vecu32 ->> Allocator: "deallocate"

Sources: tests/allocator.rs(L13 - L22)  benches/collections.rs(L18 - L24) 

Fragmentation Testing

The test_vec2 and vec_rand_free functions specifically target memory fragmentation by creating many small allocations and then randomly deallocating them.

flowchart TD
subgraph subGraph1["Test Parameters"]
    PARAMS1["25K blocks × 64 bytes"]
    PARAMS2["7.5K blocks × 520 bytes"]
    RNG["SmallRng::seed_from_u64(0xdead_beef)"]
end
subgraph subGraph0["Fragmentation Test Flow"]
    ALLOCATE["Allocate n blocks of blk_size"]
    SHUFFLE["Shuffle deallocation order"]
    DEALLOCATE["Randomly deallocate blocks"]
    VERIFY["Verify allocator state"]
end

ALLOCATE --> SHUFFLE
DEALLOCATE --> VERIFY
PARAMS1 --> ALLOCATE
PARAMS2 --> ALLOCATE
RNG --> SHUFFLE
SHUFFLE --> DEALLOCATE

Sources: tests/allocator.rs(L24 - L40)  benches/collections.rs(L26 - L44) 

Alignment and Layout Testing

The test_alignment function validates that allocators properly handle various size and alignment requirements, which is critical for compatibility with different data types.

Sources: tests/allocator.rs(L63 - L85) 

Performance Measurement Framework

The benchmark suite uses the criterion crate to provide statistically rigorous performance measurements across all allocator implementations.

Benchmark Execution Flow

flowchart TD
subgraph subGraph2["Benchmark Functions"]
    VEC_PUSH["vec_push_3M"]
    VEC_RAND1["vec_rand_free_25K_64"]
    VEC_RAND2["vec_rand_free_7500_520"]
    BTREE["btree_map_50K"]
end
subgraph subGraph1["Allocator Testing Matrix"]
    SYSTEM["bench(c, 'system', std::alloc::System)"]
    TLSF["bench(c, 'tlsf', AllocatorRc)"]
    SLAB["bench(c, 'slab', AllocatorRc)"]
    BUDDY["bench(c, 'buddy', AllocatorRc)"]
end
subgraph subGraph0["Benchmark Setup"]
    POOL_INIT["MemoryPool::new(POOL_SIZE)"]
    CRITERION["Criterion::default()"]
    BENCH_GROUP["benchmark_group(alloc_name)"]
end

BENCH_GROUP --> VEC_PUSH
CRITERION --> BENCH_GROUP
POOL_INIT --> BUDDY
POOL_INIT --> SLAB
POOL_INIT --> SYSTEM
POOL_INIT --> TLSF
SYSTEM --> BTREE
SYSTEM --> VEC_PUSH
SYSTEM --> VEC_RAND1
SYSTEM --> VEC_RAND2

Sources: benches/collections.rs(L63 - L98) 

Benchmark Scenarios

The benchmark suite measures performance across scenarios that represent common real-world allocation patterns:

BenchmarkOperation CountBlock SizePattern Type
vec_push_3M3,000,000 pushesVariableSequential growth
vec_rand_free_25K_6425,000 blocks64 bytesRandom fragmentation
vec_rand_free_7500_5207,500 blocks520 bytesLarge block fragmentation
btree_map_50K50,000 operationsVariableComplex data structure

Sources: benches/collections.rs(L65 - L77) 

Running Tests and Interpreting Results

Integration Test Execution

Integration tests are executed using standard Rust testing tools and validate allocator correctness across all implementations:

# Run all integration tests
cargo test

# Run tests for specific allocator
cargo test buddy_alloc
cargo test slab_alloc
cargo test tlsf_alloc
cargo test system_alloc

Each test function follows the pattern {allocator}_alloc() and executes the complete test suite against that allocator implementation.

Sources: tests/allocator.rs(L97 - L143) 

Benchmark Execution

Performance benchmarks are run using the criterion framework, which provides detailed statistical analysis:

# Run all benchmarks
cargo bench

# Generate HTML report
cargo bench -- --output-format html

The benchmark results compare allocator performance against the system allocator baseline, providing insights into:

  • Allocation speed for different patterns
  • Memory fragmentation behavior
  • Overhead of custom allocator implementations

Sources: benches/collections.rs(L100 - L101) 

Integration Tests

Relevant source files

Purpose and Scope

The integration tests validate that all allocator implementations in the crate correctly implement the standard library's Allocator trait and can handle real-world allocation patterns. These tests ensure that each allocator (BuddyByteAllocator, SlabByteAllocator, TlsfByteAllocator) functions correctly when used with standard Rust collections and various allocation scenarios.

For information about the specific allocator implementations being tested, see the allocator-specific documentation in section 3. For performance benchmarking information, see Performance Benchmarks.

Test Architecture

The integration test suite follows a systematic approach where each allocator is tested against the same set of scenarios to ensure consistent behavior across implementations.

Test Execution Flow

flowchart TD
subgraph subGraph1["Allocator Types"]
    SYS["std::alloc::System"]
    BUDDY["AllocatorRc"]
    SLAB["AllocatorRc"]
    TLSF["AllocatorRc"]
end
subgraph subGraph0["Test Scenarios"]
    VEC["test_vec() - Vector Operations"]
    VEC2["test_vec2() - Fragmentation Testing"]
    BTREE["test_btree_map() - BTreeMap Operations"]
    ALIGN["test_alignment() - Alignment Testing"]
end
START["Test Execution Start"]
POOL["run_test() - Memory Pool Setup"]
ALLOC_INIT["Allocator Initialization"]
SCENARIOS["Test Scenario Execution"]
CLEANUP["Memory Pool Cleanup"]

ALIGN --> CLEANUP
ALLOC_INIT --> BUDDY
ALLOC_INIT --> SCENARIOS
ALLOC_INIT --> SLAB
ALLOC_INIT --> SYS
ALLOC_INIT --> TLSF
BTREE --> CLEANUP
POOL --> ALLOC_INIT
SCENARIOS --> ALIGN
SCENARIOS --> BTREE
SCENARIOS --> VEC
SCENARIOS --> VEC2
START --> POOL
VEC --> CLEANUP
VEC2 --> CLEANUP

Sources: tests/allocator.rs(L87 - L95)  tests/allocator.rs(L97 - L143) 

Test Scenarios

Each test scenario validates different aspects of allocator behavior and memory management patterns.

Vector Operations Test (test_vec)

This test validates basic allocation and collection growth by creating a vector with a specified capacity, filling it with random data, and performing operations that require memory reallocation.

Test ParameterValue
Vector Size3,000,000 elements
Element Typeu32
OperationsPush, sort, comparison

The test exercises the allocator's ability to handle large contiguous allocations and validates that the allocated memory remains accessible and correctly manages data integrity.

Sources: tests/allocator.rs(L13 - L22) 

Fragmentation Testing (test_vec2)

This test creates intentional memory fragmentation by allocating numerous small vectors and then deallocating them in random order, simulating real-world fragmentation scenarios.

Test ConfigurationSmall BlocksLarge Blocks
Block Count30,0007,500
Block Size64 elements520 elements
Element Typeu64u64

The test validates the allocator's ability to handle fragmented memory and efficiently reuse deallocated blocks.

Sources: tests/allocator.rs(L24 - L40) 

BTreeMap Operations Test (test_btree_map)

This test exercises complex allocation patterns through BTreeMap operations, including node allocation, key-value storage, and tree rebalancing operations.

Test ParameterValue
Operations50,000
Deletion Probability20% (1 in 5)
Key TypeVec(dynamic strings)
Value Typeu32

The test validates that allocators can handle the complex allocation patterns required by balanced tree data structures.

Sources: tests/allocator.rs(L42 - L61) 

Alignment Testing (test_alignment)

This test validates that allocators correctly handle various alignment requirements and allocation sizes, which is critical for systems programming.

flowchart TD
RANDOM["Random Number Generator"]
SIZE_GEN["Size Generation2^0 to 2^15 * 1.0-2.0"]
ALIGN_GEN["Alignment Generation2^0 to 2^7"]
LAYOUT["Layout::from_size_align()"]
ALLOC_OP["Allocate/Deallocate2:1 ratio"]

ALIGN_GEN --> LAYOUT
LAYOUT --> ALLOC_OP
RANDOM --> ALIGN_GEN
RANDOM --> ALLOC_OP
RANDOM --> SIZE_GEN
SIZE_GEN --> LAYOUT
Test ParameterRange
Allocation Size1 to 131,072 bytes
Alignment1 to 128 bytes
Operations50
Allocation Probability66%

Sources: tests/allocator.rs(L63 - L85) 

Allocator Test Matrix

The test suite validates four different allocator implementations using identical test scenarios to ensure behavioral consistency.

Allocator Implementation Matrix

flowchart TD
subgraph subGraph1["Allocator Implementations"]
    SYS["system_alloc()std::alloc::System"]
    BUDDY["buddy_alloc()AllocatorRc + BuddyByteAllocator"]
    SLAB["slab_alloc()AllocatorRc + SlabByteAllocator"]
    TLSF["tlsf_alloc()AllocatorRc + TlsfByteAllocator"]
end
subgraph subGraph0["Test Functions"]
    TV["test_vec(3_000_000)"]
    TV2A["test_vec2(30_000, 64)"]
    TV2B["test_vec2(7_500, 520)"]
    TBM["test_btree_map(50_000)"]
    TA["test_alignment(50)"]
end

TA --> BUDDY
TA --> SLAB
TA --> SYS
TA --> TLSF
TBM --> BUDDY
TBM --> SLAB
TBM --> SYS
TBM --> TLSF
TV --> BUDDY
TV --> SLAB
TV --> SYS
TV --> TLSF
TV2A --> BUDDY
TV2A --> SLAB
TV2A --> SYS
TV2A --> TLSF
TV2B --> BUDDY
TV2B --> SLAB
TV2B --> SYS
TV2B --> TLSF

System Allocator Test (system_alloc)

Tests the standard library allocator as a baseline reference implementation. This test validates that the test scenarios themselves are correct and provides performance/behavior comparison data.

Custom Allocator Tests

Each custom allocator test follows the same pattern:

  1. Initialize the allocator using AllocatorRc::new()
  2. Execute all test scenarios with the wrapped allocator
  3. Validate correct behavior across all scenarios

The AllocatorRc wrapper enables the custom byte allocators to implement the standard Allocator trait required by Rust collections.

Sources: tests/allocator.rs(L97 - L143) 

Memory Pool Management

The test infrastructure uses a dedicated memory pool to isolate allocator testing from system memory management.

Memory Pool Setup Process

sequenceDiagram
    participant TestFunction as "Test Function"
    participant run_test as "run_test()"
    participant stdalloc as "std::alloc"
    participant MemoryPool as "Memory Pool"
    participant CustomAllocator as "Custom Allocator"

    TestFunction ->> run_test: Call run_test(closure)
    run_test ->> stdalloc: alloc_zeroed(128MB, 4KB align)
    stdalloc -->> run_test: Raw memory pointer
    run_test ->> MemoryPool: Create slice from raw memory
    run_test ->> CustomAllocator: Initialize with pool slice
    run_test ->> TestFunction: Execute test closure(pool)
    TestFunction -->> run_test: Test completion
    run_test ->> stdalloc: dealloc(ptr, layout)
Pool ParameterValue
Size128 MB (POOL_SIZE)
Alignment4096 bytes
InitializationZero-filled
LayoutContiguous memory block

The run_test helper function manages the complete lifecycle of the memory pool, ensuring proper cleanup regardless of test outcomes.

Sources: tests/allocator.rs(L11)  tests/allocator.rs(L87 - L95) 

Running and Interpreting Tests

Execution Commands

# Run all integration tests
cargo test

# Run tests for a specific allocator
cargo test buddy_alloc
cargo test slab_alloc
cargo test tlsf_alloc
cargo test system_alloc

# Run with specific features enabled
cargo test --features "buddy,slab,tlsf,allocator_api"

Test Requirements

The integration tests require several unstable Rust features:

FeaturePurpose
btreemap_allocEnable BTreeMap with custom allocators
allocator_apiEnable Allocator trait implementation

These features are enabled via the feature gates at the top of the test file.

Expected Behavior

All tests should pass for all allocator implementations, demonstrating that:

  • Each allocator correctly implements the Allocator trait
  • Memory allocation and deallocation work correctly
  • Collections can be used with custom allocators
  • Alignment requirements are respected
  • Memory fragmentation is handled appropriately
  • Large allocations are supported

Test failures indicate potential issues with allocator implementation, memory corruption, or alignment violations.

Sources: tests/allocator.rs(L1 - L2)  tests/allocator.rs(L8) 

Performance Benchmarks

Relevant source files

This document covers the performance benchmark suite for the allocator crate, which provides standardized testing methodology to compare the performance characteristics of different memory allocation implementations. The benchmarks evaluate allocation patterns commonly found in real-world applications and measure relative performance against the system allocator baseline.

For information about the individual allocator implementations being tested, see Allocator Implementations. For details about the integration test suite, see Integration Tests.

Benchmark Architecture

The benchmark infrastructure is built using the Criterion benchmarking framework and provides a controlled testing environment for evaluating allocator performance across different workload patterns.

Benchmark Framework Overview

flowchart TD
subgraph subGraph3["Allocator Instances"]
    SYS_ALLOC["std::alloc::System"]
    TLSF_ALLOC["AllocatorRc"]
    SLAB_ALLOC["AllocatorRc"]
    BUDDY_ALLOC["AllocatorRc"]
end
subgraph subGraph2["Memory Pool Management"]
    POOL_NEW["MemoryPool::new()"]
    POOL_SLICE["MemoryPool::as_slice()"]
    ALLOC_ZEROED["std::alloc::alloc_zeroed()"]
    LAYOUT["Layout::from_size_align(size, 4096)"]
end
subgraph subGraph1["Test Infrastructure"]
    POOL["MemoryPool::new(POOL_SIZE)"]
    BENCH_FN["bench(c, alloc_name, alloc)"]
    CRITERION["Criterion::benchmark_group()"]
end
subgraph subGraph0["Benchmark Entry Point"]
    MAIN["criterion_main!(benches)"]
    GROUP["criterion_group!(benches, criterion_benchmark)"]
end

BENCH_FN --> BUDDY_ALLOC
BENCH_FN --> CRITERION
BENCH_FN --> POOL
BENCH_FN --> SLAB_ALLOC
BENCH_FN --> SYS_ALLOC
BENCH_FN --> TLSF_ALLOC
GROUP --> BENCH_FN
MAIN --> GROUP
POOL --> POOL_NEW
POOL --> POOL_SLICE
POOL_NEW --> ALLOC_ZEROED
POOL_NEW --> LAYOUT
POOL_SLICE --> BUDDY_ALLOC
POOL_SLICE --> SLAB_ALLOC
POOL_SLICE --> TLSF_ALLOC

The benchmark system creates a 128MB memory pool (POOL_SIZE = 1024 * 1024 * 128) for testing custom allocators, while the system allocator operates directly on system memory. Each allocator is wrapped in AllocatorRc to provide the standard library Allocator trait interface.

Sources: benches/collections.rs(L16)  benches/collections.rs(L80 - L98)  benches/utils/mod.rs(L9 - L18) 

Test Scenario Implementation

flowchart TD
subgraph subGraph3["btree_map Implementation"]
    BT_NEW["BTreeMap::new_in(alloc.clone())"]
    BT_RNG["SmallRng::seed_from_u64(0xdead_beef)"]
    BT_INSERT["m.insert(key, value)"]
    BT_REMOVE["m.pop_first()"]
    BT_CLEAR["m.clear()"]
end
subgraph subGraph2["vec_rand_free Implementation"]
    VR_ALLOC["Vec::with_capacity_in(blk_size, alloc)"]
    VR_RNG["SmallRng::seed_from_u64(0xdead_beef)"]
    VR_SHUFFLE["index.shuffle(&mut rng)"]
    VR_FREE["v[i] = Vec::new_in(alloc)"]
end
subgraph subGraph1["vec_push Implementation"]
    VP_NEW["Vec::new_in(alloc.clone())"]
    VP_LOOP["for _ in 0..n"]
    VP_PUSH["v.push(0xdead_beef)"]
    VP_DROP["drop(v)"]
end
subgraph subGraph0["Benchmark Functions"]
    VEC_PUSH["vec_push(n, alloc)"]
    VEC_RAND["vec_rand_free(n, blk_size, alloc)"]
    BTREE_MAP["btree_map(n, alloc)"]
end

BTREE_MAP --> BT_NEW
BTREE_MAP --> BT_RNG
BT_INSERT --> BT_CLEAR
BT_REMOVE --> BT_CLEAR
BT_RNG --> BT_INSERT
BT_RNG --> BT_REMOVE
VEC_PUSH --> VP_NEW
VEC_RAND --> VR_ALLOC
VEC_RAND --> VR_RNG
VP_LOOP --> VP_PUSH
VP_NEW --> VP_LOOP
VP_PUSH --> VP_DROP
VR_RNG --> VR_SHUFFLE
VR_SHUFFLE --> VR_FREE

Each benchmark function implements a specific allocation pattern designed to stress different aspects of allocator performance. The functions use deterministic random seeds to ensure reproducible results across benchmark runs.

Sources: benches/collections.rs(L18 - L24)  benches/collections.rs(L26 - L44)  benches/collections.rs(L46 - L61) 

Test Scenarios

The benchmark suite includes four distinct test scenarios that evaluate different aspects of allocator performance:

BenchmarkFunctionParametersPurpose
vec_push_3Mvec_pushn=3,000,000Sequential allocation stress test
vec_rand_free_25K_64vec_rand_freen=25,000, blk_size=64Small block fragmentation test
vec_rand_free_7500_520vec_rand_freen=7,500, blk_size=520Large block fragmentation test
btree_map_50Kbtree_mapn=50,000Mixed allocation/deallocation pattern

Sequential Allocation Test

The vec_push_3M benchmark evaluates pure allocation performance by pushing 3 million u32 values into a vector. This test measures:

  • Allocation throughput for growing data structures
  • Memory reallocation efficiency during vector growth
  • Allocator overhead for sequential memory requests

Fragmentation Tests

The vec_rand_free tests evaluate allocator behavior under fragmentation stress:

Small Block Test (vec_rand_free_25K_64):

  • Allocates 25,000 blocks of 64 bytes each
  • Randomly deallocates blocks using shuffled indices
  • Tests small allocation efficiency and fragmentation handling

Large Block Test (vec_rand_free_7500_520):

  • Allocates 7,500 blocks of 520 bytes each
  • Randomly deallocates blocks using shuffled indices
  • Tests allocator behavior with larger allocation sizes

Mixed Workload Test

The btree_map_50K benchmark simulates realistic application behavior:

  • Performs 50,000 operations on a BTreeMap
  • 20% probability of removal operations (rng.next_u32() % 5 == 0)
  • 80% probability of insertion operations
  • Uses string keys with dynamic allocation
  • Tests allocator performance under mixed allocation patterns

Sources: benches/collections.rs(L65 - L77)  benches/collections.rs(L26 - L44)  benches/collections.rs(L46 - L61) 

Allocator Testing Matrix

The benchmark system tests four different allocators to provide comprehensive performance comparison:

Allocator Configuration

flowchart TD
subgraph subGraph3["Allocator Initialization"]
    TLSF_NEW["TlsfByteAllocator::new()"]
    SLAB_NEW["SlabByteAllocator::new()"]
    BUDDY_NEW["BuddyByteAllocator::new()"]
end
subgraph subGraph2["Memory Pool"]
    SYS["std::alloc::System"]
    POOL_MEM["MemoryPool (128MB)"]
    POOL_SLICE["&mut [u8] slice"]
end
subgraph subGraph1["Custom Allocators"]
    TLSF_RC["AllocatorRc"]
    SLAB_RC["AllocatorRc"]
    BUDDY_RC["AllocatorRc"]
end
subgraph subGraph0["System Allocator"]
    SYS["std::alloc::System"]
    SYS_DESC["Direct system memoryBaseline reference"]
    POOL_MEM["MemoryPool (128MB)"]
end

BUDDY_NEW --> BUDDY_RC
POOL_MEM --> POOL_SLICE
POOL_SLICE --> BUDDY_RC
POOL_SLICE --> SLAB_RC
POOL_SLICE --> TLSF_RC
SLAB_NEW --> SLAB_RC
SYS --> SYS_DESC
TLSF_NEW --> TLSF_RC

Each custom allocator is initialized with the same 128MB memory pool to ensure fair comparison. The AllocatorRc wrapper provides reference counting and the standard library Allocator trait implementation.

Sources: benches/collections.rs(L82 - L97)  benches/utils/mod.rs(L10 - L14) 

Memory Pool Utility

The MemoryPool utility provides controlled memory management for benchmark testing:

MemoryPool Implementation

The MemoryPool struct manages a fixed-size memory region for allocator testing:

// Memory pool allocation with 4KB alignment
let layout = Layout::from_size_align(size, 4096).unwrap();
let ptr = NonNull::new(unsafe { std::alloc::alloc_zeroed(layout) }).unwrap();

Key characteristics:

  • Size: 128MB (POOL_SIZE = 1024 * 1024 * 128)
  • Alignment: 4KB page alignment for optimal performance
  • Initialization: Zero-filled memory using alloc_zeroed
  • Lifetime: Automatic cleanup through Drop implementation

The pool provides a mutable slice interface (as_slice()) that allocators use for their internal memory management, ensuring all custom allocators operate within the same controlled memory environment.

Sources: benches/utils/mod.rs(L9 - L18)  benches/utils/mod.rs(L21 - L25)  benches/collections.rs(L16) 

Running Benchmarks

The benchmark suite uses the Criterion framework for statistical analysis and result reporting. To execute the benchmarks:

cargo bench --features full

The --features full flag enables all allocator implementations for comprehensive testing. The benchmark configuration includes:

  • Sample size: 10 iterations per test for statistical significance
  • Measurement: Wall clock time for complete test scenarios
  • Output: Statistical analysis including mean, standard deviation, and confidence intervals

Individual allocator benchmarks can be run using Criterion's filtering capability:

cargo bench -- tlsf        # Run only TLSF allocator benchmarks
cargo bench -- vec_push    # Run only vector push benchmarks

Sources: benches/collections.rs(L68)  benches/collections.rs(L100 - L101) 

Benchmark Results Interpretation

The benchmark results provide comparative performance metrics across allocators:

Performance Metrics

Throughput Comparison: Results show relative performance against the system allocator baseline, helping identify which allocators perform best for specific workload patterns.

Fragmentation Behavior: The random free tests reveal how effectively each allocator handles memory fragmentation and reuses freed blocks.

Mixed Workload Performance: The BTreeMap benchmark demonstrates allocator behavior under realistic application usage patterns with mixed allocation and deallocation operations.

Expected Performance Characteristics

  • TLSF: Consistent O(1) allocation/deallocation with good fragmentation resistance
  • Slab: Optimal for fixed-size allocations, may show overhead for variable sizes
  • Buddy: Good general-purpose performance with power-of-two block sizes
  • System: Reference baseline representing operating system allocator performance

Sources: benches/collections.rs(L63 - L78) 

Development and Contributing

Relevant source files

This document provides comprehensive guidance for developers who want to contribute to the allocator crate, including development environment setup, code quality standards, testing procedures, and the CI/CD pipeline. It covers the technical requirements and workflows necessary for maintaining the project's high standards of quality and cross-platform compatibility.

For information about using the allocators in your projects, see Usage and Configuration. For details about the testing infrastructure from a user perspective, see Testing and Benchmarks.

Development Environment Setup

The allocator crate requires a specific development environment to ensure consistency across all contributors and maintain compatibility with the supported target platforms.

Required Toolchain

The project exclusively uses the Rust nightly toolchain with specific components and target platforms. The nightly toolchain is required for no_std compatibility and advanced allocation features.

Required Components:

  • rust-src - Source code for cross-compilation
  • clippy - Linting tool for code quality
  • rustfmt - Code formatting tool

Supported Target Platforms:

  • x86_64-unknown-linux-gnu - Standard Linux development
  • x86_64-unknown-none - Bare metal x86_64
  • riscv64gc-unknown-none-elf - RISC-V bare metal
  • aarch64-unknown-none-softfloat - ARM64 bare metal

Development Workflow

flowchart TD
SETUP["Development Setup"]
TOOLCHAIN["Install Nightly Toolchainrustup toolchain install nightly"]
COMPONENTS["Add Componentsrust-src, clippy, rustfmt"]
TARGETS["Add Target Platformsx86_64, RISC-V, ARM64"]
DEVELOP["Development Phase"]
FORMAT["Code Formattingcargo fmt --all -- --check"]
LINT["Lintingcargo clippy --all-features"]
BUILD["Cross-Platform Buildcargo build --target TARGET"]
TEST["Unit Testingcargo test --nocapture"]
SUBMIT["Contribution Submission"]
PR["Create Pull Request"]
CI["Automated CI Pipeline"]
REVIEW["Code Review Process"]

BUILD --> SUBMIT
COMPONENTS --> DEVELOP
DEVELOP --> BUILD
DEVELOP --> FORMAT
DEVELOP --> LINT
DEVELOP --> TEST
FORMAT --> SUBMIT
LINT --> SUBMIT
SETUP --> COMPONENTS
SETUP --> TARGETS
SETUP --> TOOLCHAIN
SUBMIT --> CI
SUBMIT --> PR
SUBMIT --> REVIEW
TARGETS --> DEVELOP
TEST --> SUBMIT
TOOLCHAIN --> DEVELOP

Sources: .github/workflows/ci.yml(L11 - L19) 

CI/CD Pipeline Overview

The project uses GitHub Actions for continuous integration and deployment, ensuring code quality and cross-platform compatibility for every contribution.

CI Pipeline Architecture

flowchart TD
subgraph subGraph3["Documentation Job"]
    DOC_BUILD["Documentation Buildcargo doc --no-deps --all-features"]
    DOC_DEPLOY["Deploy to GitHub Pagesgh-pages branch"]
end
subgraph subGraph2["Quality Checks"]
    FORMAT_CHECK["Format Checkcargo fmt --all -- --check"]
    CLIPPY_CHECK["Clippy Lintingcargo clippy --all-features"]
    BUILD_CHECK["Build Verificationcargo build --all-features"]
    UNIT_TEST["Unit Testsx86_64-unknown-linux-gnu only"]
end
subgraph subGraph1["CI Job Matrix"]
    CI_JOB["CI JobUbuntu Latest"]
    RUST_NIGHTLY["Rust Toolchainnightly"]
    subgraph subGraph0["Target Matrix"]
        TARGET_LINUX["x86_64-unknown-linux-gnu"]
        TARGET_BARE["x86_64-unknown-none"]
        TARGET_RISCV["riscv64gc-unknown-none-elf"]
        TARGET_ARM["aarch64-unknown-none-softfloat"]
    end
end
TRIGGER["GitHub Eventspush, pull_request"]

CI_JOB --> RUST_NIGHTLY
DOC_BUILD --> DOC_DEPLOY
RUST_NIGHTLY --> TARGET_ARM
RUST_NIGHTLY --> TARGET_BARE
RUST_NIGHTLY --> TARGET_LINUX
RUST_NIGHTLY --> TARGET_RISCV
TARGET_LINUX --> BUILD_CHECK
TARGET_LINUX --> CLIPPY_CHECK
TARGET_LINUX --> FORMAT_CHECK
TARGET_LINUX --> UNIT_TEST
TRIGGER --> CI_JOB
TRIGGER --> DOC_BUILD

Pipeline Configuration Details

The CI pipeline is configured with fail-fast disabled to ensure all target platforms are tested even if one fails. This comprehensive testing approach ensures the allocator implementations work correctly across all supported embedded and hosted environments.

Quality Gate Requirements:

  • All code must pass rustfmt formatting checks
  • All code must pass clippy linting with all-features enabled
  • All target platforms must build successfully
  • Unit tests must pass on the Linux target
  • Documentation must build without broken links or missing docs

Sources: .github/workflows/ci.yml(L5 - L31) 

Code Quality Standards

The project maintains strict code quality standards enforced through automated tooling and CI pipeline checks.

Formatting Standards

All code must adhere to the standard Rust formatting rules enforced by rustfmt. The CI pipeline automatically rejects any code that doesn't meet these formatting requirements.

Formatting Command:

cargo fmt --all -- --check

Linting Requirements

The project uses clippy with all features enabled to catch potential issues and enforce best practices. One specific clippy warning is allowed: clippy::new_without_default, which is common in allocator implementations where Default trait implementation may not be appropriate.

Linting Configuration:

  • Target: All supported platforms
  • Features: --all-features enabled
  • Allowed warnings: clippy::new_without_default

Sources: .github/workflows/ci.yml(L23 - L25) 

Cross-Platform Compatibility

All contributions must build successfully on all supported target platforms. This ensures the allocator implementations work correctly in both hosted and bare-metal environments.

Build Verification Process:

flowchart TD
subgraph subGraph0["Build Targets"]
    LINUX["Linux GNUx86_64-unknown-linux-gnu"]
    BARE_X86["Bare Metal x86x86_64-unknown-none"]
    RISCV["RISC-Vriscv64gc-unknown-none-elf"]
    ARM["ARM64aarch64-unknown-none-softfloat"]
end
SOURCE["Source Code"]
SUCCESS["All Builds Must Pass"]

ARM --> SUCCESS
BARE_X86 --> SUCCESS
LINUX --> SUCCESS
RISCV --> SUCCESS
SOURCE --> ARM
SOURCE --> BARE_X86
SOURCE --> LINUX
SOURCE --> RISCV

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L27) 

Testing Procedures

The project includes comprehensive testing procedures that run automatically in the CI pipeline and can be executed locally during development.

Unit Test Execution

Unit tests are executed only on the x86_64-unknown-linux-gnu target platform, as this provides the most complete testing environment while still ensuring the code works correctly.

Test Command:

cargo test --target x86_64-unknown-linux-gnu -- --nocapture

The --nocapture flag ensures that all test output is visible, which is essential for debugging allocator behavior and performance characteristics.

Local Testing Workflow

For local development, contributors should run the complete test suite that mirrors the CI pipeline:

  1. Format Check: cargo fmt --all -- --check
  2. Lint Check: cargo clippy --all-features
  3. Build All Targets: cargo build --target <TARGET> --all-features
  4. Run Tests: cargo test -- --nocapture

Sources: .github/workflows/ci.yml(L28 - L30) 

Documentation Requirements

The project maintains high documentation standards with automated enforcement through the CI pipeline.

Documentation Standards

All public APIs must include comprehensive documentation. The CI pipeline enforces these standards through specific rustdoc flags that treat documentation issues as errors.

Documentation Requirements:

  • No broken intra-doc links (-D rustdoc::broken_intra_doc_links)
  • No missing documentation (-D missing-docs)
  • All features must be documented (--all-features)

Automated Documentation Deployment

Documentation is automatically built and deployed to GitHub Pages for the default branch. This ensures that the latest documentation is always available to users and contributors.

Documentation Build Process:

flowchart TD
DOC_TRIGGER["Documentation Job Trigger"]
BUILD["Build Documentationcargo doc --no-deps --all-features"]
INDEX["Generate Index PageRedirect to crate docs"]
DEPLOY["Deploy to GitHub Pagesgh-pages branch"]
PAGES["GitHub Pages SitePublic Documentation"]
NOTE["Only deploys fromdefault branch"]

BUILD --> INDEX
DEPLOY --> NOTE
DEPLOY --> PAGES
DOC_TRIGGER --> BUILD
INDEX --> DEPLOY

The documentation build includes a custom index page that automatically redirects to the main crate documentation, providing a seamless user experience.

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L55) 

Contributing Guidelines

Contribution Workflow

  1. Fork and Clone: Fork the repository and clone your fork locally
  2. Setup Environment: Install nightly toolchain with required components and targets
  3. Create Branch: Create a feature branch for your changes
  4. Develop: Make changes following the code quality standards
  5. Test Locally: Run the complete test suite locally
  6. Submit PR: Create a pull request with a clear description of changes
  7. CI Validation: Ensure all CI checks pass
  8. Code Review: Participate in the code review process

Submission Requirements

All contributions must:

  • Pass automated formatting and linting checks
  • Build successfully on all supported platforms
  • Include appropriate unit tests where applicable
  • Maintain or improve documentation coverage
  • Follow the existing code style and architecture patterns

Project Maintenance

The project uses specific ignore patterns to maintain a clean repository structure:

Ignored Files and Directories:

  • /target - Build artifacts
  • /.vscode - IDE-specific configuration
  • .DS_Store - macOS system files
  • Cargo.lock - Lock file (library project)

Sources: .gitignore(L1 - L4) 

Overview

Relevant source files

Purpose and Scope

The axmm_crates repository provides foundational memory management primitives designed for operating system kernels, hypervisors, and embedded systems. This workspace contains two complementary crates that implement type-safe address handling and high-level memory mapping operations for no-std environments.

This document covers the overall architecture and purpose of the axmm_crates workspace. For detailed information about the layered design principles, see System Architecture. For implementation details of the individual crates, see memory_addr Crate and memory_set Crate.

Repository Structure

The axmm_crates workspace is organized as a Rust workspace containing two primary crates that work together to provide comprehensive memory management capabilities:

CratePurposeKey Components
memory_addrAddress type foundationsPhysAddr,VirtAddr,MemoryAddrtrait, alignment utilities
memory_setMemory mapping managementMemorySet,MemoryArea,MappingBackendtrait
flowchart TD
subgraph subGraph2["axmm_crates Workspace"]
    WORKSPACE["Cargo.tomlWorkspace Root"]
    subgraph subGraph1["Management Layer"]
        MS["memory_set/"]
        MS_CARGO["memory_set/Cargo.toml"]
        MS_LIB["memory_set/src/lib.rs"]
    end
    subgraph subGraph0["Foundation Layer"]
        MA["memory_addr/"]
        MA_CARGO["memory_addr/Cargo.toml"]
        MA_LIB["memory_addr/src/lib.rs"]
    end
end

MA --> MA_CARGO
MA --> MA_LIB
MS --> MS_CARGO
MS --> MS_LIB
MS_LIB --> MA_LIB
WORKSPACE --> MA
WORKSPACE --> MS

Sources: Cargo.toml(L1 - L20)  README.md(L1 - L8) 

Core Crates Overview

memory_addr: Address Type Foundations

The memory_addr crate provides type-safe abstractions for physical and virtual memory addresses. It implements the MemoryAddr trait that serves as the foundation for all address operations throughout the system.

Key Features:

  • Type-safe PhysAddr and VirtAddr wrappers preventing address type confusion
  • Alignment operations for page boundaries and custom alignments
  • Address range types with containment and overlap detection
  • Page iteration utilities for memory traversal

memory_set: Memory Mapping Management

The memory_set crate builds upon memory_addr to provide high-level memory mapping operations similar to mmap() functionality. It manages collections of memory areas and provides abstraction over different mapping backends.

Key Features:

  • MemorySet collections for managing multiple memory areas
  • MemoryArea structures representing individual mapped regions
  • MappingBackend trait for hardware abstraction
  • Area splitting, merging, and overlap resolution
flowchart TD
subgraph subGraph0["Code Entity Relationships"]
    MEMORY_ADDR_TRAIT["MemoryAddr trait"]
    PHYS_ADDR["PhysAddr struct"]
    VIRT_ADDR["VirtAddr struct"]
    ADDR_RANGE["AddrRange types"]
    MEMORY_AREA["MemoryArea struct"]
    MAPPING_BACKEND["MappingBackend trait"]
    MEMORY_SET["MemorySet struct"]
    MAPPING_ERROR["MappingError enum"]
end

ADDR_RANGE --> MEMORY_AREA
MAPPING_BACKEND --> MAPPING_ERROR
MAPPING_BACKEND --> MEMORY_AREA
MEMORY_ADDR_TRAIT --> ADDR_RANGE
MEMORY_ADDR_TRAIT --> PHYS_ADDR
MEMORY_ADDR_TRAIT --> VIRT_ADDR
MEMORY_AREA --> MEMORY_SET
VIRT_ADDR --> MEMORY_AREA

Sources: README.md(L5 - L6) 

Target Environment and Use Cases

The axmm_crates workspace is specifically designed for systems programming contexts where memory management is critical:

Primary Target: ArceOS Integration

This workspace serves as a foundational component for the ArceOS operating system project, providing reusable memory management primitives that can be integrated into kernel subsystems.

Supported Environments

EnvironmentCompatibilityNotes
no-stdFull supportCore design requirement
Rust 1.70.0+RequiredMinimum supported version
OS KernelsPrimary use caseDirect hardware memory management
HypervisorsSupportedGuest memory management
Embedded SystemsSupportedResource-constrained environments

License and Availability

The workspace is released under a triple license scheme allowing flexibility for different use cases:

  • GPL-3.0-or-later for open source projects
  • Apache-2.0 for permissive licensing needs
  • MulanPSL-2.0 for compliance with Chinese software standards
flowchart TD
subgraph subGraph1["axmm_crates Components"]
    MEMORY_ADDR_LIB["memory_addr crate"]
    MEMORY_SET_LIB["memory_set crate"]
end
subgraph subGraph0["Integration Context"]
    ARCEOS["ArceOS Operating System"]
    KERNEL_SUBSYS["Kernel Subsystems"]
    MM_LAYER["Memory Management Layer"]
    CONSUMERS["Consumer Applications"]
    HYPERVISOR["Hypervisor Systems"]
    EMBEDDED["Embedded Systems"]
end

ARCEOS --> MM_LAYER
CONSUMERS --> MEMORY_SET_LIB
EMBEDDED --> MEMORY_ADDR_LIB
HYPERVISOR --> MEMORY_SET_LIB
KERNEL_SUBSYS --> MEMORY_SET_LIB
MEMORY_SET_LIB --> MEMORY_ADDR_LIB
MM_LAYER --> MEMORY_SET_LIB

Sources: Cargo.toml(L9 - L19)  README.md(L1 - L3) 

The modular design allows consumers to use only the components they need - systems requiring only basic address handling can depend solely on memory_addr, while those needing full memory mapping capabilities can use both crates together.

System Architecture

Relevant source files

Purpose and Scope

This document explains the layered architecture design of the axmm_crates workspace, which provides foundational memory management capabilities for operating system development. The architecture consists of two primary crates: memory_addr serving as the foundational address handling layer, and memory_set providing higher-level memory mapping management built upon those foundations.

For detailed documentation of individual components, see memory_addr Crate and memory_set Crate. For practical implementation guidance, see Development Guide.

Workspace Structure and Dependencies

The axmm_crates workspace implements a clean two-layer architecture where each layer provides specific abstractions for memory management operations.

Workspace Architecture

flowchart TD
subgraph external["External Context"]
    arceos["ArceOS Operating System"]
    consumers["Consumer Systems"]
end
subgraph workspace["axmm_crates Workspace"]
    subgraph management["Management Layer"]
        memory_set["memory_set crate"]
    end
    subgraph foundation["Foundation Layer"]
        memory_addr["memory_addr crate"]
    end
end

arceos --> memory_addr
arceos --> memory_set
memory_addr --> consumers
memory_addr --> memory_set
memory_set --> consumers

Sources: Cargo.toml(L1 - L20)  README.md(L1 - L8) 

Core Component Dependencies

flowchart TD
subgraph set_crate["memory_set Components"]
    MappingBackend["MappingBackend trait"]
    MemoryArea["MemoryArea struct"]
    MemorySet["MemorySet struct"]
    MappingError["MappingError enum"]
end
subgraph addr_crate["memory_addr Components"]
    MemoryAddr["MemoryAddr trait"]
    PhysAddr["PhysAddr struct"]
    VirtAddr["VirtAddr struct"]
    AddrRange["AddrRange types"]
    PageIter["PageIter types"]
end

AddrRange --> MemoryArea
AddrRange --> PageIter
MappingBackend --> MappingError
MappingBackend --> MemoryArea
MemoryAddr --> PhysAddr
MemoryAddr --> VirtAddr
MemoryArea --> MemorySet
PhysAddr --> AddrRange
VirtAddr --> AddrRange
VirtAddr --> MappingBackend
VirtAddr --> MemoryArea

Sources: memory_addr/README.md(L12 - L29)  memory_set/README.md(L17 - L18)  memory_set/README.md(L49 - L89) 

Design Principles

Type Safety and Address Separation

The architecture enforces strict type safety by separating physical and virtual addresses through distinct types. The MemoryAddr trait provides common operations while PhysAddr and VirtAddr prevent mixing address types inappropriately.

PrincipleImplementationBenefit
Type SafetySeparatePhysAddrandVirtAddrtypesPrevents address type confusion
Trait AbstractionMemoryAddrtrait for common operationsConsistent interface across address types
Range OperationsAddrRangetypes for memory regionsSafe range manipulation and validation

Backend Abstraction Layer

The MappingBackend trait provides hardware-agnostic memory mapping operations, enabling the same high-level logic to work across different memory management units and page table implementations.

flowchart TD
subgraph implementations["Backend Implementations"]
    MockBackend["MockBackend"]
    HardwareBackend["Hardware-specific backends"]
    TestBackend["Testing backends"]
end
subgraph abstraction["Abstraction Layer"]
    MappingBackend_trait["MappingBackend trait"]
end
subgraph highlevel["High-Level Operations"]
    MemorySet_map["MemorySet::map()"]
    MemorySet_unmap["MemorySet::unmap()"]
    MemorySet_protect["MemorySet::protect()"]
end

MappingBackend_trait --> HardwareBackend
MappingBackend_trait --> MockBackend
MappingBackend_trait --> TestBackend
MemorySet_map --> MappingBackend_trait
MemorySet_protect --> MappingBackend_trait
MemorySet_unmap --> MappingBackend_trait

Sources: memory_set/README.md(L49 - L89) 

No-std Compatibility

The entire architecture is designed for no-std environments, making it suitable for kernel development and embedded systems where standard library features are unavailable.

Integration Patterns

ArceOS Integration

The crates are specifically designed as foundational components for the ArceOS operating system project, providing reusable memory management primitives that can be composed into larger memory management systems.

Consumer Usage Patterns

sequenceDiagram
    participant OSKernelHypervisor as "OS Kernel/Hypervisor"
    participant MemorySet as "MemorySet"
    participant MemoryArea as "MemoryArea"
    participant MappingBackendimpl as "MappingBackend impl"
    participant VirtAddrPhysAddr as "VirtAddr/PhysAddr"

    OSKernelHypervisor ->> VirtAddrPhysAddr: "Create typed addresses"
    OSKernelHypervisor ->> MemoryArea: "new(start, size, flags, backend)"
    MemoryArea ->> VirtAddrPhysAddr: "Validate address ranges"
    OSKernelHypervisor ->> MemorySet: "map(area, page_table, overwrite)"
    MemorySet ->> MemoryArea: "map_area(page_table)"
    MemoryArea ->> MappingBackendimpl: "map(start, size, flags, pt)"
    MappingBackendimpl -->> MemoryArea: "Success/Error"
    MemoryArea -->> MemorySet: "Result"
    MemorySet -->> OSKernelHypervisor: "Final result"

Sources: memory_set/README.md(L34 - L46) 

Key Architecture Benefits

BenefitImplementation DetailCode Location
ModularitySeparate crates for different abstraction levelsCargo.toml4-7
ReusabilityTrait-based interfaces enable multiple implementationsmemory_set/README.md49-89
SafetyType-safe address handling prevents common errorsmemory_addr/README.md14-21
TestabilityMock backends enable comprehensive testingmemory_set/README.md22-30
Hardware IndependenceBackend abstraction supports different MMU architecturesmemory_set/README.md49-89

The layered architecture ensures that low-level address manipulation primitives remain independent and reusable, while higher-level memory mapping operations build upon these foundations to provide comprehensive memory management capabilities.

Sources: Cargo.toml(L1 - L20)  README.md(L1 - L8)  memory_addr/README.md(L1 - L30)  memory_set/README.md(L1 - L91) 

memory_addr Crate

Relevant source files

Purpose and Scope

The memory_addr crate provides foundational abstractions for physical and virtual memory addresses in the ArceOS ecosystem. It serves as the lowest-level component in the memory management stack, offering type-safe address representations, alignment utilities, and iteration capabilities over memory regions.

This crate focuses specifically on address manipulation primitives and does not handle memory mapping or higher-level memory management operations. For memory mapping functionality and management of memory regions, see memory_set Crate.

The crate is designed for no-std environments and provides essential building blocks for operating system kernels, hypervisors, and embedded systems that need precise control over memory addresses.

Sources: memory_addr/Cargo.toml(L1 - L17)  memory_addr/README.md(L1 - L30)  memory_addr/src/lib.rs(L1 - L11) 

Core Address Type System

The crate implements a type-safe address system built around a common trait with specialized implementations for different address spaces.

flowchart TD
subgraph Constants["Constants"]
    PAGE_SIZE_4K["PAGE_SIZE_4Kconst = 0x1000"]
end
subgraph subGraph2["Iteration Support"]
    PageIter["PageIter<SIZE, A>generic iterator"]
    PageIter4K["PageIter4K<A>type alias"]
end
subgraph subGraph1["Range Types"]
    AddrRange["AddrRange<A>generic range"]
    PhysAddrRange["PhysAddrRangetype alias"]
    VirtAddrRange["VirtAddrRangetype alias"]
end
subgraph subGraph0["Core Types"]
    MemoryAddr["MemoryAddrtrait"]
    PhysAddr["PhysAddrstruct"]
    VirtAddr["VirtAddrstruct"]
end

AddrRange --> PhysAddrRange
AddrRange --> VirtAddrRange
MemoryAddr --> PageIter
MemoryAddr --> PhysAddr
MemoryAddr --> VirtAddr
PAGE_SIZE_4K --> PageIter4K
PageIter --> PageIter4K
PhysAddr --> PhysAddrRange
VirtAddr --> VirtAddrRange

Sources: memory_addr/src/lib.rs(L8 - L16) 

Address Operations and Utilities

The crate provides a comprehensive set of alignment and arithmetic operations that work consistently across all address types.

flowchart TD
subgraph subGraph2["Address Methods"]
    addr_align_down["addr.align_down(align)"]
    addr_align_up_4k["addr.align_up_4k()"]
    addr_align_offset_4k["addr.align_offset_4k()"]
    addr_is_aligned_4k["addr.is_aligned_4k()"]
end
subgraph subGraph1["4K-Specific Functions"]
    align_down_4k["align_down_4k(addr)"]
    align_up_4k["align_up_4k(addr)"]
    align_offset_4k["align_offset_4k(addr)"]
    is_aligned_4k["is_aligned_4k(addr)"]
end
subgraph subGraph0["Generic Functions"]
    align_down["align_down(addr, align)"]
    align_up["align_up(addr, align)"]
    align_offset["align_offset(addr, align)"]
    is_aligned["is_aligned(addr, align)"]
end

align_down --> addr_align_down
align_down --> align_down_4k
align_offset --> align_offset_4k
align_offset_4k --> addr_align_offset_4k
align_up --> align_up_4k
align_up_4k --> addr_align_up_4k
is_aligned --> is_aligned_4k
is_aligned_4k --> addr_is_aligned_4k

Sources: memory_addr/src/lib.rs(L18 - L76) 

Function Categories and Implementation

The crate organizes its functionality into several key categories that build upon each other to provide a complete address manipulation toolkit.

CategoryFunctionsPurpose
Generic Alignmentalign_down,align_up,align_offset,is_alignedPower-of-two alignment operations for any alignment value
4K Page Helpersalign_down_4k,align_up_4k,align_offset_4k,is_aligned_4kSpecialized functions for common 4K page operations
Type WrappersPhysAddr,VirtAddrType-safe address representations with built-in methods
Range OperationsAddrRange,PhysAddrRange,VirtAddrRangeAddress range abstractions for memory region management
Iteration SupportPageIter,PageIter4KIterator types for traversing memory in page-sized chunks

Sources: memory_addr/src/lib.rs(L18 - L76) 

Core Constants and Type Aliases

The crate defines essential constants and type aliases that standardize common memory management patterns:

// From memory_addr/src/lib.rs:12-16
pub const PAGE_SIZE_4K: usize = 0x1000;
pub type PageIter4K<A> = PageIter<PAGE_SIZE_4K, A>;

The PAGE_SIZE_4K constant (4096 bytes) is fundamental to the system's page-oriented design, while PageIter4K provides a convenient type alias for the most common page iteration scenario.

Sources: memory_addr/src/lib.rs(L12 - L16) 

Module Structure and Exports

The crate follows a clean modular design with focused responsibilities:

flowchart TD
subgraph subGraph1["Public API"]
    MemoryAddr_export["MemoryAddr"]
    PhysAddr_export["PhysAddr"]
    VirtAddr_export["VirtAddr"]
    PageIter_export["PageIter"]
    AddrRange_export["AddrRange"]
    PhysAddrRange_export["PhysAddrRange"]
    VirtAddrRange_export["VirtAddrRange"]
end
subgraph memory_addr/src/["memory_addr/src/"]
    lib["lib.rsMain module & re-exports"]
    addr_mod["addr.rsMemoryAddr trait & types"]
    iter_mod["iter.rsPageIter implementation"]
    range_mod["range.rsAddrRange types"]
end

addr_mod --> MemoryAddr_export
addr_mod --> PhysAddr_export
addr_mod --> VirtAddr_export
iter_mod --> PageIter_export
lib --> AddrRange_export
lib --> MemoryAddr_export
lib --> PageIter_export
lib --> PhysAddrRange_export
lib --> PhysAddr_export
lib --> VirtAddrRange_export
lib --> VirtAddr_export
range_mod --> AddrRange_export
range_mod --> PhysAddrRange_export
range_mod --> VirtAddrRange_export

Sources: memory_addr/src/lib.rs(L4 - L10) 

Alignment Implementation Details

The alignment functions implement efficient bit manipulation operations that require power-of-two alignments:

  • align_down(addr, align): Uses addr & !(align - 1) to mask off lower bits
  • align_up(addr, align): Uses (addr + align - 1) & !(align - 1) to round up
  • align_offset(addr, align): Uses addr & (align - 1) to get remainder
  • is_aligned(addr, align): Checks if align_offset(addr, align) == 0

These operations are marked const fn, enabling compile-time computation when used with constant values.

Sources: memory_addr/src/lib.rs(L24 - L51) 

Integration with ArceOS Ecosystem

The memory_addr crate serves as the foundation layer in the ArceOS memory management stack. It provides the type-safe primitives that higher-level components build upon, particularly the memory_set crate which uses these address types for memory region management.

The crate's no_std compatibility and const function implementations make it suitable for both kernel and embedded environments where compile-time optimization and resource constraints are critical.

Sources: memory_addr/Cargo.toml(L5)  memory_addr/src/lib.rs(L1) 

Address Types and Operations

Relevant source files

This document covers the foundational address handling types and operations provided by the memory_addr crate. These types provide type-safe abstractions for physical and virtual memory addresses, along with comprehensive alignment and arithmetic operations. For information about address ranges and iteration, see Address Ranges and Page Iteration.

Core Architecture

The address system is built around the MemoryAddr trait, which provides a common interface for all memory address types. The crate includes two concrete address types (PhysAddr and VirtAddr) and utilities for creating custom address types.

Address Type Hierarchy

classDiagram
note for MemoryAddr "Auto-implemented for types that are:Copy + From + Into + Ord"
class MemoryAddr {
    <<trait>>
    
    +align_down(align) Self
    +align_up(align) Self
    +align_offset(align) usize
    +is_aligned(align) bool
    +align_down_4k() Self
    +align_up_4k() Self
    +offset(offset) Self
    +add(rhs) Self
    +sub(rhs) Self
    +checked_add(rhs) Option~Self~
    +checked_sub(rhs) Option~Self~
}

class PhysAddr {
    -usize: usize
    +from_usize(addr) PhysAddr
    +as_usize() usize
}

class VirtAddr {
    -usize: usize
    +from_usize(addr) VirtAddr
    +as_usize() usize
    +from_ptr_of~T~(*const T) VirtAddr
    +as_ptr() *const u8
    +as_ptr_of~T~() *const T
    +as_mut_ptr() *mut u8
}

MemoryAddr  ..|>  PhysAddr : implements
MemoryAddr  ..|>  VirtAddr : implements

Sources: memory_addr/src/addr.rs(L12 - L263)  memory_addr/src/addr.rs(L450 - L461) 

Address Types

PhysAddr

PhysAddr represents a physical memory address. It is a transparent wrapper around usize that provides type safety to prevent mixing physical and virtual addresses.

MethodDescriptionReturn Type
from_usize(addr)Creates from raw address valuePhysAddr
as_usize()Converts to raw address valueusize
pa!(addr)Convenience macro for creationPhysAddr

VirtAddr

VirtAddr represents a virtual memory address with additional pointer conversion capabilities beyond the base MemoryAddr trait.

MethodDescriptionReturn Type
from_ptr_of(ptr)Creates from typed pointerVirtAddr
from_mut_ptr_of(ptr)Creates from mutable pointerVirtAddr
as_ptr()Converts to byte pointer*const u8
as_ptr_of()Converts to typed pointer*const T
as_mut_ptr()Converts to mutable byte pointer*mut u8
as_mut_ptr_of()Converts to mutable typed pointer*mut T
va!(addr)Convenience macro for creationVirtAddr

Sources: memory_addr/src/addr.rs(L450 - L461)  memory_addr/src/addr.rs(L463 - L500)  memory_addr/src/addr.rs(L502 - L516) 

Address Type Operations Flow

flowchart TD
subgraph Results["Results"]
    new_addr["New Address"]
    offset_val["usize offset"]
    ptr_result["*const/*mut T"]
    bool_result["bool"]
end
subgraph Operations["Operations"]
    align["Alignment Operations"]
    arith["Arithmetic Operations"]
    convert["Conversion Operations"]
end
subgraph subGraph1["Address Types"]
    PhysAddr["PhysAddr"]
    VirtAddr["VirtAddr"]
end
subgraph Creation["Creation"]
    usize["usize value"]
    ptr["*const/*mut T"]
    macro_pa["pa!(0x1000)"]
    macro_va["va!(0x2000)"]
end

PhysAddr --> align
PhysAddr --> arith
VirtAddr --> align
VirtAddr --> arith
VirtAddr --> convert
align --> bool_result
align --> new_addr
align --> offset_val
arith --> new_addr
arith --> offset_val
convert --> ptr_result
macro_pa --> PhysAddr
macro_va --> VirtAddr
ptr --> VirtAddr
usize --> PhysAddr
usize --> VirtAddr

Sources: memory_addr/src/addr.rs(L450 - L500)  memory_addr/src/addr.rs(L502 - L516) 

Alignment Operations

The MemoryAddr trait provides comprehensive alignment functionality for working with page boundaries and custom alignments.

Core Alignment Methods

MethodDescriptionExample Usage
align_down(align)Rounds down to alignment boundaryaddr.align_down(0x1000)
align_up(align)Rounds up to alignment boundaryaddr.align_up(0x1000)
align_offset(align)Returns offset within alignmentaddr.align_offset(0x1000)
is_aligned(align)Checks if address is alignedaddr.is_aligned(0x1000)

4K Page Alignment Shortcuts

MethodDescriptionEquivalent To
align_down_4k()Aligns down to 4K boundaryalign_down(4096)
align_up_4k()Aligns up to 4K boundaryalign_up(4096)
align_offset_4k()Offset within 4K pagealign_offset(4096)
is_aligned_4k()Checks 4K alignmentis_aligned(4096)

Alignment Operation Examples

flowchart TD
subgraph align_up_4k()["align_up_4k()"]
    result4["0x3000"]
    result5["0x3000"]
    result6["0x4000"]
end
subgraph align_down_4k()["align_down_4k()"]
    result1["0x2000"]
    result2["0x3000"]
    result3["0x3000"]
end
subgraph subGraph0["Input Addresses"]
    addr1["0x2fff"]
    addr2["0x3000"]
    addr3["0x3001"]
end

addr1 --> result1
addr1 --> result4
addr2 --> result2
addr2 --> result5
addr3 --> result3
addr3 --> result6

Sources: memory_addr/src/addr.rs(L27 - L94) 

Arithmetic Operations

The trait provides extensive arithmetic operations with multiple overflow handling strategies.

Basic Arithmetic

MethodDescriptionOverflow Behavior
offset(isize)Signed offset additionPanics
add(usize)Unsigned additionPanics
sub(usize)Unsigned subtractionPanics
sub_addr(Self)Address differencePanics

Wrapping Arithmetic

MethodDescriptionOverflow Behavior
wrapping_offset(isize)Signed offset additionWraps around
wrapping_add(usize)Unsigned additionWraps around
wrapping_sub(usize)Unsigned subtractionWraps around
wrapping_sub_addr(Self)Address differenceWraps around

Checked Arithmetic

MethodDescriptionReturn Type
checked_add(usize)Safe additionOption
checked_sub(usize)Safe subtractionOption
checked_sub_addr(Self)Safe address differenceOption

Overflowing Arithmetic

MethodDescriptionReturn Type
overflowing_add(usize)Addition with overflow flag(Self, bool)
overflowing_sub(usize)Subtraction with overflow flag(Self, bool)
overflowing_sub_addr(Self)Address difference with overflow flag(usize, bool)

Arithmetic Operations Decision Tree

flowchart TD
start["Need Address Arithmetic?"]
signed["Signed Offset?"]
overflow["Overflow Handling?"]
offset_op["offset(isize)wrapping_offset(isize)"]
panic_ops["add(usize)sub(usize)sub_addr(Self)"]
wrap_ops["wrapping_add(usize)wrapping_sub(usize)wrapping_sub_addr(Self)"]
check_ops["checked_add(usize)checked_sub(usize)checked_sub_addr(Self)"]
over_ops["overflowing_add(usize)overflowing_sub(usize)overflowing_sub_addr(Self)"]

overflow --> check_ops
overflow --> over_ops
overflow --> panic_ops
overflow --> wrap_ops
signed --> offset_op
signed --> overflow
start --> signed

Sources: memory_addr/src/addr.rs(L99 - L258) 

Address Type Creation

The crate provides macros for creating custom address types with the same capabilities as PhysAddr and VirtAddr.

def_usize_addr! Macro

The def_usize_addr! macro generates new address types with automatic implementations:

  • Basic traits: Copy, Clone, Default, Ord, PartialOrd, Eq, PartialEq
  • Conversion traits: From<usize>, Into<usize>
  • Arithmetic operators: Add<usize>, Sub<usize>, Sub<Self>
  • Utility methods: from_usize(), as_usize()

def_usize_addr_formatter! Macro

The def_usize_addr_formatter! macro generates formatting implementations:

  • Debug trait with custom format string
  • LowerHex and UpperHex traits

Custom Address Type Creation Flow

sequenceDiagram
    participant Developer as "Developer"
    participant def_usize_addr as "def_usize_addr!"
    participant def_usize_addr_formatter as "def_usize_addr_formatter!"
    participant CustomAddressType as "Custom Address Type"

    Developer ->> def_usize_addr: "Define type CustomAddr"
    def_usize_addr ->> CustomAddressType: "Generate struct with usize field"
    def_usize_addr ->> CustomAddressType: "Implement required traits"
    def_usize_addr ->> CustomAddressType: "Add conversion methods"
    def_usize_addr ->> CustomAddressType: "Add arithmetic operators"
    Developer ->> def_usize_addr_formatter: "Set format string"
    def_usize_addr_formatter ->> CustomAddressType: "Implement Debug trait"
    def_usize_addr_formatter ->> CustomAddressType: "Implement Hex traits"
    CustomAddressType ->> Developer: "Ready for use with MemoryAddr"

Sources: memory_addr/src/addr.rs(L302 - L448) 

MemoryAddr Trait Implementation

The MemoryAddr trait is automatically implemented for any type meeting the required bounds, providing a blanket implementation that enables consistent behavior across all address types.

Automatic Implementation Requirements

flowchart TD
subgraph Result["Result"]
    unified["Unified Interface"]
    type_safety["Type Safety"]
    rich_api["Rich API"]
end
subgraph subGraph1["Automatic Implementation"]
    memory_addr["MemoryAddr trait"]
    alignment["Alignment Methods"]
    arithmetic["Arithmetic Methods"]
end
subgraph subGraph0["Required Bounds"]
    copy["Copy"]
    from_usize["From"]
    into_usize["Into"]
    ord["Ord"]
end

alignment --> unified
arithmetic --> type_safety
copy --> memory_addr
from_usize --> memory_addr
into_usize --> memory_addr
memory_addr --> alignment
memory_addr --> arithmetic
ord --> memory_addr
unified --> rich_api

Sources: memory_addr/src/addr.rs(L261 - L263) 

Address Ranges

Relevant source files

This document covers the address range functionality provided by the memory_addr crate, specifically the AddrRange generic type and its associated operations. Address ranges represent contiguous spans of memory addresses with type safety and comprehensive range manipulation operations.

For information about individual address types and arithmetic operations, see Address Types and Operations. For page iteration over ranges, see Page Iteration.

Address Range Structure

The core of the address range system is the AddrRange<A> generic struct, where A implements the MemoryAddr trait. This provides type-safe representation of memory address ranges with inclusive start bounds and exclusive end bounds.

AddrRange Type Hierarchy

flowchart TD
subgraph subGraph2["Address Types"]
    VA["VirtAddr"]
    PA["PhysAddr"]
    UA["usize"]
end
subgraph subGraph1["Concrete Type Aliases"]
    VAR["VirtAddrRangeAddrRange<VirtAddr>"]
    PAR["PhysAddrRangeAddrRange<PhysAddr>"]
end
subgraph subGraph0["Generic Types"]
    AR["AddrRange<A>Generic range type"]
    MA["MemoryAddr traitConstraint for A"]
end

AR --> MA
AR --> UA
PA --> MA
PAR --> AR
PAR --> PA
UA --> MA
VA --> MA
VAR --> AR
VAR --> VA

Sources: memory_addr/src/range.rs(L1 - L28)  memory_addr/src/range.rs(L365 - L368) 

The AddrRange<A> struct contains two public fields:

  • start: A - The lower bound of the range (inclusive)
  • end: A - The upper bound of the range (exclusive)

Type aliases provide convenient names for common address range types:

  • VirtAddrRange = AddrRange<VirtAddr>
  • PhysAddrRange = AddrRange<PhysAddr>

Range Construction Methods

Address ranges can be constructed through several methods, each with different safety and error-handling characteristics.

Construction Method Categories

MethodSafetyOverflow HandlingUse Case
new()Panics on invalid rangeCheckedSafe construction with panic
try_new()ReturnsOptionCheckedFallible construction
new_unchecked()UnsafeUncheckedPerformance-critical paths
from_start_size()Panics on overflowCheckedSize-based construction
try_from_start_size()ReturnsOptionCheckedFallible size-based construction
from_start_size_unchecked()UnsafeUncheckedPerformance-critical size-based

Sources: memory_addr/src/range.rs(L34 - L193) 

Range Construction Flow

flowchart TD
subgraph Results["Results"]
    SUCCESS["AddrRange<A>"]
    PANIC["Panic"]
    NONE["None"]
    ERROR["Error"]
end
subgraph Validation["Validation"]
    CHECK["start <= end?"]
    OVERFLOW["Addition overflow?"]
end
subgraph subGraph1["Construction Methods"]
    NEW["new()"]
    TRYNEW["try_new()"]
    UNSAFE["new_unchecked()"]
    FSS["from_start_size()"]
    TRYFSS["try_from_start_size()"]
    UNSAFEFSS["from_start_size_unchecked()"]
    TRYFROM["TryFrom<Range<T>>"]
end
subgraph subGraph0["Input Types"]
    SE["start, end addresses"]
    SS["start address, size"]
    RNG["Rust Range<T>"]
end
START["Range Construction Request"]

CHECK --> NONE
CHECK --> PANIC
CHECK --> SUCCESS
FSS --> OVERFLOW
NEW --> CHECK
OVERFLOW --> NONE
OVERFLOW --> PANIC
OVERFLOW --> SUCCESS
RNG --> TRYFROM
SE --> NEW
SE --> TRYNEW
SE --> UNSAFE
SS --> FSS
SS --> TRYFSS
SS --> UNSAFEFSS
START --> RNG
START --> SE
START --> SS
TRYFROM --> CHECK
TRYFSS --> OVERFLOW
TRYNEW --> CHECK
UNSAFE --> SUCCESS
UNSAFEFSS --> SUCCESS

Sources: memory_addr/src/range.rs(L57 - L193)  memory_addr/src/range.rs(L307 - L317) 

Range Operations and Relationships

Address ranges support comprehensive operations for checking containment, overlap, and spatial relationships between ranges.

Range Relationship Operations

flowchart TD
subgraph Results["Results"]
    BOOL["bool"]
    USIZE["usize"]
end
subgraph subGraph1["Query Operations"]
    CONTAINS["contains(addr: A)Point containment"]
    CONTAINSRANGE["contains_range(other)Range containment"]
    CONTAINEDIN["contained_in(other)Reverse containment"]
    OVERLAPS["overlaps(other)Range intersection"]
    ISEMPTY["is_empty()Zero-size check"]
    SIZE["size()Range size"]
end
subgraph subGraph0["Range A"]
    RA["AddrRange<A>"]
end

CONTAINEDIN --> BOOL
CONTAINS --> BOOL
CONTAINSRANGE --> BOOL
ISEMPTY --> BOOL
OVERLAPS --> BOOL
RA --> CONTAINEDIN
RA --> CONTAINS
RA --> CONTAINSRANGE
RA --> ISEMPTY
RA --> OVERLAPS
RA --> SIZE
SIZE --> USIZE

Sources: memory_addr/src/range.rs(L228 - L302) 

Key Range Operations

The following operations are available on all AddrRange<A> instances:

  • Point Containment: contains(addr) checks if a single address falls within the range
  • Range Containment: contains_range(other) checks if another range is entirely within this range
  • Containment Check: contained_in(other) checks if this range is entirely within another range
  • Overlap Detection: overlaps(other) checks if two ranges have any intersection
  • Empty Range Check: is_empty() returns true if start equals end
  • Size Calculation: size() returns the number of bytes in the range

Convenience Macros

The crate provides three macros for convenient address range creation with compile-time type inference and runtime validation.

Macro Overview

MacroTarget TypePurpose
addr_range!AddrRange(inferred)Generic range creation
va_range!VirtAddrRangeVirtual address range creation
pa_range!PhysAddrRangePhysical address range creation

Sources: memory_addr/src/range.rs(L391 - L448) 

Macro Usage Pattern

flowchart TD
subgraph subGraph3["Output Types"]
    GENERICRANGE["AddrRange<A> (inferred)"]
    VIRTRANGE["VirtAddrRange"]
    PHYSRANGE["PhysAddrRange"]
end
subgraph subGraph2["Internal Processing"]
    TRYFROM["TryFrom<Range<T>>::try_from()"]
    EXPECT["expect() on Result"]
end
subgraph subGraph1["Macro Processing"]
    ADDRRANGE["addr_range!(0x1000..0x2000)"]
    VARANGE["va_range!(0x1000..0x2000)"]
    PARANGE["pa_range!(0x1000..0x2000)"]
end
subgraph subGraph0["Input Syntax"]
    RANGE["Rust range syntaxstart..end"]
end

ADDRRANGE --> TRYFROM
EXPECT --> GENERICRANGE
EXPECT --> PHYSRANGE
EXPECT --> VIRTRANGE
PARANGE --> TRYFROM
RANGE --> ADDRRANGE
RANGE --> PARANGE
RANGE --> VARANGE
TRYFROM --> EXPECT
VARANGE --> TRYFROM

Sources: memory_addr/src/range.rs(L391 - L448) 

Usage Examples and Patterns

The test suite demonstrates common usage patterns for address ranges, showing both basic operations and complex relationship checking.

Basic Range Operations

// Creating ranges with different methods
let range = VirtAddrRange::new(0x1000.into(), 0x2000.into());
let size_range = VirtAddrRange::from_start_size(0x1000.into(), 0x1000);

// Using macros for convenience
let macro_range = va_range!(0x1000..0x2000);

// Checking basic properties
assert_eq!(range.size(), 0x1000);
assert!(!range.is_empty());
assert!(range.contains(0x1500.into()));

Sources: memory_addr/src/range.rs(L464 - L511) 

Range Relationship Testing

The codebase extensively tests range relationships using various scenarios:

// Containment checking
assert!(range.contains_range(va_range!(0x1001..0x1fff)));
assert!(!range.contains_range(va_range!(0xfff..0x2001)));

// Overlap detection
assert!(range.overlaps(va_range!(0x1800..0x2001)));
assert!(!range.overlaps(va_range!(0x2000..0x2800)));

// Spatial relationships
assert!(range.contained_in(va_range!(0xfff..0x2001)));

Sources: memory_addr/src/range.rs(L483 - L505) 

Error Handling Patterns

The range system provides both panicking and fallible construction methods:

// Panicking construction (for known-valid ranges)
let valid_range = VirtAddrRange::new(start, end);

// Fallible construction (for potentially invalid input)
if let Some(range) = VirtAddrRange::try_new(start, end) {
    // Use the valid range
}

// Size-based construction with overflow handling
let safe_range = VirtAddrRange::try_from_start_size(start, size)?;

Sources: memory_addr/src/range.rs(L58 - L167) 

Page Iteration

Relevant source files

This document covers the page iteration functionality provided by the memory_addr crate, specifically the PageIter struct and its associated methods. Page iteration allows traversing memory addresses in fixed-size page increments, which is essential for memory management operations like mapping, unmapping, and allocating memory in page-aligned chunks.

For information about address types and basic operations, see Address Types and Operations. For address range operations, see Address Ranges.

Overview

The PageIter struct provides a safe, type-safe iterator for traversing memory addresses in page-sized steps. It enforces alignment requirements and validates page sizes at construction time, ensuring that iteration only occurs over properly aligned memory regions.

PageIter Structure Design


Sources: memory_addr/src/iter.rs(L22 - L28)  memory_addr/src/iter.rs(L50 - L65) 

PageIter Structure and Parameters

The PageIter struct is defined with two generic parameters that control its behavior:

ParameterTypePurpose
PAGE_SIZEconst usizeCompile-time constant specifying page size in bytes
AType bound byMemoryAddrAddress type (e.g.,PhysAddr,VirtAddr,usize)

The struct contains two fields:

  • start: Current position in the iteration
  • end: End boundary (exclusive) for the iteration

The PAGE_SIZE parameter must be a power of 2, and both start and end addresses must be aligned to PAGE_SIZE boundaries.

Sources: memory_addr/src/iter.rs(L22 - L28) 

Creating PageIter Instances

PageIter Construction Flow

flowchart TD
Start["PageIter::new(start, end)"]
CheckPowerOf2["PAGE_SIZE.is_power_of_two()"]
ReturnNone["Return None"]
CheckStartAlign["start.is_aligned(PAGE_SIZE)"]
CheckEndAlign["end.is_aligned(PAGE_SIZE)"]
CreateIter["Create PageIter instance"]
ReturnSome["Return Some(PageIter)"]

CheckEndAlign --> CreateIter
CheckEndAlign --> ReturnNone
CheckPowerOf2 --> CheckStartAlign
CheckPowerOf2 --> ReturnNone
CheckStartAlign --> CheckEndAlign
CheckStartAlign --> ReturnNone
CreateIter --> ReturnSome
Start --> CheckPowerOf2

Sources: memory_addr/src/iter.rs(L34 - L47) 

The new method performs several validation checks:

  1. Page Size Validation: Ensures PAGE_SIZE is a power of 2
  2. Start Address Alignment: Verifies start is aligned to PAGE_SIZE
  3. End Address Alignment: Verifies end is aligned to PAGE_SIZE

If any validation fails, the method returns None. Otherwise, it returns Some(PageIter) with the validated parameters.

Iterator Implementation

The PageIter implements the Iterator trait, yielding addresses of type A on each iteration:

Iterator Mechanics

sequenceDiagram
    participant Client as Client
    participant PageIterinstance as "PageIter instance"
    participant MemoryAddrtrait as "MemoryAddr trait"

    Client ->> PageIterinstance: next()
    PageIterinstance ->> PageIterinstance: Check if start < end
    alt start < end
    PageIterinstance ->> PageIterinstance: Store current start as ret
    PageIterinstance ->> MemoryAddrtrait: start.add(PAGE_SIZE)
    MemoryAddrtrait -->> PageIterinstance: New start address
    PageIterinstance ->> PageIterinstance: Update self.start
    PageIterinstance -->> Client: Some(ret)
    else start >= end
    PageIterinstance -->> Client: None
    end

Sources: memory_addr/src/iter.rs(L50 - L65) 

The iteration process:

  1. Boundary Check: Compare current start with end
  2. Value Return: If within bounds, return current start value
  3. Advance: Increment start by PAGE_SIZE using the add method
  4. Termination: Return None when start reaches or exceeds end

Usage Patterns

Basic Page Iteration

The most common usage pattern involves iterating over a memory range with 4KB pages:

// Example from documentation
let mut iter = PageIter::<0x1000, usize>::new(0x1000, 0x3000).unwrap();
assert_eq!(iter.next(), Some(0x1000));
assert_eq!(iter.next(), Some(0x2000));
assert_eq!(iter.next(), None);

Error Handling

PageIter construction can fail if alignment requirements are not met:

// This will return None due to misaligned end address
assert!(PageIter::<0x1000, usize>::new(0x1000, 0x3001).is_none());

Common PageIter Usage Scenarios

flowchart TD
subgraph subGraph0["Common Page Sizes"]
    Page4K["0x1000 (4KB)"]
    Page2M["0x200000 (2MB)"]
    Page1G["0x40000000 (1GB)"]
end
PageIter["PageIter<PAGE_SIZE, A>"]
Mapping["Memory Mapping Operations"]
Allocation["Page Allocation"]
Scanning["Memory Scanning"]
Cleanup["Memory Cleanup"]
MapPages["Map individual pages"]
FindFree["Find free page sequences"]
CheckPerms["Check page permissions"]
UnmapPages["Unmap page ranges"]

Allocation --> FindFree
Cleanup --> UnmapPages
Mapping --> MapPages
PageIter --> Allocation
PageIter --> Cleanup
PageIter --> Mapping
PageIter --> Page1G
PageIter --> Page2M
PageIter --> Page4K
PageIter --> Scanning
Scanning --> CheckPerms

Sources: memory_addr/src/iter.rs(L10 - L21) 

Integration with Address Types

PageIter works with any type implementing MemoryAddr, enabling type-safe iteration over different address spaces:

  • Physical addresses: PageIter<0x1000, PhysAddr>
  • Virtual addresses: PageIter<0x1000, VirtAddr>
  • Raw addresses: PageIter<0x1000, usize>

The iterator leverages the MemoryAddr trait's is_aligned and add methods to ensure correct alignment validation and address arithmetic.

Sources: memory_addr/src/iter.rs(L1)  memory_addr/src/iter.rs(L24)  memory_addr/src/iter.rs(L32) 

memory_set Crate

Relevant source files

Purpose and Scope

The memory_set crate provides data structures and operations for managing memory mappings in operating system kernels and hypervisors. It implements a high-level abstraction layer for memory area management that supports operations similar to Unix mmap, munmap, and mprotect system calls. This crate builds upon the foundational address types from the memory_addr crate to provide a complete memory mapping management solution.

For information about the underlying address types and operations, see memory_addr Crate. For detailed documentation of specific components within this crate, see MemorySet Core, MemoryArea, and MappingBackend.

Core Components Overview

The memory_set crate provides three primary components that work together to manage memory mappings:

Core Types Architecture

flowchart TD
subgraph subGraph2["Generic Parameters"]
    PT["PageTable type"]
    FL["Flags type"]
    AD["Addr type"]
end
subgraph subGraph1["memory_addr Dependencies"]
    VA["VirtAddr"]
    AR["AddrRange<VirtAddr>"]
end
subgraph subGraph0["memory_set Crate"]
    MS["MemorySet<B>"]
    MA["MemoryArea<B>"]
    MB["MappingBackend trait"]
    ME["MappingError enum"]
    MR["MappingResult<T> type"]
end

MA --> AR
MA --> MB
MA --> MR
MA --> VA
MB --> AD
MB --> FL
MB --> PT
MR --> ME
MS --> MA
MS --> MR

Sources: memory_set/src/lib.rs(L13 - L15)  memory_set/src/lib.rs(L17 - L29) 

Component Responsibilities

ComponentPurposeKey Methods
MemorySetCollection manager for memory areasmap(),unmap(),protect(),find_free_area()
MemoryAreaIndividual memory region representationnew(),va_range(),size(),flags()
MappingBackendHardware abstraction traitmap(),unmap(),protect()
MappingErrorError type for mapping operationsInvalidParam,AlreadyExists,BadState

Sources: memory_set/src/lib.rs(L13 - L15)  memory_set/src/lib.rs(L17 - L26) 

Memory Mapping Workflow

The following diagram illustrates how the components interact during typical memory mapping operations:

Memory Mapping Operation Flow


Sources: memory_set/README.md(L34 - L46)  memory_set/README.md(L49 - L89) 

Error Handling and Types

The crate defines a comprehensive error handling system for memory mapping operations:

Error Types

flowchart TD
subgraph subGraph1["Error Variants"]
    IP["InvalidParam"]
    AE["AlreadyExists"]
    BS["BadState"]
end
subgraph subGraph0["Result Types"]
    MR["MappingResult<T>"]
    RES["Result<T, MappingError>"]
end
DESC1["Parameter Validation"]
DESC2["Overlap Detection"]
DESC3["Backend State"]

AE --> DESC2
BS --> DESC3
IP --> DESC1
MR --> RES
RES --> AE
RES --> BS
RES --> IP

Sources: memory_set/src/lib.rs(L17 - L29) 

Error Handling Usage

The error types provide specific information about mapping operation failures:

  • InvalidParam: Used when input parameters like addresses, sizes, or flags are invalid
  • AlreadyExists: Returned when attempting to map a range that overlaps with existing mappings
  • BadState: Indicates the underlying page table or backend is in an inconsistent state

The MappingResult<T> type alias simplifies function signatures throughout the crate by defaulting the success type to unit () for operations that don't return values.

Sources: memory_set/src/lib.rs(L20 - L26)  memory_set/src/lib.rs(L28 - L29) 

Integration with memory_addr

The memory_set crate builds directly on the memory_addr crate's foundational types:

Address Type Integration

flowchart TD
subgraph subGraph2["MappingBackend Generic"]
    MB_ADDR["MappingBackend::Addr"]
    MB_MAP["map(start: Self::Addr, ...)"]
    MB_UNMAP["unmap(start: Self::Addr, ...)"]
end
subgraph subGraph1["memory_set Usage"]
    MA_START["MemoryArea::start: VirtAddr"]
    MA_RANGE["MemoryArea::va_range() → VirtAddrRange"]
    MS_FIND["MemorySet::find_free_area(base: VirtAddr)"]
    MS_UNMAP["MemorySet::unmap(start: VirtAddr, size: usize)"]
end
subgraph subGraph0["memory_addr Types"]
    VA["VirtAddr"]
    PA["PhysAddr"]
    AR["AddrRange<A>"]
    VAR["VirtAddrRange"]
end

MB_ADDR --> MB_MAP
MB_ADDR --> MB_UNMAP
VA --> MA_START
VA --> MB_ADDR
VA --> MS_FIND
VA --> MS_UNMAP
VAR --> MA_RANGE

Sources: memory_set/Cargo.toml(L16 - L17)  memory_set/README.md(L17)  memory_set/README.md(L50) 

The memory_set crate leverages the type safety and address manipulation capabilities provided by memory_addr to ensure that memory mapping operations are performed on properly validated and typed addresses. This integration prevents common errors like mixing physical and virtual addresses or operating on misaligned memory ranges.

MemorySet Core

Relevant source files

This document covers the MemorySet struct, which serves as the central container for managing collections of memory areas in the memory_set crate. The MemorySet provides high-level memory mapping operations similar to Unix mmap/munmap, including sophisticated area management with automatic splitting, merging, and overlap detection.

For details about individual memory areas, see MemoryArea. For information about the backend abstraction layer, see MappingBackend. For practical usage examples, see Usage Examples and Testing.

Core Data Structure

The MemorySet<B: MappingBackend> struct maintains an ordered collection of non-overlapping memory areas using a BTreeMap for efficient range-based operations.

Internal Organization

classDiagram
class MemorySet {
    -BTreeMap~B_Addr_MemoryArea~ areas
    +new() MemorySet
    +len() usize
    +is_empty() bool
    +iter() Iterator
    +overlaps(range) bool
    +find(addr) Option_MemoryArea_
    +find_free_area(hint, size, limit) Option_B_Addr_
    +map(area, page_table, unmap_overlap) MappingResult
    +unmap(start, size, page_table) MappingResult
    +protect(start, size, update_flags, page_table) MappingResult
    +clear(page_table) MappingResult
}

class BTreeMap~B::Addr, MemoryArea~B~~ {
    
    +range(range) Iterator
    +insert(key, value) Option_MemoryArea_
    +remove(key) Option_MemoryArea_
    +retain(predicate) void
}

class MemoryArea {
    
    +start() B_Addr
    +end() B_Addr
    +size() usize
    +va_range() AddrRange
    +map_area(page_table) MappingResult
    +unmap_area(page_table) MappingResult
    +split(at) Option_MemoryArea_
    +shrink_left(new_size, page_table) MappingResult
    +shrink_right(new_size, page_table) MappingResult
}

MemorySet  *--  BTreeMap : contains
BTreeMap  -->  MemoryArea : stores

The BTreeMap key is the start address of each memory area, enabling efficient range queries and maintaining areas in sorted order. This design allows O(log n) lookup operations and efficient iteration over address ranges.

Sources: memory_set/src/set.rs(L1 - L36) 

Memory Mapping Operations

Map Operation

The map method adds new memory areas to the set, with sophisticated overlap handling:

flowchart TD
start["map(area, page_table, unmap_overlap)"]
validate["Validate area range not empty"]
check_overlap["Check overlaps(area.va_range())"]
has_overlap["Overlaps exist?"]
check_unmap_flag["unmap_overlap = true?"]
do_unmap["Call unmap() on overlap range"]
return_error["Return MappingError::AlreadyExists"]
map_area["Call area.map_area(page_table)"]
insert_area["Insert area into BTreeMap"]
success["Return Ok(())"]
end_error["End with error"]
end_success["End successfully"]

check_overlap --> has_overlap
check_unmap_flag --> do_unmap
check_unmap_flag --> return_error
do_unmap --> map_area
has_overlap --> check_unmap_flag
has_overlap --> map_area
insert_area --> success
map_area --> insert_area
return_error --> end_error
start --> validate
success --> end_success
validate --> check_overlap

The overlap detection uses the BTreeMap's range capabilities to efficiently check both preceding and following areas without scanning the entire collection.

Sources: memory_set/src/set.rs(L93 - L122)  memory_set/src/set.rs(L38 - L51) 

Unmap Operation

The unmap operation is the most complex, handling area removal, shrinking, and splitting:

flowchart TD
start["unmap(start, size, page_table)"]
create_range["Create AddrRange from start/size"]
validate_range["Validate range not empty"]
remove_contained["Remove areas fully contained in range"]
check_left["Check area intersecting left boundary"]
left_intersect["Area extends past start?"]
left_middle["Unmapped range in middle?"]
split_left["Split area, shrink left part"]
shrink_left["Shrink area from right"]
check_right["Check area intersecting right boundary"]
right_intersect["Area starts before end?"]
shrink_right["Remove and shrink area from left"]
complete["Operation complete"]
success["Return Ok(())"]

check_left --> left_intersect
check_right --> right_intersect
complete --> success
create_range --> validate_range
left_intersect --> check_right
left_intersect --> left_middle
left_middle --> shrink_left
left_middle --> split_left
remove_contained --> check_left
right_intersect --> complete
right_intersect --> shrink_right
shrink_left --> check_right
shrink_right --> complete
split_left --> check_right
start --> create_range
validate_range --> remove_contained

The unmap operation maintains three key invariants:

  1. All areas remain non-overlapping
  2. Area boundaries align with page boundaries
  3. The BTreeMap remains sorted by start address

Sources: memory_set/src/set.rs(L124 - L184) 

Search and Query Operations

Address Lookup

The find method locates the memory area containing a specific address:

flowchart TD
addr["Target Address"]
range_query["BTreeMap.range(..=addr).last()"]
candidate["Get last area with start ≤ addr"]
contains_check["Check if area.va_range().contains(addr)"]
result["Return Option<&MemoryArea>"]

addr --> range_query
candidate --> contains_check
contains_check --> result
range_query --> candidate

This leverages the BTreeMap's ordered structure to find the candidate area in O(log n) time, then performs a simple range check.

Sources: memory_set/src/set.rs(L53 - L57) 

Free Space Detection

The find_free_area method implements a gap-finding algorithm:

flowchart TD
start["find_free_area(hint, size, limit)"]
init_last_end["last_end = max(hint, limit.start)"]
check_before["Check area before last_end"]
adjust_last_end["Adjust last_end to area.end() if needed"]
iterate["Iterate areas from last_end"]
check_gap["Gap size ≥ requested size?"]
return_address["Return last_end"]
next_area["last_end = area.end(), continue"]
no_more["No more areas?"]
check_final_gap["Final gap ≥ size?"]
return_final["Return last_end"]
return_none["Return None"]
end_success["End with address"]
end_failure["End with None"]

adjust_last_end --> iterate
check_before --> adjust_last_end
check_final_gap --> return_final
check_final_gap --> return_none
check_gap --> next_area
check_gap --> return_address
init_last_end --> check_before
iterate --> check_gap
iterate --> no_more
next_area --> iterate
no_more --> check_final_gap
return_address --> end_success
return_final --> end_success
return_none --> end_failure
start --> init_last_end

The algorithm performs a single linear scan through areas in address order, making it efficient for typical usage patterns.

Sources: memory_set/src/set.rs(L59 - L91) 

Permission Management

Protect Operation

The protect method changes memory permissions within a specified range, involving complex area manipulation:

flowchart TD
start["protect(start, size, update_flags, page_table)"]
iterate["Iterate through all areas"]
check_flags["Call update_flags(area.flags())"]
has_new_flags["New flags returned?"]
skip["Skip this area"]
check_position["Determine area position vs range"]
fully_contained["Area fully in range"]
straddles_both["Area straddles both boundaries"]
left_boundary["Area intersects left boundary"]
right_boundary["Area intersects right boundary"]
simple_protect["area.protect_area(), set_flags()"]
triple_split["Split into left|middle|right parts"]
right_split["Split area, protect right part"]
left_split["Split area, protect left part"]
continue_loop["Continue to next area"]
insert_new_areas["Queue new areas for insertion"]
all_done["All areas processed?"]
extend_areas["Insert all queued areas"]
success["Return Ok(())"]

all_done --> extend_areas
all_done --> iterate
check_flags --> has_new_flags
check_position --> fully_contained
check_position --> left_boundary
check_position --> right_boundary
check_position --> straddles_both
continue_loop --> all_done
extend_areas --> success
fully_contained --> simple_protect
has_new_flags --> check_position
has_new_flags --> skip
insert_new_areas --> continue_loop
iterate --> check_flags
left_boundary --> right_split
left_split --> insert_new_areas
right_boundary --> left_split
right_split --> insert_new_areas
simple_protect --> continue_loop
skip --> continue_loop
start --> iterate
straddles_both --> triple_split
triple_split --> insert_new_areas

The protect operation must handle five distinct cases based on how the protection range aligns with existing area boundaries, potentially creating new areas through splitting operations.

Sources: memory_set/src/set.rs(L195 - L264) 

Area Management Properties

The MemorySet maintains several critical invariants:

PropertyImplementationVerification
Non-overlapping AreasOverlap detection inmap(), careful splitting inunmap()/protect()overlaps()method, assertions in insertion
Sorted OrderBTreeMap automatically maintains order by start addressBTreeMap invariants
Consistent MappingsAll operations call correspondingMemoryAreamethodsBackend abstraction layer
Boundary AlignmentSize validation and address arithmeticAddrRangetype safety

The design ensures that operations can be composed safely - for example, mapping over an existing area with unmap_overlap=true will cleanly remove conflicts before establishing the new mapping.

Sources: memory_set/src/set.rs(L101 - L121)  memory_set/src/set.rs(L145 - L152) 

MemoryArea

Relevant source files

This document covers the MemoryArea struct, which represents individual memory regions within the memory_set crate. MemoryArea encapsulates a continuous range of virtual memory addresses along with their associated flags and mapping backend. For information about managing collections of memory areas, see MemorySet Core. For details about the abstraction layer for memory mapping implementations, see MappingBackend.

Core Structure and Purpose

The MemoryArea<B: MappingBackend> struct represents a single contiguous virtual memory region with uniform properties. It serves as the fundamental building block for memory management operations, combining address ranges from the memory_addr crate with backend-specific mapping functionality.

MemoryArea Components Diagram

flowchart TD
subgraph subGraph2["MappingBackend Dependencies"]
    BACKEND_TRAIT["MappingBackend trait"]
    PAGE_TABLE["B::PageTable"]
    ADDR_TYPE["B::Addr"]
    FLAGS_TYPE["B::Flags"]
end
subgraph subGraph1["memory_addr Dependencies"]
    ADDR_RANGE["AddrRange"]
    MEMORY_ADDR["MemoryAddr trait"]
end
subgraph subGraph0["MemoryArea Structure"]
    MA["MemoryArea<B>"]
    VAR["va_range: AddrRange<B::Addr>"]
    FLAGS["flags: B::Flags"]
    BACKEND["backend: B"]
end

ADDR_RANGE --> MEMORY_ADDR
BACKEND --> BACKEND_TRAIT
BACKEND --> PAGE_TABLE
FLAGS --> FLAGS_TYPE
MA --> BACKEND
MA --> FLAGS
MA --> VAR
VAR --> ADDR_RANGE
VAR --> ADDR_TYPE

Sources: memory_set/src/area.rs(L12 - L16) 

Construction and Access Methods

MemoryArea provides a straightforward constructor and comprehensive access methods for its properties.

Constructor and Basic Properties

MethodReturn TypePurpose
new(start, size, flags, backend)SelfCreates new memory area from start address and size
va_range()AddrRange<B::Addr>Returns the virtual address range
flags()B::FlagsReturns memory flags (permissions, etc.)
start()B::AddrReturns start address
end()B::AddrReturns end address
size()usizeReturns size in bytes
backend()&BReturns reference to mapping backend

The constructor uses AddrRange::from_start_size() to create the virtual address range and will panic if start + size overflows.

Sources: memory_set/src/area.rs(L18 - L61) 

Memory Mapping Operations

MemoryArea provides three core mapping operations that interface with the page table through the MappingBackend.

Mapping Operations Flow

sequenceDiagram
    participant MemorySet as MemorySet
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend
    participant PageTable as PageTable

    Note over MemorySet,PageTable: Core Mapping Operations
    MemorySet ->> MemoryArea: map_area(page_table)
    MemoryArea ->> MemoryArea: self.start(), self.size(), self.flags
    MemoryArea ->> MappingBackend: backend.map(start, size, flags, page_table)
    MappingBackend ->> PageTable: Hardware-specific mapping
    PageTable -->> MappingBackend: Success/failure
    MappingBackend -->> MemoryArea: bool result
    MemoryArea -->> MemorySet: MappingResult
    MemorySet ->> MemoryArea: unmap_area(page_table)
    MemoryArea ->> MappingBackend: backend.unmap(start, size, page_table)
    MappingBackend ->> PageTable: Hardware-specific unmapping
    PageTable -->> MappingBackend: Success/failure
    MappingBackend -->> MemoryArea: bool result
    MemoryArea -->> MemorySet: MappingResult
    MemorySet ->> MemoryArea: protect_area(new_flags, page_table)
    MemoryArea ->> MappingBackend: backend.protect(start, size, new_flags, page_table)
    MemoryArea ->> MemoryArea: self.flags = new_flags
    MemoryArea -->> MemorySet: MappingResult

Core Mapping Methods

  • map_area(page_table) - Maps the entire memory area using the backend's map function
  • unmap_area(page_table) - Unmaps the entire memory area
  • protect_area(new_flags, page_table) - Changes memory protection flags

All methods return MappingResult, which is Result<(), MappingError>. Operations that fail return MappingError::BadState.

Sources: memory_set/src/area.rs(L74 - L99) 

Area Manipulation Operations

MemoryArea supports sophisticated manipulation operations for dynamic memory management scenarios, including shrinking from either end and splitting into multiple areas.

Area Manipulation Capabilities

flowchart TD
subgraph subGraph3["split Operations"]
    SP1["Area1: start |-- size1 --| pos"]
    SP2["Area2: pos |-- size2 --| end"]
end
subgraph subGraph2["shrink_right Operations"]
    SR1["start |-- new_size --| new_end"]
    SR2["unmapped: new_end |--| end"]
end
subgraph subGraph1["shrink_left Operations"]
    SL1["new_start |-- new_size --| end"]
    SL2["unmapped: start |--| new_start"]
end
subgraph subGraph0["Original MemoryArea"]
    ORIG["start |-------- size --------| end"]
end

ORIG --> SL1
ORIG --> SL2
ORIG --> SP1
ORIG --> SP2
ORIG --> SR1
ORIG --> SR2

Manipulation Method Details

MethodParametersBehaviorConstraints
shrink_left(new_size, page_table)new_size, page_tableIncreases start address, unmaps left portionnew_size > 0 && < current_size
shrink_right(new_size, page_table)new_size, page_tableDecreases end address, unmaps right portionnew_size > 0 && < current_size
split(pos)pos (split position)Creates two areas, returns right portionstart < pos < end

shrink_left Implementation Details

  • Calculates unmap_size = old_size - new_size
  • Unmaps from current start for unmap_size bytes
  • Updates va_range.start using wrapping_add for overflow safety
  • Returns MappingError::BadState if unmapping fails

split Implementation Details

  • Validates that pos is within the area boundaries (exclusive)
  • Creates new MemoryArea for the right portion using backend.clone()
  • Updates original area's end to pos
  • Returns None if split position is invalid or would create empty areas

Sources: memory_set/src/area.rs(L101 - L178) 

Internal Modification Methods

MemoryArea provides several private methods used by MemorySet for internal state management.

Internal State Management

flowchart TD
subgraph subGraph1["Usage Context"]
    MS["MemorySet"]
    PROTECT["protect operations"]
    MERGE["area merging"]
    UNMAP["partial unmapping"]
end
subgraph subGraph0["Internal Modification Methods"]
    SF["set_flags(new_flags)"]
    SE["set_end(new_end)"]
end

MERGE --> SE
MS --> SE
MS --> SF
PROTECT --> SF
UNMAP --> SE
  • set_flags(new_flags) - Updates the flags field directly
  • set_end(new_end) - Modifies the end address of the virtual address range

These methods are marked pub(crate) and are used internally by MemorySet for complex operations like area merging and partial unmapping.

Sources: memory_set/src/area.rs(L63 - L73) 

Integration with Type System

MemoryArea implements Debug when the backend's address and flags types support debugging, providing formatted output for development and debugging purposes.

The struct uses extensive generic constraints through the MappingBackend trait, ensuring type safety across different address types and flag representations while maintaining backend independence.

Sources: memory_set/src/area.rs(L180 - L191) 

MappingBackend

Relevant source files

The MappingBackend trait provides an abstraction layer for different memory mapping implementations within the memory_set crate. This trait defines the underlying operations that occur when manipulating memory mappings within specific memory areas, allowing the system to support different mapping strategies such as linear mappings, lazy mappings, and custom hardware-specific implementations.

For information about how MappingBackend integrates with memory area management, see MemoryArea. For details on how backends are used within the broader memory set system, see MemorySet Core.

Trait Definition and Architecture

The MappingBackend trait serves as the foundation for all memory mapping operations in the memory_set system. It defines a generic interface that can be implemented by different backend strategies while maintaining type safety through associated types.

Associated Types Architecture


The trait defines three associated types that provide flexibility while maintaining type safety:

Associated TypeConstraintPurpose
AddrMemoryAddrAddress type used for all memory operations
FlagsCopyPermission and attribute flags for memory regions
PageTableNonePage table implementation specific to the backend

Sources: memory_set/src/backend.rs(L10 - L16) 

Core Operations

The MappingBackend trait defines three fundamental operations that all memory mapping implementations must provide:

Memory Mapping Operations

sequenceDiagram
    participant Client as Client
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend
    participant PageTable as PageTable

    Note over Client,PageTable: "Map Operation Flow"
    Client ->> MemoryArea: "map_area(page_table)"
    MemoryArea ->> MappingBackend: "map(start, size, flags, page_table)"
    MappingBackend ->> PageTable: "Modify page table entries"
    PageTable -->> MappingBackend: "Success/failure"
    MappingBackend -->> MemoryArea: "bool result"
    MemoryArea -->> Client: "Mapping result"
    Note over Client,PageTable: "Unmap Operation Flow"
    Client ->> MemoryArea: "unmap_area(page_table)"
    MemoryArea ->> MappingBackend: "unmap(start, size, page_table)"
    MappingBackend ->> PageTable: "Remove page table entries"
    PageTable -->> MappingBackend: "Success/failure"
    MappingBackend -->> MemoryArea: "bool result"
    MemoryArea -->> Client: "Unmapping result"
    Note over Client,PageTable: "Protect Operation Flow"
    Client ->> MemoryArea: "protect(new_flags, page_table)"
    MemoryArea ->> MappingBackend: "protect(start, size, new_flags, page_table)"
    MappingBackend ->> PageTable: "Update entry permissions"
    PageTable -->> MappingBackend: "Success/failure"
    MappingBackend -->> MemoryArea: "bool result"
    MemoryArea -->> Client: "Protection result"

Method Specifications

Map Operation

The map method establishes new memory mappings within a region:

  • Parameters: start address, size, access flags, mutable page table reference
  • Returns: bool indicating success or failure
  • Purpose: Create page table entries according to the backend's mapping strategy

Unmap Operation

The unmap method removes existing memory mappings:

  • Parameters: start address, size, mutable page table reference
  • Returns: bool indicating success or failure
  • Purpose: Remove page table entries and invalidate translations

Protect Operation

The protect method modifies access permissions for existing mappings:

  • Parameters: start address, size, new flags, mutable page table reference
  • Returns: bool indicating success or failure
  • Purpose: Update page table entry permissions without changing mappings

Sources: memory_set/src/backend.rs(L18 - L38) 

Backend Implementation Patterns

Different backend implementations support various memory management strategies within the same unified interface:

Backend Strategy Types

flowchart TD
subgraph subGraph1["Mapping Strategies"]
    DirectMap["Direct physical-to-virtualmapping at map() time"]
    PageFault["Empty mappings thattrigger page faults"]
    CustomHW["Hardware-specificoptimizations"]
    Testing["Controlled behaviorfor unit tests"]
end
subgraph subGraph0["Implementation Types"]
    LinearBackend["Linear Mapping Backend"]
    LazyBackend["Lazy Mapping Backend"]
    HardwareBackend["Hardware-Specific Backend"]
    MockBackend["Mock/Test Backend"]
end
MappingBackend["MappingBackend Trait"]

HardwareBackend --> CustomHW
LazyBackend --> PageFault
LinearBackend --> DirectMap
MappingBackend --> HardwareBackend
MappingBackend --> LazyBackend
MappingBackend --> LinearBackend
MappingBackend --> MockBackend
MockBackend --> Testing

Linear vs Lazy Mapping Strategies

The trait documentation specifically mentions two primary mapping approaches:

Linear Mappings: The target physical address is known when added to the page table. These backends implement direct address translation setup during the map operation.

Lazy Mappings: Empty mappings are added to the page table to trigger page faults. These backends defer actual memory allocation until first access, implementing demand paging strategies.

Sources: memory_set/src/backend.rs(L6 - L9) 

Integration with Memory Management System

The MappingBackend trait integrates closely with other components in the memory_set system:

System Integration Architecture


Backend Selection and Usage

Different memory areas can use different backends based on their specific requirements:

  • Device memory areas might use linear backends for direct hardware access
  • Application memory areas might use lazy backends for demand paging
  • Kernel memory areas might use hardware-optimized backends

The Clone requirement on the trait ensures that backends can be easily shared and copied when creating new memory areas or splitting existing ones.

Sources: memory_set/src/backend.rs(L1 - L38) 

Type Safety and Constraints

The trait design emphasizes type safety through associated type constraints:

Type Constraint System


The associated type constraints ensure that:

  • Address operations are type-safe and support all required memory address manipulations
  • Flag operations are efficient through the Copy trait requirement
  • Backend sharing is possible through the Clone requirement

Sources: memory_set/src/backend.rs(L10 - L16) 

Usage Examples and Testing

Relevant source files

This page demonstrates practical usage patterns for the memory_set crate components through comprehensive examples and testing scenarios. It covers both basic operations and advanced memory management patterns that showcase the full capabilities of the system.

For information about the core MemorySet data structures, see MemorySet Core. For details about the MappingBackend trait implementation requirements, see MappingBackend.

Mock Testing Infrastructure

The memory_set crate uses a comprehensive mock backend system for testing that serves as an excellent example of how to implement the MappingBackend trait for real-world usage.

MockBackend Implementation

The testing infrastructure centers around a MockBackend that simulates memory mapping operations using a simple array-based page table:

flowchart TD
subgraph subGraph2["Test Operations"]
    TMAP["Test Mappingset.map(area, pt, overlap)"]
    TUNMAP["Test Unmappingset.unmap(start, size, pt)"]
    TPROTECT["Test Protectionset.protect(start, size, update_fn, pt)"]
    TFIND["Test Free Area Findingset.find_free_area(base, size, range)"]
end
subgraph subGraph1["MappingBackend Implementation"]
    MAP["map(start, size, flags, pt)→ bool"]
    UNMAP["unmap(start, size, pt)→ bool"]
    PROTECT["protect(start, size, new_flags, pt)→ bool"]
end
subgraph subGraph0["Mock Testing Setup"]
    MT["MockPageTable[MockFlags; MAX_ADDR]"]
    MB["MockBackendstruct"]
    MF["MockFlagstype = u8"]
    MS["MockMemorySetMemorySet<MockBackend>"]
end

MB --> MAP
MB --> PROTECT
MB --> UNMAP
MF --> MB
MS --> MB
MT --> MB
TFIND --> MS
TMAP --> MS
TPROTECT --> MS
TUNMAP --> MS

Sources: memory_set/src/tests.rs(L10 - L55)  memory_set/README.md(L22 - L89) 

Key Testing Constants and Types

ComponentDefinitionPurpose
MAX_ADDR0x10000Maximum address space for testing
MockFlagsu8Simple flags representation
MockPageTable[MockFlags; MAX_ADDR]Array-based page table simulation
MockBackendstructImplementation ofMappingBackendtrait

The mock backend implements three core operations that demonstrate the expected behavior patterns for real memory management systems.

Sources: memory_set/src/tests.rs(L5 - L13) 

Basic Usage Patterns

Simple Map and Unmap Operations

The most fundamental usage pattern involves creating a MemorySet, mapping memory areas, and performing basic operations:


Sources: memory_set/src/tests.rs(L110 - L116)  memory_set/src/tests.rs(L330 - L364) 

Advanced Testing Scenarios

Area Splitting During Unmap Operations

One of the most complex scenarios tested is how the system handles unmapping portions of existing areas, which can result in area splitting:


The test demonstrates that protection changes that don't actually modify flags (like the third protect operation) are optimized to avoid unnecessary area splitting.

Sources: memory_set/src/tests.rs(L232 - L328) 

Free Area Discovery

The system includes sophisticated logic for finding available memory regions that can accommodate new allocations:

Test ScenarioBase AddressSizeExpected ResultReasoning
Exact fit0x00x10000x1000First available gap
Partial fit0x8000x8000x1000Rounds up to available space
Within gap0x18000x8000x1800Fits in existing gap
Requires larger gap0x18000x10000x3000Moves to next suitable gap
At boundary0xf0000x10000xf000Fits at end of address space
Impossible0xf0010x1000NoneNo space available

This testing validates the free area detection logic that would be crucial for implementing memory allocators on top of the system.

Sources: memory_set/src/tests.rs(L330 - L364) 

Test Infrastructure Utilities

Assertion Macros

The test suite defines custom assertion macros that simplify error checking:

  • assert_ok!(expr) - Verifies operation succeeds
  • assert_err!(expr) - Verifies operation fails
  • assert_err!(expr, ErrorType) - Verifies specific error type

Memory Set Inspection

The dump_memory_set function provides detailed debugging output showing the current state of all memory areas, which is invaluable during development and testing.

Sources: memory_set/src/tests.rs(L57 - L81) 

Integration Testing Patterns

Comprehensive State Validation

The tests systematically verify both the high-level MemorySet state and the low-level MockPageTable state after each operation:

flowchart TD
subgraph subGraph1["Test Operations"]
    OP["Memory Operation(map/unmap/protect)"]
end
subgraph subGraph0["Validation Layers"]
    HL["High-Level Validation• Area count (set.len())• Area boundaries• Area flags• Iterator consistency"]
    LL["Low-Level Validation• Page table entries• Flag consistency• Unmapped region verification"]
    CROSS["Cross-Layer Validation• MemorySet areas match page table• No gaps in coverage• Flag coherence"]
end

HL --> CROSS
LL --> CROSS
OP --> HL
OP --> LL

This multi-layered validation approach ensures that the abstract memory area management remains consistent with the underlying page table state.

Sources: memory_set/src/tests.rs(L106 - L108)  memory_set/src/tests.rs(L140 - L142)  memory_set/src/tests.rs(L186 - L221) 

Development Guide

Relevant source files

This document provides comprehensive information for developers contributing to the axmm_crates repository. It covers development environment setup, CI/CD pipeline configuration, code quality standards, testing procedures, and contribution workflow. For information about the system architecture and crate functionality, see Overview and the individual crate documentation pages memory_addr Crate and memory_set Crate.

Development Environment Setup

The axmm_crates project requires specific toolchain configuration to support multiple target architectures and maintain compatibility with no-std environments.

Required Rust Toolchain

The project exclusively uses Rust nightly toolchain with the following required components:

ComponentPurpose
rust-srcSource code for cross-compilation to bare-metal targets
clippyLinting and static analysis
rustfmtCode formatting enforcement

Supported Target Architectures

flowchart TD
nightly["Rust Nightly Toolchain"]
x86_64_linux["x86_64-unknown-linux-gnu"]
x86_64_none["x86_64-unknown-none"]
riscv64["riscv64gc-unknown-none-elf"]
aarch64["aarch64-unknown-none-softfloat"]
hosted["Hosted EnvironmentUnit Testing"]
bare_metal_x86["Bare Metal x86_64Kernel Development"]
bare_metal_riscv["Bare Metal RISC-VEmbedded Systems"]
bare_metal_arm["Bare Metal ARM64Embedded/Mobile"]

aarch64 --> bare_metal_arm
nightly --> aarch64
nightly --> riscv64
nightly --> x86_64_linux
nightly --> x86_64_none
riscv64 --> bare_metal_riscv
x86_64_linux --> hosted
x86_64_none --> bare_metal_x86

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L16 - L19) 

CI/CD Pipeline Architecture

The continuous integration system is implemented using GitHub Actions and follows a comprehensive validation strategy.

CI Workflow Structure

flowchart TD
trigger["Push/Pull Request Trigger"]
ci_job["CI Job Matrix"]
doc_job["Documentation Job"]
setup["Environment Setup- Checkout code- Install Rust nightly- Add components & targets"]
version_check["rustc --version --verbose"]
format_check["cargo fmt --all -- --check"]
clippy_check["cargo clippy --target TARGET --all-features"]
build_step["cargo build --target TARGET --all-features"]
unit_tests["cargo test (x86_64-linux only)"]
doc_setup["Documentation Setup- Checkout code- Install Rust nightly"]
doc_build["cargo doc --no-deps --all-features"]
pages_deploy["Deploy to GitHub Pages(main branch only)"]

build_step --> unit_tests
ci_job --> setup
clippy_check --> build_step
doc_build --> pages_deploy
doc_job --> doc_setup
doc_setup --> doc_build
format_check --> clippy_check
setup --> version_check
trigger --> ci_job
trigger --> doc_job
version_check --> format_check

Sources: .github/workflows/ci.yml(L5 - L31)  .github/workflows/ci.yml(L32 - L55) 

Quality Gates and Validation

StageCommandTarget ScopeFailure Behavior
Format Checkcargo fmt --all -- --checkAll codeFail fast
Lintingcargo clippy --target TARGET --all-featuresPer targetAllow new_without_default warnings
Build Validationcargo build --target TARGET --all-featuresPer targetFail on error
Unit Testingcargo test --target x86_64-unknown-linux-gnuHosted onlyOutput captured

Sources: .github/workflows/ci.yml(L22 - L30) 

Code Quality Standards

Formatting Configuration

The project enforces consistent code formatting through rustfmt with specific configuration:

wrap_comments = true

This setting ensures that long comments are automatically wrapped to maintain readability.

Sources: rustfmt.toml(L1 - L2) 

Linting Rules

The CI pipeline runs clippy with specific allowances configured:

  • Suppressed Warning: clippy::new_without_default - Allows new() methods without implementing Default trait
  • Target-Specific Analysis: Linting runs against all supported target architectures to catch platform-specific issues

Sources: .github/workflows/ci.yml(L25) 

Testing Strategy

Test Execution Matrix

flowchart TD
subgraph subGraph1["Test Types"]
    unit_tests["Unit Tests--nocapture flag"]
    build_tests["Build ValidationCross-compilation"]
    format_tests["Format ValidationCode Style"]
    lint_tests["Static AnalysisClippy Checks"]
end
subgraph subGraph0["Test Targets"]
    x86_64_linux["x86_64-unknown-linux-gnuFull Test Suite"]
    other_targets["Other TargetsBuild-Only Validation"]
end

build_tests --> format_tests
format_tests --> lint_tests
other_targets --> build_tests
unit_tests --> format_tests
x86_64_linux --> build_tests
x86_64_linux --> unit_tests

Sources: .github/workflows/ci.yml(L28 - L30) 

Test Environment Configuration

  • Output Capture: Tests run with --nocapture flag to display all output during CI execution
  • Target Restriction: Unit tests only execute on x86_64-unknown-linux-gnu due to hosted environment requirements
  • Cross-Compilation Validation: All targets must successfully build to ensure broad compatibility

Documentation Generation

Automated Documentation Pipeline

The documentation system generates comprehensive API documentation with enhanced features:

Documentation Build Configuration

RUSTDOCFLAGS="-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs"
cargo doc --no-deps --all-features
FlagPurpose
--enable-index-pageCreates unified documentation index
-D rustdoc::broken_intra_doc_linksTreats broken internal links as errors
-D missing-docsRequires documentation for all public items
--no-depsExcludes dependency documentation
--all-featuresDocuments all feature combinations

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47) 

Documentation Deployment

sequenceDiagram
    participant Developer as "Developer"
    participant GitHubActions as "GitHub Actions"
    participant GitHubPages as "GitHub Pages"

    Developer ->> GitHubActions: "Push to main branch"
    GitHubActions ->> GitHubActions: "cargo doc --no-deps --all-features"
    GitHubActions ->> GitHubActions: "Validate documentation build"
    alt Documentation build succeeds
        GitHubActions ->> GitHubPages: "Deploy target/doc to gh-pages branch"
        GitHubPages ->> GitHubPages: "Update live documentation site"
    else Documentation build fails
        GitHubActions ->> Developer: "CI failure notification"
    end

Sources: .github/workflows/ci.yml(L48 - L54) 

Development Workflow

Repository Structure

The project follows standard Rust workspace conventions with specific exclusions:

axmm_crates/
├── target/          # Build artifacts (ignored)
├── .vscode/         # Editor configuration (ignored)
├── Cargo.lock       # Dependency lock file (ignored)
├── memory_addr/     # Foundation address crate
├── memory_set/      # Memory mapping management crate
└── .github/workflows/ # CI/CD configuration

Sources: .gitignore(L1 - L5) 

Contribution Process

flowchart TD
fork["Fork Repository"]
clone["Clone Fork Locally"]
branch["Create Feature Branch"]
develop["Local Development"]
format["Run cargo fmt"]
test["Run cargo test"]
clippy["Run cargo clippy"]
commit["Commit Changes"]
push["Push to Fork"]
pr["Create Pull Request"]
ci_check["CI Pipeline Execution"]
review["Code Review Process"]
merge["Merge to Main"]

branch --> develop
ci_check --> review
clippy --> commit
clone --> branch
commit --> push
develop --> format
fork --> clone
format --> test
pr --> ci_check
push --> pr
review --> merge
test --> clippy

Local Development Commands

TaskCommandPurpose
Format Codecargo fmt --allApply consistent formatting
Run Testscargo test -- --nocaptureExecute test suite with output
Lint Codecargo clippy --all-featuresStatic analysis and suggestions
Build All Targetscargo build --target --all-featuresValidate cross-compilation
Generate Docscargo doc --no-deps --all-featuresBuild documentation locally

Sources: .github/workflows/ci.yml(L22 - L30)  .github/workflows/ci.yml(L47) 

Overview

Relevant source files

Purpose and Scope

The arm_pl031 crate provides a safe Rust driver for the ARM PL031 Real-Time Clock (RTC) hardware on aarch64 systems. This driver enables embedded and systems software to read and set time values, configure interrupts, and manage RTC functionality through a memory-mapped I/O interface.

The crate is designed for no_std environments and supports both basic Unix timestamp operations and optional high-level DateTime handling through the chrono feature. It targets embedded systems, hypervisors, and operating system kernels that need direct hardware access to PL031 RTC devices.

For detailed implementation specifics, see Core Driver Implementation. For optional features and extensions, see Features and Extensions. For practical usage guidance, see Getting Started.

Sources: Cargo.toml(L1 - L20)  src/lib.rs(L1 - L8)  README.md(L1 - L6) 

System Architecture

The arm_pl031 driver implements a layered architecture that provides multiple abstraction levels over the PL031 hardware, from raw register access to high-level DateTime operations.

Core Driver Architecture

flowchart TD
subgraph subGraph3["Safe API Layer"]
    GetTime["get_unix_timestamp()"]
    SetTime["set_unix_timestamp()"]
    SetMatch["set_match_timestamp()"]
    IntOps["Interrupt Operations"]
end
subgraph subGraph2["Register Interface"]
    DR["dr: Data Register"]
    MR["mr: Match Register"]
    LR["lr: Load Register"]
    CR["cr: Control Register"]
    IMSC["imsc: Interrupt Mask"]
    RIS["ris: Raw Interrupt Status"]
    MIS["mis: Masked Interrupt Status"]
    ICR["icr: Interrupt Clear"]
end
subgraph subGraph1["Driver Core (src/lib.rs)"]
    Rtc["Rtc struct"]
    Registers["Registers struct"]
    SafetyBoundary["unsafe fn new()"]
end
subgraph subGraph0["Hardware Layer"]
    PL031["PL031 RTC Hardware"]
    MMIO["Memory-Mapped Registers"]
    DT["Device Tree Configuration"]
end

DT --> PL031
MMIO --> SafetyBoundary
PL031 --> MMIO
Registers --> CR
Registers --> DR
Registers --> ICR
Registers --> IMSC
Registers --> LR
Registers --> MIS
Registers --> MR
Registers --> RIS
Rtc --> GetTime
Rtc --> IntOps
Rtc --> Registers
Rtc --> SetMatch
Rtc --> SetTime
SafetyBoundary --> Rtc

The driver centers around the Rtc struct which encapsulates a raw pointer to the hardware registers. The Registers struct defines the memory layout matching the PL031 hardware specification, ensuring correct MMIO access patterns.

Sources: src/lib.rs(L15 - L44)  src/lib.rs(L46 - L61) 

Feature-Based System Components


The system uses Cargo features to provide modular functionality. The core driver in lib.rs provides essential RTC operations, while the optional chrono module adds convenient DateTime handling for applications that need it.

Sources: Cargo.toml(L14 - L19)  src/lib.rs(L10 - L11)  src/lib.rs(L123 - L128) 

Hardware Interface and Safety Model

Memory-Mapped I/O Operations

flowchart TD
subgraph subGraph4["Hardware Registers"]
    HardwareRegs["PL031 MMIO Registers"]
    PhysicalRTC["Physical RTC Hardware"]
end
subgraph subGraph3["Volatile MMIO"]
    ReadVol["addr_of!(*registers.dr).read_volatile()"]
    WriteVol["addr_of_mut!(*registers.lr).write_volatile()"]
    RegAccess["Register field access"]
end
subgraph subGraph2["Safe Operations"]
    GetUnix["get_unix_timestamp() -> u32"]
    SetUnix["set_unix_timestamp(u32)"]
    SetMatch["set_match_timestamp(u32)"]
    EnableInt["enable_interrupt(bool)"]
    CheckInt["interrupt_pending() -> bool"]
    ClearInt["clear_interrupt()"]
end
subgraph subGraph1["Safety Boundary"]
    UnsafeNew["unsafe fn new(base_address)"]
    SafetyDoc["Safety Documentation"]
end
subgraph subGraph0["Application Code"]
    UserApp["User Application"]
    BaseAddr["base_address: *mut u32"]
end

BaseAddr --> UnsafeNew
CheckInt --> ReadVol
ClearInt --> WriteVol
EnableInt --> WriteVol
GetUnix --> ReadVol
HardwareRegs --> PhysicalRTC
ReadVol --> RegAccess
RegAccess --> HardwareRegs
SetMatch --> WriteVol
SetUnix --> WriteVol
UnsafeNew --> CheckInt
UnsafeNew --> ClearInt
UnsafeNew --> EnableInt
UnsafeNew --> GetUnix
UnsafeNew --> SafetyDoc
UnsafeNew --> SetMatch
UnsafeNew --> SetUnix
UserApp --> BaseAddr
WriteVol --> RegAccess

The driver implements a clear safety boundary where unsafe operations are isolated to the constructor new() function, while all subsequent operations use safe Rust APIs. All hardware access uses volatile operations to ensure proper MMIO semantics.

Sources: src/lib.rs(L46 - L60)  src/lib.rs(L62 - L74)  src/lib.rs(L76 - L120) 

Key Code Entities and Relationships

The following table maps the primary system components to their code representations:

System ComponentCode EntityFile LocationPurpose
RTC DriverRtcstructsrc/lib.rs42-44Main driver interface
Hardware LayoutRegistersstructsrc/lib.rs15-39Memory-mapped register layout
Initializationunsafe fn new()src/lib.rs47-60Driver constructor with safety contract
Time Readingget_unix_timestamp()src/lib.rs62-67Read current time from hardware
Time Settingset_unix_timestamp()src/lib.rs69-74Set hardware time
Interrupt Controlenable_interrupt()src/lib.rs108-113Manage interrupt masking
Interrupt Statusinterrupt_pending()src/lib.rs97-102Check interrupt state
Optional DateTimechrono modulesrc/lib.rs10-11High-level time operations

Register Field Mapping

The Registers struct directly corresponds to the PL031 hardware specification:

Register FieldHardware PurposeAccess Pattern
drData Register - current timeRead-only viaread_volatile()
mrMatch Register - interrupt triggerWrite viawrite_volatile()
lrLoad Register - set timeWrite viawrite_volatile()
crControl Register - device controlReserved for future use
imscInterrupt Mask - enable/disableWrite viawrite_volatile()
risRaw Interrupt Status - match stateRead viaread_volatile()
misMasked Interrupt Status - pendingRead viaread_volatile()
icrInterrupt Clear - acknowledgeWrite viawrite_volatile()

Sources: src/lib.rs(L17 - L39)  src/lib.rs(L63 - L66)  src/lib.rs(L70 - L73)  src/lib.rs(L78 - L81) 

Project Purpose and Scope

Relevant source files

This document defines the purpose and scope of the arm_pl031 crate, a Real Time Clock (RTC) driver for ARM PL031 hardware on aarch64 platforms. For information about the internal driver implementation, see Core Driver Implementation. For build and deployment details, see Building and Testing.

Overview

The arm_pl031 crate provides a safe Rust interface to ARM PL031 Real Time Clock hardware, specifically designed for aarch64-based embedded systems and operating system kernels. The driver abstracts the low-level memory-mapped I/O operations required to interact with PL031 RTC hardware while maintaining memory safety and providing both basic timestamp operations and high-level DateTime functionality.

Sources: Cargo.toml(L1 - L20)  README.md(L1 - L38) 

Target Platforms and Compatibility

Supported Architectures

The driver primarily targets aarch64 platforms but maintains compatibility with multiple architectures through its no_std design:

ArchitectureSupport LevelUse Case
aarch64-unknown-none-*PrimaryEmbedded/bare-metal systems
x86_64-unknown-linux-gnuTestingDevelopment and CI
riscv64gc-unknown-none-elfSecondaryCross-platform embedded

Environment Compatibility

The crate is designed for no_std environments, making it suitable for:

  • Embedded operating systems (such as ArceOS)
  • Bare-metal applications
  • Kernel-level drivers
  • Real-time systems requiring precise timekeeping

Sources: Cargo.toml(L12)  README.md(L5) 

Core Capabilities and Feature Set

Hardware Interface Diagram

flowchart TD
subgraph subGraph2["Hardware Layer"]
    pl031_mmio["PL031 MMIO Registers"]
    device_tree["Device Tree Configuration"]
    base_addr["Base Address (0x9010000)"]
end
subgraph subGraph1["Optional Chrono Feature"]
    get_time["get_time() -> DateTime"]
    set_time["set_time(DateTime)"]
    chrono_dep["chrono crate v0.4.38"]
end
subgraph subGraph0["arm_pl031 Crate Interface"]
    Rtc["Rtc struct"]
    new["unsafe new(base_addr)"]
    get_unix_timestamp["get_unix_timestamp()"]
    set_unix_timestamp["set_unix_timestamp()"]
    enable_interrupt["enable_interrupt()"]
    clear_interrupt["clear_interrupt()"]
end

Rtc --> clear_interrupt
Rtc --> enable_interrupt
Rtc --> get_unix_timestamp
Rtc --> pl031_mmio
Rtc --> set_unix_timestamp
base_addr --> new
device_tree --> base_addr
get_time --> chrono_dep
get_time --> get_unix_timestamp
new --> Rtc
set_time --> chrono_dep
set_time --> set_unix_timestamp

Feature Configuration

The crate provides a modular feature system:

flowchart TD
subgraph subGraph1["Optional Features"]
    chrono_feat["chrono feature (default)"]
    datetime_api["DateTime API"]
    chrono_crate["chrono dependency"]
end
subgraph subGraph0["Core Features (Always Available)"]
    core_rtc["Core RTC Operations"]
    unix_timestamp["Unix Timestamp API"]
    interrupt_mgmt["Interrupt Management"]
    mmio_safety["Memory-Safe MMIO"]
end

chrono_feat --> datetime_api
core_rtc --> interrupt_mgmt
core_rtc --> mmio_safety
core_rtc --> unix_timestamp
datetime_api --> chrono_crate
datetime_api --> unix_timestamp

Sources: Cargo.toml(L14 - L19)  README.md(L9 - L14) 

Usage Scenarios

Primary Use Cases

  1. Embedded Operating Systems: Integration into kernel-level time management systems
  2. Bare-Metal Applications: Direct hardware timekeeping without OS overhead
  3. Real-Time Systems: Precise timestamp generation and interrupt-driven time events
  4. Device Drivers: Foundation for higher-level system time services

Integration Pattern

The typical integration follows this initialization pattern:

sequenceDiagram
    participant ApplicationKernel as "Application/Kernel"
    participant DeviceTree as "Device Tree"
    participant arm_pl031Rtc as "arm_pl031::Rtc"
    participant PL031Hardware as "PL031 Hardware"

    ApplicationKernel ->> DeviceTree: "Parse device tree"
    DeviceTree -->> ApplicationKernel: "Base address (0x9010000)"
    ApplicationKernel ->> arm_pl031Rtc: "unsafe Rtc::new(base_addr)"
    arm_pl031Rtc ->> PL031Hardware: "Initialize MMIO access"
    ApplicationKernel ->> arm_pl031Rtc: "get_unix_timestamp()"
    arm_pl031Rtc ->> PL031Hardware: "Read DR register"
    PL031Hardware -->> arm_pl031Rtc: "Raw timestamp"
    arm_pl031Rtc -->> ApplicationKernel: "u32 timestamp"

Sources: README.md(L16 - L37) 

Scope Boundaries

What the Crate Provides

  • Hardware Abstraction: Safe wrapper around PL031 MMIO operations
  • Memory Safety: Encapsulation of unsafe hardware access
  • Time Operations: Unix timestamp reading and writing
  • Interrupt Support: Match register configuration and interrupt handling
  • Optional DateTime: High-level time manipulation via chrono integration

What the Crate Does NOT Provide

  • Device Discovery: Users must obtain base addresses from device tree or firmware
  • Clock Configuration: Hardware clock setup is assumed to be done by firmware/bootloader
  • Time Zone Support: Only UTC timestamps are supported natively
  • Persistence: No handling of RTC battery backup or power management
  • Multiple Instance Management: Single RTC instance per hardware unit

Licensing and Compliance

The crate uses a triple-license approach supporting multiple compliance requirements:

  • GPL-3.0-or-later: For GPL-compatible projects
  • Apache-2.0: For Apache ecosystem integration
  • MulanPSL-2.0: For Chinese regulatory compliance

Sources: Cargo.toml(L7)  Cargo.toml(L6)  Cargo.toml(L11 - L12) 

System Architecture

Relevant source files

Purpose and Scope

This document explains the high-level architecture of the arm_pl031 crate, focusing on how the driver abstracts ARM PL031 RTC hardware through layered components, safety boundaries, and feature integration. It covers the structural relationships between the core Rtc driver, hardware register interface, and optional feature extensions.

For implementation details of specific components, see Core Driver Implementation. For practical usage guidance, see Getting Started.

Overall Architecture Overview

The arm_pl031 crate implements a layered architecture that provides safe, high-level access to ARM PL031 RTC hardware while maintaining clear separation between abstraction levels.

flowchart TD
subgraph subGraph3["Safety Boundary"]
    UNSAFE_OPS["unsafe operations"]
end
subgraph subGraph2["Hardware Layer"]
    MMIO["Memory-Mapped I/O"]
    PL031_HW["ARM PL031 Hardware"]
end
subgraph subGraph1["arm_pl031 Crate"]
    RTC["Rtc struct"]
    REGISTERS["Registers struct"]
    CHRONO_MOD["chrono module"]
end
subgraph subGraph0["Application Layer"]
    APP["Application Code"]
    CHRONO_API["chrono::DateTime API"]
    UNIX_API["Unix Timestamp API"]
end

APP --> CHRONO_API
APP --> UNIX_API
CHRONO_API --> CHRONO_MOD
CHRONO_MOD --> RTC
MMIO --> PL031_HW
REGISTERS --> MMIO
REGISTERS --> UNSAFE_OPS
RTC --> REGISTERS
UNIX_API --> RTC
UNSAFE_OPS --> MMIO

Architecture Diagram: Overall System Layers

This architecture provides multiple abstraction levels, allowing developers to choose between high-level DateTime operations or low-level timestamp manipulation while maintaining memory safety through controlled unsafe boundaries.

Sources: src/lib.rs(L1 - L159) 

Hardware Abstraction Layers

The driver implements a three-tier abstraction model that isolates hardware specifics while providing flexible access patterns.

flowchart TD
subgraph subGraph2["Hardware Interface"]
    READ_VOLATILE["read_volatile()"]
    WRITE_VOLATILE["write_volatile()"]
    ADDR_OF["addr_of!()"]
    ADDR_OF_MUT["addr_of_mut!()"]
end
subgraph subGraph1["Register Abstraction"]
    DR["dr: u32"]
    LR["lr: u32"]
    MR["mr: u32"]
    IMSC["imsc: u8"]
    MIS["mis: u8"]
    ICR["icr: u8"]
end
subgraph subGraph0["Safe Rust Interface"]
    GET_TIME["get_unix_timestamp()"]
    SET_TIME["set_unix_timestamp()"]
    INTERRUPTS["enable_interrupt()"]
    MATCH_OPS["set_match_timestamp()"]
end

DR --> READ_VOLATILE
GET_TIME --> DR
IMSC --> WRITE_VOLATILE
INTERRUPTS --> IMSC
LR --> WRITE_VOLATILE
MATCH_OPS --> MR
MR --> WRITE_VOLATILE
READ_VOLATILE --> ADDR_OF
SET_TIME --> LR
WRITE_VOLATILE --> ADDR_OF_MUT

Architecture Diagram: Hardware Abstraction Tiers

Each tier serves a specific purpose:

  • Safe Interface: Provides memory-safe methods with clear semantics
  • Register Abstraction: Maps PL031 registers to Rust types with proper layout
  • Hardware Interface: Handles volatile memory operations with explicit safety documentation

Sources: src/lib.rs(L15 - L39)  src/lib.rs(L62 - L121) 

Component Architecture and Relationships

The core architecture centers around two primary structures that encapsulate hardware interaction and provide the driver interface.

ComponentPurposeSafety LevelKey Responsibilities
RtcDriver interfaceSafe wrapperTime operations, interrupt management
RegistersHardware layoutMemory representationRegister field mapping, memory alignment
chronomoduleFeature extensionSafe wrapperDateTime conversion, high-level API
flowchart TD
subgraph subGraph2["Memory Operations"]
    PTR_DEREF["(*self.registers)"]
    VOLATILE_READ["addr_of!(field).read_volatile()"]
    VOLATILE_WRITE["addr_of_mut!(field).write_volatile(value)"]
end
subgraph subGraph1["Register Layout Structure"]
    REG_STRUCT["#[repr(C, align(4))] Registers"]
    DR_FIELD["dr: u32"]
    MR_FIELD["mr: u32"]
    LR_FIELD["lr: u32"]
    CONTROL_FIELDS["cr, imsc, ris, mis, icr: u8"]
    RESERVED["_reserved0..4: [u8; 3]"]
end
subgraph subGraph0["Rtc Driver Structure"]
    RTC_STRUCT["Rtc { registers: *mut Registers }"]
    NEW_METHOD["unsafe fn new(base_address: *mut u32)"]
    GET_TIMESTAMP["fn get_unix_timestamp() -> u32"]
    SET_TIMESTAMP["fn set_unix_timestamp(unix_time: u32)"]
    INTERRUPT_METHODS["interrupt management methods"]
end

GET_TIMESTAMP --> PTR_DEREF
INTERRUPT_METHODS --> PTR_DEREF
NEW_METHOD --> RTC_STRUCT
PTR_DEREF --> VOLATILE_READ
PTR_DEREF --> VOLATILE_WRITE
RTC_STRUCT --> REG_STRUCT
SET_TIMESTAMP --> PTR_DEREF
VOLATILE_READ --> DR_FIELD
VOLATILE_WRITE --> LR_FIELD
VOLATILE_WRITE --> MR_FIELD

Architecture Diagram: Core Component Relationships

The Rtc struct maintains a single pointer to a Registers structure, which provides C-compatible memory layout matching the PL031 hardware specification. All hardware access flows through volatile pointer operations to ensure proper memory semantics.

Sources: src/lib.rs(L15 - L44)  src/lib.rs(L46 - L61) 

Safety Model and Boundaries

The driver implements a clear safety model that contains all unsafe operations at the hardware interface boundary while providing safe abstractions for application code.

flowchart TD
subgraph subGraph2["Hardware Zone"]
    MMIO_ACCESS["Direct MMIO register access"]
    DEVICE_MEMORY["PL031 device memory"]
end
subgraph subGraph1["Unsafe Boundary"]
    UNSAFE_NEW["unsafe fn new()"]
    SAFETY_INVARIANTS["Safety invariants and documentation"]
    VOLATILE_OPS["Volatile memory operations"]
end
subgraph subGraph0["Safe Zone"]
    SAFE_METHODS["Public safe methods"]
    TIMESTAMP_OPS["get_unix_timestamp, set_unix_timestamp"]
    INTERRUPT_OPS["enable_interrupt, clear_interrupt"]
    MATCH_OPS["set_match_timestamp, matched"]
end
subgraph subGraph3["Concurrency Safety"]
    SEND_IMPL["unsafe impl Send for Rtc"]
    THREAD_SAFETY["Multi-thread access guarantees"]
    SYNC_IMPL["unsafe impl Sync for Rtc"]
end
UNSAFE_BOUNDARY["UNSAFE_BOUNDARY"]

INTERRUPT_OPS --> VOLATILE_OPS
MATCH_OPS --> VOLATILE_OPS
MMIO_ACCESS --> DEVICE_MEMORY
SAFE_METHODS --> UNSAFE_BOUNDARY
SEND_IMPL --> THREAD_SAFETY
SYNC_IMPL --> THREAD_SAFETY
TIMESTAMP_OPS --> VOLATILE_OPS
UNSAFE_NEW --> SAFETY_INVARIANTS
VOLATILE_OPS --> MMIO_ACCESS

Architecture Diagram: Safety Boundaries and Concurrency Model

Safety Requirements

The driver enforces specific safety requirements at the boundary:

  1. Memory Safety: The base_address parameter in Rtc::new() must point to valid PL031 MMIO registers
  2. Alignment: Hardware addresses must be 4-byte aligned as enforced by #[repr(C, align(4))]
  3. Exclusive Access: No other aliases to the device memory region are permitted
  4. Device Mapping: Memory must be mapped as device memory, not normal cacheable memory

Concurrency Safety

The driver provides thread-safety through carefully designed Send and Sync implementations:

  • Send: Safe because device memory access works from any thread context
  • Sync: Safe because shared read access to device registers is inherently safe, and mutation requires &mut self

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

Feature Integration Architecture

The crate supports optional feature integration through Cargo feature flags, with the chrono integration serving as the primary example of the extensibility model.


Architecture Diagram: Feature Integration and Build Configuration

Feature Design Principles

  1. Minimal Core: The base driver remains lightweight with only essential functionality
  2. Optional Extensions: Advanced features like DateTime support are opt-in through feature flags
  3. No-std Compatibility: All features maintain compatibility with embedded environments
  4. Layered Integration: Feature modules build upon the core API without modifying its interface

The chrono integration demonstrates this pattern by providing higher-level DateTime<Utc> operations while delegating to the core get_unix_timestamp() and set_unix_timestamp() methods.

Sources: src/lib.rs(L10 - L11)  src/lib.rs(L3) 

Getting Started

Relevant source files

This page provides practical guidance for integrating and using the arm_pl031 driver in your projects. It covers the essential steps to add the crate as a dependency, initialize the driver, and perform basic time operations. For detailed installation instructions and dependency management, see Installation and Dependencies. For comprehensive usage examples and device tree configuration details, see Basic Usage and Examples.

Integration Workflow

The following diagram shows the typical workflow for integrating the arm_pl031 driver into an embedded project:

flowchart TD
START["Project Setup"]
CARGO["Add arm_pl031 to Cargo.toml"]
DT["Configure Device Tree"]
ADDR["Obtain Base Address"]
INIT["unsafe Rtc::new()"]
USAGE["Use RTC APIs"]
CHRONO["chrono support enabled"]
CORE["core APIs only"]
READ["get_unix_timestamp()"]
WRITE["set_unix_timestamp()"]
INT["interrupt operations"]
DATETIME["DateTime APIs"]
TIMESTAMP["u32 timestamp APIs"]

ADDR --> INIT
CARGO --> CHRONO
CARGO --> CORE
CARGO --> DT
CHRONO --> DATETIME
CORE --> TIMESTAMP
DT --> ADDR
INIT --> USAGE
START --> CARGO
USAGE --> INT
USAGE --> READ
USAGE --> WRITE

Sources: Cargo.toml(L1 - L20)  src/lib.rs(L46 - L60)  README.md(L9 - L14) 

Core Driver Components

This diagram maps the key components you'll interact with when using the driver:

flowchart TD
subgraph subGraph3["Optional Features"]
    CHRONO_MOD["chrono module"]
    DATETIME_API["DateTime conversion"]
end
subgraph subGraph2["Hardware Integration"]
    DT_CONFIG["Device Tree pl031@address"]
    MMIO_ADDR["MMIO Base Address"]
    PL031_HW["PL031 Hardware"]
end
subgraph subGraph1["arm_pl031 Crate"]
    RTC_STRUCT["Rtc struct"]
    NEW_FN["unsafe fn new(base_address)"]
    GET_TS["get_unix_timestamp()"]
    SET_TS["set_unix_timestamp()"]
    MATCH_FN["set_match_timestamp()"]
    INT_FNS["interrupt methods"]
end
subgraph subGraph0["User Code"]
    APP["Application"]
    CONFIG["Cargo.toml dependencies"]
end

APP --> GET_TS
APP --> INT_FNS
APP --> MATCH_FN
APP --> NEW_FN
APP --> SET_TS
CHRONO_MOD --> DATETIME_API
CONFIG --> CHRONO_MOD
CONFIG --> RTC_STRUCT
DATETIME_API --> GET_TS
DT_CONFIG --> MMIO_ADDR
MMIO_ADDR --> PL031_HW
NEW_FN --> MMIO_ADDR

Sources: src/lib.rs(L42 - L44)  src/lib.rs(L56 - L60)  Cargo.toml(L14 - L19)  README.md(L27 - L33) 

Quick Start Example

Here's the minimal code needed to read the current time from a PL031 RTC:

Add the dependency to your Cargo.toml:

[dependencies]
arm_pl031 = "0.2.1"

Basic usage in your code:

use arm_pl031::Rtc;

// Initialize with hardware base address from device tree
let rtc = unsafe { Rtc::new(0x901_0000 as *mut u32) };

// Read current time as Unix timestamp
let current_time = rtc.get_unix_timestamp();

Sources: Cargo.toml(L2 - L3)  README.md(L9 - L14)  src/lib.rs(L56 - L67) 

Essential Requirements

RequirementDetailsCode Reference
Base AddressMust point to PL031 MMIO registerssrc/lib.rs53-55
Address AlignmentMust be 4-byte alignedsrc/lib.rs55
Memory MappingMust be mapped as device memorysrc/lib.rs53-54
SafetyConstructor requiresunsafedue to raw pointersrc/lib.rs51-56
Device TreeHardware address typically from device treeREADME.md19-37

Device Tree Configuration

The PL031 device must be properly configured in your device tree. Here's a typical configuration:

pl031@9010000 {
    clock-names = "apb_pclk";
    clocks = <0x8000>;
    interrupts = <0x00 0x02 0x04>;
    reg = <0x00 0x9010000 0x00 0x1000>;
    compatible = "arm,pl031", "arm,primecell";
};

The reg property provides the base address (0x9010000) and size (0x1000) that you'll use when calling Rtc::new().

Sources: README.md(L27 - L33) 

Feature Configuration

The crate supports optional features that can be controlled in your Cargo.toml:

FeatureDefaultPurposeDependencies
chronoEnabledDateTime conversion supportchrono = "0.4.38"
(none)Core onlyUnix timestamp operations onlyNone

To use only core functionality without chrono:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }

To explicitly enable chrono support:

[dependencies]
arm_pl031 = { version = "0.2.1", features = ["chrono"] }

Sources: Cargo.toml(L14 - L19)  src/lib.rs(L10 - L11) 

Next Steps

Installation and Dependencies

Relevant source files

This section covers how to integrate the arm_pl031 crate into your Rust project, configure optional features, and manage dependencies. It focuses on the practical aspects of adding the crate to your Cargo.toml and understanding the available configuration options.

For information about using the driver after installation, see Basic Usage and Examples. For detailed feature configuration and no_std compatibility, see Feature Configuration.

Adding the Crate to Your Project

The arm_pl031 crate is designed for embedded systems requiring ARM PL031 RTC functionality. Add it to your project's Cargo.toml dependencies section:

[dependencies]
arm_pl031 = "0.2.1"

The crate is published on crates.io with the identifier arm_pl031 and follows semantic versioning. The current stable version provides the core RTC driver functionality with optional chrono integration.

Dependency Graph

flowchart TD
PROJECT["Your ProjectCargo.toml"]
ARM_PL031["arm_pl031 = '0.2.1'"]
CHRONO_DEP["chrono = '0.4.38'(optional)"]
CORE_FEATURES["Core RTC Functionality- Register access- Interrupt handling- Unix timestamps"]
DATETIME_FEATURES["DateTime Support- DateTime- Time conversion- Chrono types"]

ARM_PL031 --> CHRONO_DEP
ARM_PL031 --> CORE_FEATURES
CHRONO_DEP --> DATETIME_FEATURES
PROJECT --> ARM_PL031

Sources: Cargo.toml(L1 - L20) 

Feature Configuration

The crate provides feature-based configuration to balance functionality with dependency minimization. The available features are controlled through Cargo feature flags.

FeatureDefaultPurposeDependencies
chronoEnablesDateTimesupport and time conversion utilitieschrono = "0.4.38"

Default Configuration

By default, the chrono feature is enabled, providing high-level time handling capabilities:

[dependencies]
arm_pl031 = "0.2.1"  # chrono feature included by default

Minimal Configuration

To minimize dependencies and use only core RTC functionality, disable default features:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }

Custom Feature Selection

Enable specific features explicitly:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false, features = ["chrono"] }

Feature Configuration Flow

flowchart TD
START["Add arm_pl031 dependency"]
DEFAULT_CHECK["Use default features?"]
CHRONO_NEEDED["Need DateTime support?"]
DEFAULT_YES["arm_pl031 = '0.2.1'(includes chrono)"]
DEFAULT_NO["arm_pl031 = { version = '0.2.1',default-features = false }"]
ADD_CHRONO["features = ['chrono']"]
CORE_ONLY["Core RTC only- Unix timestamps- Register access- Interrupts"]
FULL_FEATURES["Full functionality- DateTime- Time conversions- Core RTC"]

ADD_CHRONO --> FULL_FEATURES
CHRONO_NEEDED --> ADD_CHRONO
CHRONO_NEEDED --> CORE_ONLY
DEFAULT_CHECK --> DEFAULT_NO
DEFAULT_CHECK --> DEFAULT_YES
DEFAULT_NO --> CHRONO_NEEDED
DEFAULT_YES --> FULL_FEATURES
START --> DEFAULT_CHECK

Sources: Cargo.toml(L14 - L19) 

Dependency Management

The crate maintains minimal external dependencies to support embedded environments and no_std targets.

Direct Dependencies

The only optional dependency is the chrono crate, configured with minimal features:

chrono = { version = "0.4.38", default-features = false, optional = true }

This configuration ensures:

  • No standard library dependencies from chrono
  • Compatible with no_std environments
  • Minimal binary size impact when enabled
  • Optional inclusion based on feature flags

Platform Compatibility

The crate is designed for multiple target architectures:

TargetSupportUse Case
aarch64-unknown-none-softfloatBare metal ARM64
x86_64-unknown-linux-gnuDevelopment/testing
x86_64-unknown-noneBare metal x86_64
riscv64gc-unknown-none-elfRISC-V embedded

No Standard Library Support

The crate is no_std compatible by default, making it suitable for embedded environments:

#![no_std]  // Your embedded project
use arm_pl031::Rtc;

Dependency Resolution Matrix

flowchart TD
subgraph subGraph2["Runtime Requirements"]
    NO_STD["no_std compatible"]
    MMIO["MMIO access capability"]
    UNSAFE["unsafe operations allowed"]
end
subgraph subGraph1["Feature Configurations"]
    MINIMAL["default-features = falseNo external deps"]
    CHRONO_ENABLED["chrono feature+ chrono 0.4.38"]
end
subgraph subGraph0["Target Categories"]
    EMBEDDED["Embedded Targetsaarch64-unknown-none-*riscv64gc-unknown-none-elf"]
    HOSTED["Hosted Targetsx86_64-unknown-linux-gnu"]
end

CHRONO_ENABLED --> NO_STD
EMBEDDED --> CHRONO_ENABLED
EMBEDDED --> MINIMAL
HOSTED --> CHRONO_ENABLED
HOSTED --> MINIMAL
MINIMAL --> NO_STD
MMIO --> UNSAFE
NO_STD --> MMIO

Sources: Cargo.toml(L12)  Cargo.toml(L15) 

The crate is available under multiple license options to accommodate different project requirements:

  • GPL-3.0-or-later - GNU General Public License v3.0 or later
  • Apache-2.0 - Apache License 2.0
  • MulanPSL-2.0 - Mulan Permissive Software License v2

Projects can choose the most appropriate license for their specific use case and compliance requirements.

Sources: Cargo.toml(L7) 

Basic Usage and Examples

Relevant source files

This page provides practical examples for integrating and using the arm_pl031 RTC driver in your projects. It covers device initialization, basic time operations, and simple interrupt handling. For detailed information about optional features like chrono integration, see Chrono Integration. For comprehensive driver internals and advanced usage, see Core Driver Implementation.

Device Tree Configuration and Address Discovery

The ARM PL031 RTC driver requires a memory-mapped I/O (MMIO) base address to access the hardware registers. This address is typically obtained from the device tree configuration in your system.

Device Tree Example

The device tree entry for a PL031 RTC device follows this structure:

pl031@9010000 {
    clock-names = "apb_pclk";
    clocks = <0x8000>;
    interrupts = <0x00 0x02 0x04>;
    reg = <0x00 0x9010000 0x00 0x1000>;
    compatible = "arm,pl031", "arm,primecell";
};

The reg property specifies the base address (0x9010000) and size (0x1000) of the MMIO region. Your system's device tree parser should map this physical address to a virtual address accessible to your driver.

Address Discovery Flow

flowchart TD
DT["Device Tree Entry"]
PARSER["Device Tree Parser"]
PHYS["Physical Address0x9010000"]
MMU["Memory Management Unit"]
VIRT["Virtual AddressMapped for MMIO"]
RTC_NEW["Rtc::new(base_address)"]
RTC_INSTANCE["Rtc Instance"]
REG_PROP["reg = <0x00 0x9010000 0x00 0x1000>"]
COMPAT["compatible = arm,pl031"]

DT --> COMPAT
DT --> PARSER
DT --> REG_PROP
MMU --> VIRT
PARSER --> PHYS
PHYS --> MMU
RTC_NEW --> RTC_INSTANCE
VIRT --> RTC_NEW

Sources: README.md(L19 - L37)  src/lib.rs(L47 - L60) 

Basic Initialization

The Rtc struct provides the primary interface to the PL031 hardware. Initialization requires an unsafe constructor call since it involves raw memory access.

Creating an RTC Instance

use arm_pl031::Rtc;

// Obtain base_address from device tree mapping
let base_address = 0x901_0000 as *mut u32;

// SAFETY: base_address must point to valid PL031 MMIO registers
let rtc = unsafe { Rtc::new(base_address) };

Safety Requirements

The new constructor is marked unsafe because it requires several guarantees from the caller:

  • The base address must point to valid PL031 MMIO control registers
  • The memory region must be mapped as device memory (not cached)
  • The address must be aligned to a 4-byte boundary
  • No other aliases to this memory region should exist
flowchart TD
UNSAFE_NEW["unsafe Rtc::new(base_address)"]
SAFETY_CHECK["Safety Requirements Check"]
VALID_ADDR["Valid MMIO Address"]
DEVICE_MEM["Device Memory Mapping"]
ALIGNMENT["4-byte Alignment"]
NO_ALIAS["No Memory Aliases"]
RTC_STRUCT["Rtc { registers: *mut Registers }"]
API_CALLS["Safe API Methods"]

ALIGNMENT --> RTC_STRUCT
DEVICE_MEM --> RTC_STRUCT
NO_ALIAS --> RTC_STRUCT
RTC_STRUCT --> API_CALLS
SAFETY_CHECK --> ALIGNMENT
SAFETY_CHECK --> DEVICE_MEM
SAFETY_CHECK --> NO_ALIAS
SAFETY_CHECK --> VALID_ADDR
UNSAFE_NEW --> SAFETY_CHECK
VALID_ADDR --> RTC_STRUCT

Sources: src/lib.rs(L47 - L60)  README.md(L9 - L14) 

Reading and Setting Time

The driver provides straightforward methods for time operations using Unix timestamps (seconds since epoch).

Reading Current Time

The get_unix_timestamp method reads the current time from the Data Register (dr):

let current_time = rtc.get_unix_timestamp();
println!("Current Unix timestamp: {}", current_time);

Setting Current Time

The set_unix_timestamp method writes to the Load Register (lr) to update the RTC:

let new_time = 1609459200; // January 1, 2021 00:00:00 UTC
rtc.set_unix_timestamp(new_time);

Time Operations Flow

flowchart TD
subgraph subGraph2["Hardware Registers"]
    DR_REG["DR (Data Register)Read Current Time"]
    LR_REG["LR (Load Register)Set New Time"]
end
subgraph subGraph1["Write Operations"]
    SET_CALL["set_unix_timestamp(time)"]
    WRITE_LR["write_volatile(lr)"]
    UPDATE["RTC Updated"]
end
subgraph subGraph0["Read Operations"]
    GET_CALL["get_unix_timestamp()"]
    READ_DR["read_volatile(dr)"]
    TIMESTAMP["u32 timestamp"]
end

GET_CALL --> READ_DR
READ_DR --> DR_REG
READ_DR --> TIMESTAMP
SET_CALL --> WRITE_LR
WRITE_LR --> LR_REG
WRITE_LR --> UPDATE

Sources: src/lib.rs(L63 - L74)  src/lib.rs(L135 - L157) 

Basic Interrupt Operations

The PL031 supports interrupt generation when the current time matches a preset value. This enables alarm functionality and periodic notifications.

Setting Up Interrupts

// Set a match timestamp (e.g., 1 hour from now)
let alarm_time = rtc.get_unix_timestamp() + 3600;
rtc.set_match_timestamp(alarm_time);

// Enable the interrupt
rtc.enable_interrupt(true);

Checking Interrupt Status

// Check if the match condition occurred
if rtc.matched() {
    println!("Time match occurred");
}

// Check if an interrupt is pending (matched + enabled)
if rtc.interrupt_pending() {
    println!("Interrupt is pending");
    
    // Clear the interrupt
    rtc.clear_interrupt();
}

Interrupt State Machine

stateDiagram-v2
[*] --> Idle : "enable_interrupt(false)"
Idle --> Armed : "set_match_timestamp() +enable_interrupt(true)"
Armed --> Matched : "RTC time == Match Register"
Matched --> Interrupted : "matched() == trueinterrupt_pending() == true"
Interrupted --> Idle : "clear_interrupt()"
Armed --> Idle : "enable_interrupt(false)"
Matched --> Idle : "enable_interrupt(false)"

Register-Level Interrupt Flow

flowchart TD
subgraph subGraph1["Hardware Registers"]
    MR["MR (Match Register)"]
    IMSC["IMSC (Interrupt Mask)"]
    RIS["RIS (Raw Interrupt Status)"]
    MIS["MIS (Masked Interrupt Status)"]
    ICR["ICR (Interrupt Clear)"]
end
subgraph subGraph0["Application API"]
    SET_MATCH["set_match_timestamp(time)"]
    ENABLE_INT["enable_interrupt(true)"]
    CHECK_PENDING["interrupt_pending()"]
    CLEAR_INT["clear_interrupt()"]
end

CHECK_PENDING --> MIS
CLEAR_INT --> ICR
ENABLE_INT --> IMSC
IMSC --> MIS
MR --> RIS
RIS --> MIS
SET_MATCH --> MR

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

Error Handling and Best Practices

Memory Safety

The driver maintains memory safety through careful use of volatile operations and proper Send/Sync implementations:

// The driver is Send + Sync safe
fn use_rtc_across_threads(rtc: Rtc) {
    std::thread::spawn(move || {
        let time = rtc.get_unix_timestamp(); // Safe from any thread
    });
}

Typical Usage Pattern

// 1. Initialize from device tree
let base_addr = get_pl031_base_from_device_tree();
let mut rtc = unsafe { Rtc::new(base_addr) };

// 2. Read current time
let current_time = rtc.get_unix_timestamp();

// 3. Set up periodic alarm (optional)
rtc.set_match_timestamp(current_time + 60); // 1 minute alarm
rtc.enable_interrupt(true);

// 4. In interrupt handler or polling loop
if rtc.interrupt_pending() {
    handle_rtc_alarm();
    rtc.clear_interrupt();
}

Sources: src/lib.rs(L123 - L128)  README.md(L9 - L14) 

Core Driver Implementation

Relevant source files

This document provides a detailed technical analysis of the core ARM PL031 RTC driver implementation. It covers the internal structure of the Rtc driver, register layout, hardware interface patterns, and safety mechanisms. This focuses specifically on the implementation details found in the main driver code.

For basic usage examples and integration guidance, see Basic Usage and Examples. For information about optional features like chrono integration, see Chrono Integration.

Driver Structure and Core Components

The core driver implementation centers around two primary structures that provide the foundation for all RTC operations.

Core Driver Architecture


The Rtc struct src/lib.rs(L42 - L44)  serves as the primary driver interface, containing a single field that points to the memory-mapped register layout. The Registers struct src/lib.rs(L15 - L39)  defines the exact memory layout of the PL031 hardware registers with proper alignment and padding.

Sources: src/lib.rs(L15 - L44) 

Register Layout and Hardware Interface

The driver implements a direct mapping to the ARM PL031 hardware registers through a carefully structured memory layout.

PL031 Register Mapping

RegisterTypeOffsetPurpose
dru320x00Data Register - current RTC value
mru320x04Match Register - alarm comparison value
lru320x08Load Register - sets RTC value
cru80x0CControl Register - enables RTC
imscu80x10Interrupt Mask Set/Clear
risu80x14Raw Interrupt Status
misu80x18Masked Interrupt Status
icru80x1CInterrupt Clear Register

The Registers struct uses #[repr(C, align(4))] src/lib.rs(L16)  to ensure proper memory alignment and layout compatibility with the hardware. Reserved padding fields like _reserved0: [u8; 3] src/lib.rs(L26)  maintain correct register spacing.

MMIO Access Patterns

All hardware access follows a consistent volatile read/write pattern:


The driver uses addr_of! and addr_of_mut! macros src/lib.rs(L66)  src/lib.rs(L73)  to create pointers to specific register fields, followed by volatile operations to ensure proper hardware communication without compiler optimizations.

Sources: src/lib.rs(L15 - L39)  src/lib.rs(L63 - L74) 

Core Time Operations

The driver provides fundamental time management through Unix timestamp operations, implementing both read and write access to the RTC hardware.

Time Reading and Writing

The get_unix_timestamp() method src/lib.rs(L63 - L67)  reads the current RTC value from the Data Register (dr), while set_unix_timestamp() src/lib.rs(L70 - L74)  writes to the Load Register (lr) to update the RTC time.

flowchart TD
subgraph subGraph1["Time Write Flow"]
    HW_UPDATE["Hardware updated"]
    subgraph subGraph0["Time Read Flow"]
        SET_CALL["set_unix_timestamp(unix_time)"]
        LR_WRITE["(*self.registers).lr"]
        VOLATILE_WRITE["write_volatile(unix_time)"]
        GET_CALL["get_unix_timestamp()"]
        DR_READ["(*self.registers).dr"]
        VOLATILE_READ["read_volatile()"]
        UNIX_TIME["u32 timestamp"]
    end
end

DR_READ --> VOLATILE_READ
GET_CALL --> DR_READ
LR_WRITE --> VOLATILE_WRITE
SET_CALL --> LR_WRITE
VOLATILE_READ --> UNIX_TIME
VOLATILE_WRITE --> HW_UPDATE

Both operations use 32-bit unsigned integers, matching the hardware register width. The choice of u32 over u64 was made in version 0.2.0 CHANGELOG.md(L14 - L15)  to align with the actual hardware capabilities.

Sources: src/lib.rs(L63 - L74)  CHANGELOG.md(L14 - L15) 

Interrupt Management System

The driver implements a comprehensive interrupt management system using the PL031's match register functionality and interrupt control registers.

Interrupt Control Flow


Interrupt Methods

MethodRegisterPurpose
set_match_timestamp()src/lib.rs78-82mrConfigure alarm time
enable_interrupt()src/lib.rs108-113imscEnable/disable interrupts
matched()src/lib.rs86-91risCheck if match occurred
interrupt_pending()src/lib.rs97-102misCheck if interrupt is pending
clear_interrupt()src/lib.rs116-120icrClear pending interrupt

The interrupt system operates by comparing the RTC value against the match register. When they match, the Raw Interrupt Status (ris) bit is set src/lib.rs(L89 - L90)  If interrupts are enabled via the Interrupt Mask (imsc), the Masked Interrupt Status (mis) will also be set src/lib.rs(L100 - L101) 

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

Memory Safety and Concurrency Model

The driver implements a carefully designed safety model that provides safe abstractions over unsafe hardware operations while supporting concurrent access.

Safety Boundaries


Constructor Safety Requirements

The new() constructor src/lib.rs(L56 - L60)  is marked unsafe and requires several safety guarantees:

  • Base address must point to valid PL031 MMIO registers
  • Memory must be mapped as device memory without aliases
  • Address must be aligned to 4-byte boundary
  • Caller must ensure exclusive access during construction

Thread Safety Implementation

The driver implements Send src/lib.rs(L124)  and Sync src/lib.rs(L128)  traits with careful safety reasoning:

  • Send: The Rtc struct contains only a pointer to device memory, which can be safely transferred between threads
  • Sync: Shared references (&Rtc) only allow reading device registers, which is safe for concurrent access

The set_unix_timestamp() method requires &mut self src/lib.rs(L70)  because it performs write operations, ensuring exclusive access for modifications while allowing concurrent reads.

Sources: src/lib.rs(L47 - L60)  src/lib.rs(L124 - L128)  CHANGELOG.md(L16 - L17)  CHANGELOG.md(L21) 

Driver Architecture and Design

Relevant source files

This page documents the internal architecture and design principles of the arm_pl031 RTC driver, focusing on the core Rtc struct, register layout, and hardware abstraction patterns. The material covers how the driver translates hardware register operations into safe Rust APIs while maintaining minimal overhead.

For hardware interface details and MMIO operations, see Hardware Interface and MMIO. For register-specific operations and meanings, see Register Operations.

Core Driver Structure

The driver architecture centers around two primary components: the Registers struct that defines the hardware memory layout, and the Rtc struct that provides the safe API interface.

Driver Components and Relationships

flowchart TD
subgraph subGraph3["Hardware Layer"]
    MMIO["Memory-Mapped I/O"]
    PL031["PL031 RTC Hardware"]
end
subgraph subGraph2["Unsafe Operations Layer"]
    VolatileOps["Volatile Operationsread_volatile()write_volatile()"]
    PtrOps["Pointer Operationsaddr_of!()addr_of_mut!()"]
end
subgraph subGraph1["Hardware Abstraction Layer"]
    Registers["Registers struct"]
    RegisterFields["Register Fieldsdr: u32mr: u32lr: u32cr: u8imsc: u8ris: u8mis: u8icr: u8"]
end
subgraph subGraph0["Driver API Layer"]
    Rtc["Rtc struct"]
    PublicMethods["Public Methodsget_unix_timestamp()set_unix_timestamp()set_match_timestamp()matched()interrupt_pending()enable_interrupt()clear_interrupt()"]
end

MMIO --> PL031
PtrOps --> MMIO
PublicMethods --> Registers
RegisterFields --> VolatileOps
Registers --> RegisterFields
Rtc --> PublicMethods
Rtc --> Registers
VolatileOps --> PtrOps

Sources: src/lib.rs(L42 - L44)  src/lib.rs(L15 - L39)  src/lib.rs(L46 - L121) 

Register Memory Layout

The Registers struct provides a direct mapping to the PL031 hardware register layout, ensuring proper alignment and padding for hardware compatibility.

flowchart TD
subgraph subGraph0["Memory Layout (repr C, align 4)"]
    DR["dr: u32Offset: 0x00Data Register"]
    MR["mr: u32Offset: 0x04Match Register"]
    LR["lr: u32Offset: 0x08Load Register"]
    CR["cr: u8Offset: 0x0CControl Register"]
    PAD1["_reserved0: [u8; 3]Offset: 0x0D-0x0F"]
    IMSC["imsc: u8Offset: 0x10Interrupt Mask"]
    PAD2["_reserved1: [u8; 3]Offset: 0x11-0x13"]
    RIS["ris: u8Offset: 0x14Raw Interrupt Status"]
    PAD3["_reserved2: [u8; 3]Offset: 0x15-0x17"]
    MIS["mis: u8Offset: 0x18Masked Interrupt Status"]
    PAD4["_reserved3: [u8; 3]Offset: 0x19-0x1B"]
    ICR["icr: u8Offset: 0x1CInterrupt Clear"]
    PAD5["_reserved4: [u8; 3]Offset: 0x1D-0x1F"]
end

Sources: src/lib.rs(L15 - L39) 

Driver Design Principles

Safety Boundary Architecture

The driver implements a clear safety boundary where all unsafe operations are contained within method implementations, exposing only safe APIs to users.

ComponentSafety LevelResponsibility
Rtc::new()UnsafeInitial pointer validation and construction
Public methodsSafeHigh-level operations with safety guarantees
Volatile operationsUnsafe (internal)Direct hardware register access
Pointer arithmeticUnsafe (internal)Register address calculation

State Management Design

The Rtc struct follows a stateless design pattern, storing only the base pointer to hardware registers without caching any hardware state.

flowchart TD
subgraph subGraph2["Operation Pattern"]
    Read["Read OperationsDirect volatile readsNo local caching"]
    Write["Write OperationsDirect volatile writesImmediate effect"]
end
subgraph subGraph1["Hardware State"]
    HardwareRegs["PL031 Hardware RegistersAll state lives in hardware"]
end
subgraph subGraph0["Rtc State"]
    BasePtr["registers: *mut RegistersBase pointer to MMIO region"]
end

BasePtr --> HardwareRegs
Read --> HardwareRegs
Write --> HardwareRegs

Sources: src/lib.rs(L42 - L44)  src/lib.rs(L63 - L66)  src/lib.rs(L70 - L73) 

Method Organization and Patterns

Functional Categories

The driver methods are organized into distinct functional categories, each following consistent patterns for hardware interaction.

CategoryMethodsPatternRegister Access
Time Managementget_unix_timestamp(),set_unix_timestamp()Read/Write DR/LRDirect timestamp operations
Alarm Managementset_match_timestamp(),matched()Write MR, Read RISMatch register operations
Interrupt Managementenable_interrupt(),interrupt_pending(),clear_interrupt()Read/Write IMSC/MIS/ICRInterrupt control flow

Memory Access Pattern

All hardware access follows a consistent volatile operation pattern using addr_of! and addr_of_mut! macros for safe pointer arithmetic.

flowchart TD

ReadAddr --> ReadVolatile
ReadStart --> ReadAddr
ReadVolatile --> ReadReturn
WriteAddr --> WriteVolatile
WriteStart --> WriteAddr
WriteVolatile --> WriteComplete

Sources: src/lib.rs(L63 - L66)  src/lib.rs(L70 - L73)  src/lib.rs(L108 - L112) 

Concurrency and Thread Safety

Send and Sync Implementation

The driver implements Send and Sync traits with explicit safety documentation, enabling safe usage across thread boundaries.

flowchart TD
subgraph subGraph2["Usage Patterns"]
    SharedRead["Shared Read AccessMultiple &Rtc referencesConcurrent timestamp reading"]
    ExclusiveWrite["Exclusive Write AccessSingle &mut Rtc referenceSerialized configuration changes"]
end
subgraph subGraph1["Safety Guarantees"]
    DeviceMemory["Device Memory PropertiesHardware provides atomicityNo local state to corrupt"]
    ReadSafety["Concurrent Read SafetyMultiple readers allowedHardware state remains consistent"]
end
subgraph subGraph0["Thread Safety Design"]
    SendImpl["unsafe impl Send for RtcDevice memory accessible from any context"]
    SyncImpl["unsafe impl Sync for RtcRead operations safe from multiple threads"]
end

DeviceMemory --> ExclusiveWrite
DeviceMemory --> SharedRead
ReadSafety --> SharedRead
SendImpl --> DeviceMemory
SyncImpl --> ReadSafety

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

Construction and Initialization

The driver construction follows an unsafe initialization pattern that requires explicit safety contracts from the caller.

Initialization Requirements

The new() method establishes the safety contract for proper driver operation:

  • Base address must point to valid PL031 MMIO registers
  • Memory must be mapped as device memory
  • No other aliases to the memory region
  • 4-byte alignment requirement

Sources: src/lib.rs(L46 - L60) 

Hardware Interface and MMIO

Relevant source files

This document details how the ARM PL031 RTC driver interfaces with the physical hardware through Memory-Mapped I/O (MMIO) operations. It covers the register layout, volatile access patterns, memory safety boundaries, and the hardware abstraction layer implementation.

For information about specific register operations and their meanings, see Register Operations. For details about the overall driver architecture, see Driver Architecture and Design.

MMIO Register Layout

The PL031 hardware interface is accessed through a well-defined memory-mapped register layout represented by the Registers struct. This struct provides a direct mapping to the hardware register space with proper alignment and padding.

PL031 Register Memory Layout

flowchart TD
subgraph subGraph0["MMIO Address Space"]
    BASE["Base Address(4-byte aligned)"]
    DR["DR (0x00)Data Register32-bit"]
    MR["MR (0x04)Match Register32-bit"]
    LR["LR (0x08)Load Register32-bit"]
    CR["CR (0x0C)Control Register8-bit + 3 reserved"]
    IMSC["IMSC (0x10)Interrupt Mask8-bit + 3 reserved"]
    RIS["RIS (0x14)Raw Interrupt Status8-bit + 3 reserved"]
    MIS["MIS (0x18)Masked Interrupt Status8-bit + 3 reserved"]
    ICR["ICR (0x1C)Interrupt Clear8-bit + 3 reserved"]
end

BASE --> DR
CR --> IMSC
DR --> MR
IMSC --> RIS
LR --> CR
MIS --> ICR
MR --> LR
RIS --> MIS

The Registers struct enforces proper memory layout through careful use of padding and alignment directives. Each register is positioned according to the PL031 specification, with reserved bytes maintaining proper spacing between hardware registers.

Sources: src/lib.rs(L15 - L39) 

Hardware Access Abstraction

The driver implements a three-layer abstraction for hardware access, ensuring both safety and performance while maintaining direct hardware control.

Hardware Access Layers

flowchart TD
subgraph subGraph1["Hardware Layer"]
    MMIO["Memory-Mapped I/ODevice Memory Region"]
    REGS["PL031 Hardware RegistersPhysical Hardware"]
end
subgraph subGraph0["Software Layers"]
    API["Public API Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"]
    UNSAFE["Unsafe Hardware Accessaddr_of!()read_volatile()write_volatile()"]
    PTR["Raw Pointer Operations(*self.registers).dr(*self.registers).mr"]
end

API --> UNSAFE
MMIO --> REGS
PTR --> MMIO
UNSAFE --> PTR

The Rtc struct maintains a single *mut Registers pointer that serves as the bridge between safe Rust code and raw hardware access. All hardware operations go through volatile memory operations to ensure proper interaction with device memory.

Sources: src/lib.rs(L42 - L44)  src/lib.rs(L56 - L60) 

Volatile Memory Operations

All hardware register access uses volatile operations to prevent compiler optimizations that could interfere with hardware behavior. The driver employs a consistent pattern for both read and write operations.

MMIO Operation Patterns

sequenceDiagram
    participant PublicAPI as "Public API"
    participant UnsafeBlock as "Unsafe Block"
    participant addr_ofaddr_of_mut as "addr_of!/addr_of_mut!"
    participant read_volatilewrite_volatile as "read_volatile/write_volatile"
    participant PL031Hardware as "PL031 Hardware"

    Note over PublicAPI,PL031Hardware: Read Operation Pattern
    PublicAPI ->> UnsafeBlock: Call hardware access
    UnsafeBlock ->> addr_ofaddr_of_mut: addr_of!((*registers).field)
    addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .read_volatile()
    read_volatilewrite_volatile ->> PL031Hardware: Load from device memory
    PL031Hardware -->> read_volatilewrite_volatile: Register value
    read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Raw value
    addr_ofaddr_of_mut -->> UnsafeBlock: Typed value
    UnsafeBlock -->> PublicAPI: Safe result
    Note over PublicAPI,PL031Hardware: Write Operation Pattern
    PublicAPI ->> UnsafeBlock: Call hardware access
    UnsafeBlock ->> addr_ofaddr_of_mut: addr_of_mut!((*registers).field)
    addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .write_volatile(value)
    read_volatilewrite_volatile ->> PL031Hardware: Store to device memory
    PL031Hardware -->> read_volatilewrite_volatile: Write acknowledgment
    read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Write complete
    addr_ofaddr_of_mut -->> UnsafeBlock: Operation complete
    UnsafeBlock -->> PublicAPI: Safe completion

The driver uses addr_of! and addr_of_mut! macros to safely obtain field addresses from the raw pointer without creating intermediate references, avoiding undefined behavior while maintaining direct hardware access.

Sources: src/lib.rs(L63 - L67)  src/lib.rs(L70 - L74)  src/lib.rs(L78 - L82) 

Memory Safety Model

The hardware interface implements a carefully designed safety model that isolates unsafe operations while providing safe public APIs. The safety boundaries are clearly defined and documented.

Operation TypeSafety LevelAccess PatternUsage
ConstructionUnsafeRtc::new()Requires valid MMIO base address
Register ReadSafeget_unix_timestamp()Volatile read through safe wrapper
Register WriteSafeset_unix_timestamp()Volatile write through safe wrapper
Interrupt ControlSafeenable_interrupt()Masked volatile operations

The Rtc struct implements Send and Sync traits with careful safety justification, allowing the driver to be used in multi-threaded contexts while maintaining memory safety guarantees.

Safety Boundary Implementation

flowchart TD
subgraph subGraph2["Hardware Zone"]
    DEVICE_MEM["Device Memory"]
    PL031_HW["PL031 Hardware"]
end
subgraph subGraph1["Unsafe Zone"]
    UNSAFE_BLOCKS["unsafe blocks"]
    RAW_PTR["*mut Registers"]
    VOLATILE_OPS["volatile operations"]
end
subgraph subGraph0["Safe Zone"]
    SAFE_API["Safe Public Methods"]
    SAFE_REFS["&self / &mut self"]
end

RAW_PTR --> DEVICE_MEM
SAFE_API --> UNSAFE_BLOCKS
SAFE_REFS --> RAW_PTR
UNSAFE_BLOCKS --> VOLATILE_OPS
VOLATILE_OPS --> PL031_HW

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

Address Space Configuration

The hardware interface requires proper address space configuration to function correctly. The base address must point to a valid PL031 device memory region with appropriate mapping characteristics.

Address Space Requirements

flowchart TD
subgraph subGraph2["Safety Requirements"]
    EXCLUSIVE["Exclusive AccessNo other aliases"]
    VALID["Valid MappingProcess address space"]
    DEVICE["Device Memory TypeProper memory attributes"]
end
subgraph subGraph1["Physical Hardware"]
    PL031_DEV["PL031 DeviceHardware Registers"]
    MMIO_REGION["MMIO Region32-byte space"]
end
subgraph subGraph0["Virtual Address Space"]
    BASE_ADDR["Base Address(from device tree)"]
    ALIGN["4-byte AlignmentRequired"]
    MAPPING["Device Memory MappingNon-cacheable"]
end

ALIGN --> MAPPING
BASE_ADDR --> ALIGN
BASE_ADDR --> EXCLUSIVE
MAPPING --> PL031_DEV
MAPPING --> VALID
MMIO_REGION --> DEVICE
PL031_DEV --> MMIO_REGION

The driver expects the base address to be obtained from platform-specific sources such as device tree configuration, and requires the caller to ensure proper memory mapping with device memory characteristics.

Sources: src/lib.rs(L47 - L60) 

Register Operations

Relevant source files

This page provides comprehensive documentation of the PL031 register operations implemented in the ARM PL031 RTC driver. It covers the register memory layout, volatile access patterns, and the meaning of each hardware register. For information about the broader hardware interface and MMIO concepts, see Hardware Interface and MMIO. For details about interrupt-specific register usage, see Interrupt Handling.

Register Layout and Memory Mapping

The PL031 device exposes its functionality through a set of memory-mapped registers. The driver defines these registers using a C-compatible structure that matches the hardware layout exactly.

Memory Layout Structure

The register layout is defined in the Registers struct which uses precise memory alignment and padding to match the PL031 hardware specification:

flowchart TD
subgraph subGraph0["Registers Struct Layout"]
    DR["dr: u32Data RegisterOffset: 0x00"]
    MR["mr: u32Match RegisterOffset: 0x04"]
    LR["lr: u32Load RegisterOffset: 0x08"]
    CR["cr: u8 + paddingControl RegisterOffset: 0x0C"]
    IMSC["imsc: u8 + paddingInterrupt MaskOffset: 0x10"]
    RIS["ris: u8 + paddingRaw Interrupt StatusOffset: 0x14"]
    MIS["mis: u8 + paddingMasked Interrupt StatusOffset: 0x18"]
    ICR["icr: u8 + paddingInterrupt ClearOffset: 0x1C"]
end

CR --> IMSC
DR --> MR
IMSC --> RIS
LR --> CR
MIS --> ICR
MR --> LR
RIS --> MIS

The structure uses #[repr(C, align(4))] to ensure proper memory layout and alignment with the hardware expectations. Reserved fields with specific padding ensure that each register appears at the correct memory offset.

Sources: src/lib.rs(L15 - L39) 

Register Access Patterns

All register operations use volatile memory access to prevent compiler optimizations that could interfere with hardware communication:

flowchart TD
subgraph subGraph1["Write Operations"]
    subgraph subGraph0["Read Operations"]
        WRITE_START["Function Call"]
        WRITE_PTR["addr_of_mut!((*registers).field)"]
        WRITE_VOLATILE["write_volatile(value)"]
        WRITE_COMPLETE["Hardware Updated"]
        READ_START["Function Call"]
        READ_PTR["addr_of!((*registers).field)"]
        READ_VOLATILE["read_volatile()"]
        READ_RESULT["Return Value"]
    end
end

READ_PTR --> READ_VOLATILE
READ_START --> READ_PTR
READ_VOLATILE --> READ_RESULT
WRITE_PTR --> WRITE_VOLATILE
WRITE_START --> WRITE_PTR
WRITE_VOLATILE --> WRITE_COMPLETE

Sources: src/lib.rs(L64 - L66)  src/lib.rs(L70 - L74)  src/lib.rs(L78 - L82) 

Individual Register Operations

Data Register (DR)

The Data Register provides read-only access to the current RTC value as a 32-bit Unix timestamp.

FieldTypeAccessPurpose
dru32Read-onlyCurrent time in seconds since Unix epoch

The get_unix_timestamp() method reads this register:

sequenceDiagram
    participant Application as "Application"
    participant Rtcget_unix_timestamp as "Rtc::get_unix_timestamp()"
    participant PL031Hardware as "PL031 Hardware"

    Application ->> Rtcget_unix_timestamp: "get_unix_timestamp()"
    Rtcget_unix_timestamp ->> PL031Hardware: "addr_of!((*registers).dr).read_volatile()"
    PL031Hardware -->> Rtcget_unix_timestamp: "u32 timestamp"
    Rtcget_unix_timestamp -->> Application: "u32 timestamp"

Sources: src/lib.rs(L18)  src/lib.rs(L62 - L67) 

Load Register (LR)

The Load Register allows setting the current RTC time by writing a 32-bit Unix timestamp.

FieldTypeAccessPurpose
lru32Write-onlySets current time in seconds since Unix epoch

The set_unix_timestamp() method writes to this register:

Sources: src/lib.rs(L22)  src/lib.rs(L69 - L74) 

Match Register (MR)

The Match Register stores a timestamp value that triggers an interrupt when the RTC time matches it.

FieldTypeAccessPurpose
mru32Write-onlyAlarm time for interrupt generation

The set_match_timestamp() method configures this register:

Sources: src/lib.rs(L20)  src/lib.rs(L76 - L82) 

Interrupt Control Registers

The PL031 provides several registers for interrupt management:

RegisterFieldTypePurpose
IMSCimscu8Interrupt mask control (enable/disable)
RISrisu8Raw interrupt status (always shows match state)
MISmisu8Masked interrupt status (shows pending interrupts)
ICRicru8Interrupt clear (write to acknowledge)
flowchart TD
subgraph subGraph0["Interrupt Register Flow"]
    MATCH["Hardware Match Event"]
    RIS_SET["RIS[0] = 1"]
    IMSC_CHECK["Check IMSC[0]"]
    MIS_SET["MIS[0] = RIS[0] & IMSC[0]"]
    IRQ["Interrupt Signal"]
    ICR_WRITE["Write ICR[0] = 1"]
    CLEAR["Clear RIS[0] and MIS[0]"]
end

ICR_WRITE --> CLEAR
IMSC_CHECK --> MIS_SET
IRQ --> ICR_WRITE
MATCH --> RIS_SET
MIS_SET --> IRQ
RIS_SET --> IMSC_CHECK

Sources: src/lib.rs(L27 - L38)  src/lib.rs(L104 - L113)  src/lib.rs(L115 - L120) 

Volatile Access Safety

All register operations are performed within unsafe blocks due to the raw pointer dereference required for MMIO access. The driver ensures safety through several mechanisms:

Memory Safety Guarantees

  • The Rtc::new() constructor requires the caller to provide safety guarantees about the base address
  • All register access uses addr_of! and addr_of_mut! macros to create properly aligned pointers
  • Volatile operations prevent compiler optimizations that could reorder or eliminate hardware accesses
flowchart TD
subgraph subGraph1["Safety Documentation"]
    DOC_NEW["Safety doc for new()"]
    DOC_BLOCKS["Safety comments in blocks"]
end
subgraph subGraph0["Safety Boundaries"]
    UNSAFE_NEW["unsafe fn new(base_address)"]
    SAFE_METHODS["Safe public methods"]
    UNSAFE_BLOCKS["Unsafe register access blocks"]
    HARDWARE["Hardware registers"]
end

DOC_BLOCKS --> UNSAFE_BLOCKS
DOC_NEW --> UNSAFE_NEW
SAFE_METHODS --> UNSAFE_BLOCKS
UNSAFE_BLOCKS --> HARDWARE
UNSAFE_NEW --> SAFE_METHODS

Sources: src/lib.rs(L51 - L60)  src/lib.rs(L64 - L66)  src/lib.rs(L72 - L74) 

Register Field Access Patterns

The driver uses consistent patterns for accessing register fields:

OperationPatternExample
Read u32addr_of!((*registers).field).read_volatile()DR, RIS, MIS
Write u32addr_of_mut!((*registers).field).write_volatile(value)LR, MR
Read u8addr_of!((*registers).field).read_volatile()Status registers
Write u8addr_of_mut!((*registers).field).write_volatile(value)Control registers

All register operations include safety comments explaining why the unsafe operation is valid within the context of the driver's safety guarantees.

Sources: src/lib.rs(L64 - L74)  src/lib.rs(L88 - L90)  src/lib.rs(L99 - L101)  src/lib.rs(L111 - L112)  src/lib.rs(L118 - L119) 

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) 

Memory Safety and Concurrency

Relevant source files

This page covers the memory safety model and concurrency characteristics of the ARM PL031 RTC driver. It explains how the driver maintains safety while performing low-level hardware operations, the boundaries between safe and unsafe code, and the thread safety guarantees provided by the implementation.

For information about the core driver architecture, see Driver Architecture and Design. For details about hardware register operations, see Register Operations.

Safety Model and Unsafe Boundaries

The arm_pl031 driver implements a layered safety model that encapsulates all unsafe hardware operations within well-defined boundaries while providing safe APIs to consumers.

Unsafe Code Isolation

The driver concentrates all unsafe operations within the core Rtc implementation, creating a clear safety boundary:

flowchart TD
subgraph subGraph2["Hardware Layer"]
    REGISTERS["PL031 Registers*mut Registers"]
    DEVICE_MEMORY["Device MemoryMMIO Region"]
end
subgraph subGraph1["Unsafe Boundary"]
    CONSTRUCTOR["unsafe fn new()Raw pointer validation"]
    MMIO_OPS["MMIO Operationsread_volatile()write_volatile()"]
end
subgraph subGraph0["Safe API Layer"]
    PUBLIC_API["Public Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"]
end

CONSTRUCTOR --> REGISTERS
MMIO_OPS --> REGISTERS
PUBLIC_API --> CONSTRUCTOR
PUBLIC_API --> MMIO_OPS
REGISTERS --> DEVICE_MEMORY

Sources: src/lib.rs(L46 - L60)  src/lib.rs(L63 - L120) 

Constructor Safety Requirements

The unsafe fn new() constructor establishes the fundamental safety contract for the entire driver. The caller must guarantee several critical invariants:

Safety RequirementDescriptionValidation
Valid MMIO MappingBase address points to mapped PL031 registersCaller responsibility
Exclusive AccessNo other aliases to the memory regionCaller responsibility
Device Memory TypeMemory mapped as device memory, not normal memoryCaller responsibility
AlignmentAddress aligned to 4-byte boundaryEnforced by#[repr(C, align(4))]

Sources: src/lib.rs(L51 - L60) 

Hardware Register Access Safety

The driver uses specific patterns to ensure safe access to hardware registers while maintaining proper volatile semantics.

Volatile Access Pattern

All register operations use volatile access through addr_of! and addr_of_mut! macros to prevent undefined behavior:

flowchart TD
subgraph Hardware["Hardware"]
    DR_REGISTER["DR RegisterData Register"]
end
subgraph subGraph1["Unsafe Block"]
    ADDR_OF["addr_of!((*self.registers).dr)"]
    READ_VOLATILE["read_volatile()"]
end
subgraph subGraph0["Safe Method Call"]
    METHOD["get_unix_timestamp()"]
end

ADDR_OF --> READ_VOLATILE
METHOD --> ADDR_OF
READ_VOLATILE --> DR_REGISTER

Sources: src/lib.rs(L63 - L67)  src/lib.rs(L70 - L74) 

Register Layout Safety

The Registers struct uses precise memory layout control to match hardware expectations:

flowchart TD
subgraph subGraph1["Layout Attributes"]
    REPR_C["#[repr(C)]C-compatible layout"]
    ALIGN_4["#[repr(align(4))]4-byte alignment"]
end
subgraph subGraph0["Memory Layout"]
    DR["dr: u32Offset 0x00"]
    MR["mr: u32Offset 0x04"]
    LR["lr: u32Offset 0x08"]
    CR["cr: u8 + paddingOffset 0x0C"]
    IMSC["imsc: u8 + paddingOffset 0x10"]
    RIS["ris: u8 + paddingOffset 0x14"]
    MIS["mis: u8 + paddingOffset 0x18"]
    ICR["icr: u8 + paddingOffset 0x1C"]
end

CR --> IMSC
DR --> MR
IMSC --> RIS
LR --> CR
MIS --> ICR
MR --> LR
REPR_C --> DR
RIS --> MIS

Sources: src/lib.rs(L15 - L39) 

Thread Safety and Concurrency Model

The driver implements explicit thread safety through manual Send and Sync trait implementations with documented safety reasoning.

Send Implementation

The Send trait implementation allows the Rtc instance to be transferred between threads:

flowchart TD
subgraph Hardware["Hardware"]
    DEVICE["PL031 DeviceDevice Memory"]
end
subgraph subGraph1["Thread B"]
    RTC_B["Rtc instance"]
    ACCESS_B["Hardware access"]
end
subgraph subGraph0["Thread A"]
    RTC_A["Rtc instance"]
    MOVE["move rtc"]
end

ACCESS_B --> DEVICE
MOVE --> RTC_B
RTC_A --> MOVE
RTC_B --> ACCESS_B

The implementation is safe because device memory can be accessed from any thread context.

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

Sync Implementation

The Sync trait implementation allows shared references across threads:

flowchart TD
subgraph Hardware["Hardware"]
    REGISTERS["PL031 RegistersRead-only operations"]
end
subgraph subGraph2["Shared Instance"]
    RTC_SHARED["Rtc instance"]
end
subgraph subGraph1["Thread 2"]
    REF2["&Rtc"]
    READ2["matched()"]
end
subgraph subGraph0["Thread 1"]
    REF1["&Rtc"]
    READ1["get_unix_timestamp()"]
end

READ1 --> REGISTERS
READ2 --> REGISTERS
REF1 --> RTC_SHARED
REF2 --> RTC_SHARED

The implementation is safe because shared references only allow read operations on device registers, which can be performed concurrently.

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

Mutation Safety

Methods requiring &mut self ensure exclusive access for write operations:

Method CategoryReference TypeThread SafetyHardware Impact
Read Operations&selfConcurrent safeRead-only register access
Write Operations&mut selfExclusive accessRegister modification
Interrupt Management&mut selfExclusive accessControl register writes

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

Memory Safety Guarantees

The driver provides several layers of memory safety guarantees through its design:

Pointer Validity

Once constructed with a valid base address, all register accesses are guaranteed to be within the mapped MMIO region:

flowchart TD
subgraph subGraph1["Runtime Guarantees"]
    NO_UB["No undefined behaviorAll accesses valid"]
    ATOMIC_OPS["Atomic register operationsSingle instruction access"]
    CONSISTENT_STATE["Consistent hardware stateProper ordering"]
end
subgraph subGraph0["Safety Invariants"]
    VALID_PTR["Valid base pointerEstablished in new()"]
    BOUNDED_ACCESS["Bounded register accessWithin Registers struct"]
    VOLATILE_OPS["Volatile operationsProper device memory handling"]
end

BOUNDED_ACCESS --> ATOMIC_OPS
VALID_PTR --> NO_UB
VOLATILE_OPS --> CONSISTENT_STATE

Sources: src/lib.rs(L56 - L60)  src/lib.rs(L15 - L39) 

Strict Safety Lints

The crate enforces strict safety requirements through compiler lints:

  • clippy::missing_safety_doc: All unsafe functions must have safety documentation
  • clippy::undocumented_unsafe_blocks: All unsafe blocks must have safety comments
  • unsafe_op_in_unsafe_fn: Unsafe operations in unsafe functions must be explicit

Sources: src/lib.rs(L4 - L8) 

This comprehensive safety model ensures that despite the low-level hardware interaction, the driver maintains Rust's memory safety guarantees while providing efficient concurrent access to the RTC hardware.

Features and Extensions

Relevant source files

This document covers the optional features and extensions available in the arm_pl031 crate. These features provide additional functionality beyond the core RTC driver implementation, allowing developers to choose the appropriate level of abstraction and functionality for their specific use cases.

For information about the core driver implementation and basic functionality, see Core Driver Implementation. For details about configuration and build options, see Development and Contributing.

Feature Overview

The arm_pl031 crate provides a modular architecture where additional functionality can be enabled through feature flags. This design keeps the core driver lightweight while allowing optional enhancements for improved developer experience.

Available Features

FeatureDescriptionDefaultDependencies
chronoHigh-level date/time operations using chrono crateEnabledchrono v0.4.38

The crate follows a minimal core approach where the base functionality requires no external dependencies, while optional features add convenience layers on top of the core API.

Feature Architecture

flowchart TD
subgraph subGraph3["Hardware Layer"]
    PL031_HW["PL031 Hardware"]
end
subgraph subGraph2["Core Driver"]
    CORE_API["Core RTC API"]
    UNIX_TS["Unix Timestamp Operations"]
    REGISTERS["Register Access"]
end
subgraph subGraph1["Feature Extensions"]
    CHRONO_API["chrono Feature API"]
    DATETIME["DateTime Operations"]
end
subgraph subGraph0["Application Layer"]
    APP["Application Code"]
end

APP --> CHRONO_API
APP --> CORE_API
CHRONO_API --> DATETIME
CORE_API --> UNIX_TS
DATETIME --> UNIX_TS
REGISTERS --> PL031_HW
UNIX_TS --> REGISTERS

Sources: Cargo.toml(L14 - L19)  src/chrono.rs(L1 - L26) 

Feature Integration Architecture

The feature system in arm_pl031 uses Rust's conditional compilation to provide optional functionality without affecting the core driver when features are disabled.

Chrono Feature Integration

flowchart TD
subgraph subGraph3["Core Dependencies"]
    CHRONO_CRATE["chrono = '0.4.38'"]
    DATETIME_UTC["DateTime"]
    TRY_FROM_INT["TryFromIntError"]
end
subgraph subGraph2["API Methods"]
    GET_TIME["get_time()"]
    SET_TIME["set_time()"]
    SET_MATCH["set_match()"]
end
subgraph subGraph1["Implementation Modules"]
    LIB_RS["src/lib.rs"]
    CHRONO_RS["src/chrono.rs"]
    CHRONO_IMPL["Rtc impl block"]
end
subgraph subGraph0["Feature Flag System"]
    CARGO_FEATURES["Cargo.toml features"]
    CHRONO_FLAG["chrono = ['dep:chrono']"]
    DEFAULT_FLAG["default = ['chrono']"]
end

CARGO_FEATURES --> CHRONO_FLAG
CARGO_FEATURES --> DEFAULT_FLAG
CHRONO_CRATE --> DATETIME_UTC
CHRONO_CRATE --> TRY_FROM_INT
CHRONO_FLAG --> CHRONO_CRATE
CHRONO_FLAG --> CHRONO_RS
CHRONO_IMPL --> GET_TIME
CHRONO_IMPL --> SET_MATCH
CHRONO_IMPL --> SET_TIME
CHRONO_RS --> CHRONO_IMPL
GET_TIME --> DATETIME_UTC
SET_MATCH --> TRY_FROM_INT
SET_TIME --> TRY_FROM_INT

Sources: Cargo.toml(L15 - L19)  src/chrono.rs(L1 - L4) 

API Layer Mapping

The feature system creates distinct API layers that build upon each other, allowing developers to choose the appropriate abstraction level.

Method Resolution and Type Conversion

flowchart TD
subgraph subGraph2["Type Conversions"]
    UTC_TIMESTAMP["Utc.timestamp_opt()"]
    TIMESTAMP_TRY_INTO["timestamp().try_into()"]
    INTO_CONVERSION["into()"]
end
subgraph subGraph1["Core API (always available)"]
    GET_UNIX["get_unix_timestamp() -> u32"]
    SET_UNIX["set_unix_timestamp(u32)"]
    SET_MATCH_TS["set_match_timestamp(u32)"]
end
subgraph subGraph0["High-Level API (chrono feature)"]
    GET_TIME_CHRONO["get_time() -> DateTime"]
    SET_TIME_CHRONO["set_time(DateTime) -> Result<(), TryFromIntError>"]
    SET_MATCH_CHRONO["set_match(DateTime) -> Result<(), TryFromIntError>"]
end

GET_TIME_CHRONO --> GET_UNIX
GET_TIME_CHRONO --> UTC_TIMESTAMP
GET_UNIX --> INTO_CONVERSION
SET_MATCH_CHRONO --> TIMESTAMP_TRY_INTO
SET_TIME_CHRONO --> TIMESTAMP_TRY_INTO
TIMESTAMP_TRY_INTO --> SET_MATCH_TS
TIMESTAMP_TRY_INTO --> SET_UNIX

Sources: src/chrono.rs(L6 - L25) 

Configuration and Build Integration

The feature system integrates with Cargo's dependency resolution and compilation process to provide clean separation between core and optional functionality.

Dependency Management

The chrono feature demonstrates careful dependency management:

  • Optional Dependency: The chrono crate is marked as optional = true in Cargo.toml(L15) 
  • No Default Features: chrono is imported with default-features = false to maintain minimal footprint
  • Feature Gate: The dep:chrono syntax ensures the dependency is only included when the feature is enabled
  • Default Enabled: The feature is enabled by default for convenience but can be disabled for minimal builds

Build Configuration Matrix

Feature StateDependenciesAPI AvailableUse Case
--no-default-featuresNoneCore timestamp API onlyMinimal embedded systems
--features chronochrono v0.4.38Core + DateTime APIStandard embedded applications
Defaultchrono v0.4.38Core + DateTime APIGeneral purpose usage

Sources: Cargo.toml(L14 - L19) 

Error Handling Strategy

The feature extensions implement consistent error handling patterns that integrate with the core driver's safety model.

Error Type Integration

The chrono feature introduces TryFromIntError for handling timestamp conversion failures that can occur when converting between DateTime<Utc> (using i64 timestamps) and the PL031's u32 register format.

Error Flow Pattern:

  1. DateTime<Utc> provides timestamp() returning i64
  2. Conversion to u32 via try_into() may fail for out-of-range values
  3. Methods return Result<(), TryFromIntError> to propagate conversion errors
  4. Core driver methods remain infallible for valid u32 inputs

This design ensures that type safety is maintained across the feature boundary while preserving the core driver's simple error model.

Sources: src/chrono.rs(L3)  src/chrono.rs(L15 - L17)  src/chrono.rs(L22 - L24) 

Chrono Integration

Relevant source files

This document covers the optional chrono feature integration that provides high-level DateTime operations for the ARM PL031 RTC driver. This feature extends the core Unix timestamp API with convenient date and time handling using the chrono crate.

For information about the core driver functionality and Unix timestamp operations, see Core Driver Implementation. For details about enabling and configuring the chrono feature, see Feature Configuration.

Purpose and Scope

The chrono integration provides a type-safe, ergonomic API layer over the core RTC driver by converting between Unix timestamps and DateTime<Utc> objects. This feature is enabled by default but can be disabled for minimal deployments that only need raw timestamp operations.

The integration handles timezone considerations by standardizing on UTC, performs bounds checking for timestamp conversions, and maintains the same interrupt and match functionality as the core API while operating on DateTime objects.

API Overview

The chrono integration extends the Rtc struct with three additional methods that mirror the core timestamp operations but operate on DateTime<Utc> types.

DateTime Operations

MethodCore EquivalentPurpose
get_time()get_unix_timestamp()Read current time as DateTime
set_time()set_unix_timestamp()Set current time from DateTime
set_match()set_match_timestamp()Set match time for interrupts

Method Implementation Details

Time Reading

The get_time() method provides a safe conversion from the hardware's 32-bit Unix timestamp to a proper DateTime object:

flowchart TD
RTC["Rtc::get_time()"]
CORE["get_unix_timestamp()"]
CONVERT["Utc.timestamp_opt()"]
UNWRAP["unwrap()"]
DATETIME["DateTime"]

CONVERT --> UNWRAP
CORE --> CONVERT
RTC --> CORE
UNWRAP --> DATETIME

Chrono DateTime Conversion Flow

The conversion uses chrono::Utc::timestamp_opt() with nanoseconds set to 0, since the PL031 hardware only provides second-level precision. The unwrap() is safe because valid PL031 timestamps always fall within chrono's supported range.

Sources: src/chrono.rs(L6 - L10) 

Time Setting

The set_time() method handles the reverse conversion with proper error handling for out-of-bounds timestamps:

flowchart TD
INPUT["DateTime"]
TIMESTAMP["time.timestamp()"]
CONVERT["try_into()?"]
SUCCESS["set_unix_timestamp()"]
ERROR["TryFromIntError"]

CONVERT --> ERROR
CONVERT --> SUCCESS
INPUT --> TIMESTAMP
TIMESTAMP --> CONVERT

DateTime to Timestamp Conversion with Error Handling

The method returns Result<(), TryFromIntError> to handle cases where the DateTime represents a time outside the PL031's supported range (Unix timestamps that don't fit in 32 bits).

Sources: src/chrono.rs(L12 - L18) 

Match Register Integration

The set_match() method extends the interrupt functionality to work with DateTime objects:

flowchart TD
subgraph subGraph2["Hardware Layer"]
    PL031_MR["PL031 Match Register"]
    INTERRUPT_GEN["Interrupt Generation"]
end
subgraph subGraph1["Core Driver Layer"]
    SET_MATCH_TS["set_match_timestamp(u32)"]
    MATCH_REG["write_volatile(MR)"]
end
subgraph subGraph0["Chrono API Layer"]
    SET_MATCH["set_match(DateTime)"]
    CONVERT_MATCH["match_time.timestamp().try_into()?"]
end

CONVERT_MATCH --> SET_MATCH_TS
MATCH_REG --> PL031_MR
PL031_MR --> INTERRUPT_GEN
SET_MATCH --> CONVERT_MATCH
SET_MATCH_TS --> MATCH_REG

Match Register DateTime Integration

This allows applications to schedule interrupts using natural DateTime objects rather than calculating Unix timestamps manually.

Sources: src/chrono.rs(L20 - L26) 

Error Handling and Type Safety

Conversion Error Types

The chrono integration uses TryFromIntError to handle timestamp conversion failures. This occurs when attempting to convert a DateTime that represents a time outside the range of a 32-bit Unix timestamp (before 1970 or after 2106).

flowchart TD
DATETIME["DateTime"]
TIMESTAMP["i64 timestamp"]
CHECK["fits in u32?"]
SUCCESS["u32 timestamp"]
ERROR["TryFromIntError"]
CORE_API["Core RTC API"]

CHECK --> ERROR
CHECK --> SUCCESS
DATETIME --> TIMESTAMP
SUCCESS --> CORE_API
TIMESTAMP --> CHECK

Timestamp Range Validation

Applications should handle these errors appropriately, either by validating DateTime inputs or providing fallback behavior for out-of-range times.

UTC Timezone Handling

The integration standardizes on UTC timezone to avoid complex timezone conversion logic in embedded systems. This ensures consistent behavior across different deployment environments and simplifies the API surface.

Sources: src/chrono.rs(L1 - L3) 

Integration with Core Functionality

The chrono methods are thin wrappers around the core driver functionality, maintaining the same safety properties and hardware access patterns while providing a more convenient interface.

flowchart TD
subgraph Hardware["Hardware"]
    PL031["ARM PL031 RTC"]
end
subgraph subGraph2["Core Driver (lib.rs)"]
    GET_UNIX["get_unix_timestamp()"]
    SET_UNIX["set_unix_timestamp()"]
    SET_MATCH_UNIX["set_match_timestamp()"]
    REGISTERS["Register Operations"]
end
subgraph subGraph1["Chrono Feature (chrono.rs)"]
    GET_TIME["get_time()"]
    SET_TIME["set_time()"]
    SET_MATCH_CHRONO["set_match()"]
end
subgraph subGraph0["Application Code"]
    APP_CHRONO["DateTime Operations"]
    APP_CORE["Unix Timestamp Operations"]
end

APP_CHRONO --> GET_TIME
APP_CHRONO --> SET_MATCH_CHRONO
APP_CHRONO --> SET_TIME
APP_CORE --> GET_UNIX
APP_CORE --> SET_MATCH_UNIX
APP_CORE --> SET_UNIX
GET_TIME --> GET_UNIX
GET_UNIX --> REGISTERS
REGISTERS --> PL031
SET_MATCH_CHRONO --> SET_MATCH_UNIX
SET_MATCH_UNIX --> REGISTERS
SET_TIME --> SET_UNIX
SET_UNIX --> REGISTERS

Chrono Integration Architecture

This design allows applications to choose the appropriate abstraction level: high-level DateTime operations for convenience, or low-level timestamp operations for performance-critical code.

Sources: src/chrono.rs(L1 - L27) 

Feature Configuration

Relevant source files

This document covers the available feature flags, no_std compatibility settings, and build configuration options for the arm_pl031 crate. It explains how to customize the driver's functionality through feature selection and target-specific compilation settings.

For information about the chrono-specific functionality enabled by features, see Chrono Integration. For build and testing procedures across different targets, see Building and Testing.

Feature Flags Overview

The arm_pl031 crate provides a minimal set of feature flags to control optional functionality while maintaining a lightweight core driver suitable for embedded environments.

Available Features

FeatureDescriptionDefaultDependencies
chronoEnables DateTime integration with the chrono crate✓ Enabledchrono = "0.4.38"

Feature Dependencies Diagram

flowchart TD
subgraph subGraph1["Optional Enhancement"]
    CHRONO_IMPL["DateTime<Utc> support"]
end
subgraph subGraph0["Always Available"]
    BASIC_API["Unix timestamp API"]
    INTERRUPT_API["Interrupt management"]
    REGISTER_API["Register operations"]
end
DEFAULT["default features"]
CHRONO_FEAT["chrono feature"]
CHRONO_DEP["chrono crate v0.4.38"]
CORE["Core RTC Driver"]
NO_CHRONO["without chrono feature"]

CHRONO_DEP --> CHRONO_IMPL
CHRONO_FEAT --> CHRONO_DEP
CHRONO_IMPL --> CORE
CORE --> BASIC_API
CORE --> INTERRUPT_API
CORE --> REGISTER_API
DEFAULT --> CHRONO_FEAT
NO_CHRONO --> CORE

Sources: Cargo.toml(L17 - L19) 

Core Driver (Always Available)

The core functionality remains available regardless of feature selection:

  • Unix timestamp operations via get_unix_timestamp() and set_unix_timestamp()
  • Interrupt management through match registers
  • Direct register access for all PL031 hardware features
  • Memory-mapped I/O operations

Chrono Feature

When the chrono feature is enabled (default), the driver provides additional convenience methods:

  • get_time() returns Result<DateTime<Utc>, chrono::ParseError>
  • set_time() accepts DateTime<Utc> parameters
  • Automatic conversion between Unix timestamps and RFC 3339 formatted strings

Sources: Cargo.toml(L15)  Cargo.toml(L18) 

Default Configuration

The crate ships with the chrono feature enabled by default to provide the most user-friendly experience for common use cases.

Default Feature Resolution

flowchart TD
USER_PROJECT["User's Cargo.toml"]
DEPENDENCY_DECL["arm_pl031 dependency"]
DEFAULT_RESOLUTION["Resolve default features"]
CHRONO_ENABLED["chrono = true"]
ENHANCED_API["Enhanced DateTime API"]
OPT_OUT["default-features = false"]
MINIMAL_CONFIG["Core-only build"]
UNIX_TIMESTAMP_ONLY["Unix timestamp API only"]

CHRONO_ENABLED --> ENHANCED_API
DEFAULT_RESOLUTION --> CHRONO_ENABLED
DEPENDENCY_DECL --> DEFAULT_RESOLUTION
DEPENDENCY_DECL --> OPT_OUT
MINIMAL_CONFIG --> UNIX_TIMESTAMP_ONLY
OPT_OUT --> MINIMAL_CONFIG
USER_PROJECT --> DEPENDENCY_DECL

To use the default configuration:

[dependencies]
arm_pl031 = "0.2.1"

To disable default features:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }

Sources: Cargo.toml(L19) 

No-std Compatibility

The arm_pl031 crate is designed for no_std environments and embedded systems. All features maintain no_std compatibility.

No-std Design Principles

ComponentNo-std StatusImplementation Notes
Core driver✓ Full supportUsesvolatileoperations, no heap allocation
Chrono integration✓ CompatibleUseschronowithdefault-features = false
Error handling✓ CompatibleUsesResulttypes, no panic in normal operation
Memory management✓ CompatibleZero dynamic allocation, stack-based operations

No-std Compatibility Architecture

flowchart TD
subgraph subGraph2["Hardware Layer"]
    MMIO_REGISTERS["Memory-Mapped Registers"]
    PL031_HW["ARM PL031 Hardware"]
end
subgraph subGraph1["arm_pl031 Components"]
    CORE_DRIVER["Core RTC Driver"]
    CHRONO_NOSTD["chrono (no default features)"]
    VOLATILE_OPS["Volatile MMIO Operations"]
    RESULT_TYPES["Result-based Error Handling"]
end
subgraph subGraph0["no_std Environment"]
    EMBEDDED_TARGET["Embedded Target"]
    NO_HEAP["No Heap Allocation"]
    NO_STD_LIB["No Standard Library"]
end

CORE_DRIVER --> CHRONO_NOSTD
CORE_DRIVER --> VOLATILE_OPS
EMBEDDED_TARGET --> CORE_DRIVER
MMIO_REGISTERS --> PL031_HW
NO_HEAP --> VOLATILE_OPS
NO_STD_LIB --> RESULT_TYPES
VOLATILE_OPS --> MMIO_REGISTERS

Sources: Cargo.toml(L12)  Cargo.toml(L15) 

Build Configuration Options

Target Architecture Support

The driver supports multiple target architectures while maintaining consistent functionality:

Target TriplePurposeFeature Support
aarch64-unknown-none-softfloatBare metal ARM64All features
x86_64-unknown-noneBare metal x86_64 (testing)All features
riscv64gc-unknown-none-elfRISC-V embeddedAll features
x86_64-unknown-linux-gnuLinux development/testingAll features

Feature Selection Examples

Minimal embedded build:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false }

Full-featured embedded build:

[dependencies]
arm_pl031 = "0.2.1"  # Includes chrono by default

Selective feature enabling:

[dependencies]
arm_pl031 = { version = "0.2.1", default-features = false, features = ["chrono"] }

Dependency Management

The chrono dependency is configured for embedded compatibility:

  • Version constraint: 0.4.38
  • default-features = false to avoid std dependencies
  • Optional inclusion based on feature flags

Dependency Resolution Flow

flowchart TD
CARGO_TOML["Cargo.toml feature selection"]
FEATURE_CHECK["chrono feature enabled?"]
CHRONO_DEP["Include chrono 0.4.38"]
MINIMAL_BUILD["Core driver only"]
NO_DEFAULT_FEATURES["chrono with default-features = false"]
COMPATIBLE_BUILD["no_std compatible build"]
TARGET_COMPILATION["Target-specific compilation"]

CARGO_TOML --> FEATURE_CHECK
CHRONO_DEP --> NO_DEFAULT_FEATURES
COMPATIBLE_BUILD --> TARGET_COMPILATION
FEATURE_CHECK --> CHRONO_DEP
FEATURE_CHECK --> MINIMAL_BUILD
MINIMAL_BUILD --> COMPATIBLE_BUILD
NO_DEFAULT_FEATURES --> COMPATIBLE_BUILD

Sources: Cargo.toml(L14 - L15)  Cargo.toml(L17 - L19) 

Configuration Best Practices

For Embedded Systems

  • Use default-features = false for smallest binary size
  • Enable only required features to minimize dependencies
  • Consider target-specific optimizations in your project's Cargo.toml

For Development and Testing

  • Use default features for convenient DateTime handling
  • Enable all features during development for full API access
  • Use hosted targets like x86_64-unknown-linux-gnu for rapid development

For Production Deployment

  • Carefully evaluate which features are needed for your use case
  • Test with the exact feature configuration used in production
  • Document feature selections in your project's dependency specifications

Sources: Cargo.toml(L1 - L20) 

Development and Contributing

Relevant source files

This page provides comprehensive information for developers who want to build, test, modify, or contribute to the arm_pl031 crate. It covers the development workflow, build system, testing procedures, and contribution guidelines.

For information about using the driver in your applications, see Getting Started. For details about the crate's API and functionality, see Core Driver Implementation and Features and Extensions.

Development Workflow Overview

The arm_pl031 project follows a standard Rust development workflow with automated quality assurance through GitHub Actions. The development process emphasizes cross-platform compatibility, code quality, and comprehensive testing.

Development Pipeline Architecture

flowchart TD
subgraph Deployment["Deployment"]
    MERGE["Merge to Main"]
    PAGES["GitHub Pages"]
    RELEASE["Crates.io Release"]
end
subgraph subGraph2["Quality Gates"]
    FORMAT_CHECK["Format Validation"]
    LINT_CHECK["Clippy Linting"]
    BUILD_CHECK["Cross-compilation"]
    TEST_CHECK["Test Execution"]
    DOC_CHECK["Documentation Generation"]
end
subgraph subGraph1["Continuous Integration"]
    PR["Pull Request"]
    MATRIX["CI Matrix Build"]
    TARGETS["Multi-target Validation"]
    TESTS["Unit Tests"]
    DOCS["Documentation Build"]
end
subgraph subGraph0["Development Phase"]
    DEV["Developer Changes"]
    LOCAL["Local Development"]
    FMT["cargo fmt"]
    CLIPPY["cargo clippy"]
end

BUILD_CHECK --> TARGETS
CLIPPY --> PR
DEV --> LOCAL
DOCS --> MERGE
DOC_CHECK --> DOCS
FMT --> CLIPPY
FORMAT_CHECK --> TARGETS
LINT_CHECK --> TARGETS
LOCAL --> FMT
MATRIX --> BUILD_CHECK
MATRIX --> DOC_CHECK
MATRIX --> FORMAT_CHECK
MATRIX --> LINT_CHECK
MATRIX --> TEST_CHECK
MERGE --> PAGES
MERGE --> RELEASE
PR --> MATRIX
TARGETS --> MERGE
TESTS --> MERGE
TEST_CHECK --> TESTS

Sources: .github/workflows/ci.yml(L1 - L58) 

Build System and Target Support

The project supports multiple architectures and build configurations to ensure compatibility across different embedded and hosted environments.

Supported Build Targets

TargetArchitectureEnvironmentPrimary Use Case
x86_64-unknown-linux-gnux86_64Linux hostedDevelopment, testing
x86_64-unknown-nonex86_64Bare metalTesting no_std compatibility
riscv64gc-unknown-none-elfRISC-V 64-bitBare metalRISC-V embedded systems
aarch64-unknown-none-softfloatARM64Bare metalARM64 embedded systems

The build matrix validates all targets with both minimal and full feature sets:

# Build with no features (core functionality only)
cargo build --target <target> --no-default-features

# Build with all features (including chrono integration)
cargo build --target <target> --all-features

Feature Configuration Matrix

flowchart TD
subgraph subGraph2["API Surface"]
    BASIC_API["Basic RTC APIUnix timestamps"]
    CHRONO_API["DateTime APIchrono integration"]
end
subgraph Dependencies["Dependencies"]
    NO_DEPS["No Dependencies"]
    CHRONO_DEP["chrono = 0.4.38no default features"]
end
subgraph subGraph0["Feature Combinations"]
    CORE["Core Only--no-default-features"]
    DEFAULT["Default Buildchrono enabled"]
    ALL["All Features--all-features"]
end

ALL --> CHRONO_DEP
CHRONO_DEP --> BASIC_API
CHRONO_DEP --> CHRONO_API
CORE --> NO_DEPS
DEFAULT --> CHRONO_DEP
NO_DEPS --> BASIC_API

Sources: Cargo.toml(L14 - L19)  .github/workflows/ci.yml(L26 - L29) 

Code Quality and Validation

Automated Quality Checks

The CI pipeline enforces several quality standards that must pass before code can be merged:

  1. Code Formatting: All code must be formatted with rustfmt using default settings
cargo fmt --all -- --check
  1. Linting: Code must pass clippy analysis with minimal warnings
cargo clippy --target <target> --all-features -- -A clippy::new_without_default
  1. Cross-compilation: Code must compile successfully on all supported targets
  2. Unit Testing: Tests must pass on the primary development target (x86_64-unknown-linux-gnu)

Testing Strategy

flowchart TD
subgraph subGraph2["Validation Scope"]
    CORE_FUNC["Core Functionality"]
    CHRONO_FEAT["Chrono Feature"]
    NO_STD["no_std Compatibility"]
    SAFETY["Memory Safety"]
end
subgraph subGraph1["Test Execution Environment"]
    LINUX["x86_64-unknown-linux-gnuFull test execution"]
    EMBEDDED["Embedded TargetsCompilation only"]
end
subgraph subGraph0["Test Categories"]
    UNIT["Unit Testscargo test"]
    COMPILE["Compilation TestsCross-platform builds"]
    DOC["Documentation Testsrustdoc validation"]
end

COMPILE --> EMBEDDED
DOC --> LINUX
EMBEDDED --> NO_STD
EMBEDDED --> SAFETY
LINUX --> CHRONO_FEAT
LINUX --> CORE_FUNC
UNIT --> LINUX

Sources: .github/workflows/ci.yml(L30 - L32)  .github/workflows/ci.yml(L26 - L29) 

Documentation and API Standards

Documentation Requirements

The project maintains strict documentation standards enforced through the CI pipeline:

  • Missing Documentation: All public APIs must be documented (-D missing-docs)
  • Broken Links: Intra-doc links must be valid (-D rustdoc::broken_intra_doc_links)
  • API Examples: Public functions should include usage examples
  • Safety Documentation: All unsafe functions must document their safety requirements

Documentation Deployment

Documentation is automatically built and deployed to GitHub Pages on every push to the main branch:

flowchart TD
subgraph subGraph1["Access Points"]
    DOCS_RS["docs.rsOfficial documentation"]
    GH_PAGES["GitHub PagesDevelopment docs"]
end
subgraph subGraph0["Documentation Pipeline"]
    BUILD["cargo doc--no-deps --all-features"]
    INDEX["Generate index.htmlAuto-redirect"]
    DEPLOY["GitHub Pagesgh-pages branch"]
end

BUILD --> DOCS_RS
BUILD --> INDEX
DEPLOY --> GH_PAGES
INDEX --> DEPLOY

Sources: .github/workflows/ci.yml(L34 - L57)  .github/workflows/ci.yml(L42) 

Version Management and API Evolution

Semantic Versioning Policy

The project follows semantic versioning with careful attention to API stability:

  • Patch versions (0.2.0 → 0.2.1): Bug fixes, new features that don't break existing APIs
  • Minor versions (0.1.x → 0.2.0): New features, may include breaking changes in pre-1.0 releases
  • Major versions (future 1.x → 2.x): Significant breaking changes

API Evolution History


Breaking Changes Documentation

Major API changes are carefully documented in the changelog with migration guidance:

VersionBreaking ChangeRationaleMigration Path
v0.2.0get_unix_timestamp()returnsu32Match hardware register sizeCastu64tou32if needed
v0.2.0Rtc::new()isunsafeRequires valid MMIO pointerAddunsafeblock with safety comment
v0.2.0set_unix_timestamp()takes&mut selfReflects write operation semanticsUse mutable reference

Sources: CHANGELOG.md(L1 - L26) 

Contributing Guidelines

Development Environment Setup

  1. Rust Toolchain: Install nightly Rust with required components
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt
  1. Target Support: Add required compilation targets
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
  1. Development Workflow: Follow the standard validation steps
cargo fmt --all                    # Format code
cargo clippy --all-features        # Lint code
cargo test                         # Run tests
cargo build --no-default-features  # Test minimal build

Pull Request Process

flowchart TD
subgraph Integration["Integration"]
    APPROVAL["Review Approval"]
    MERGE["Merge to Main"]
    CLEANUP["Branch Cleanup"]
end
subgraph Submission["Submission"]
    PR_CREATE["Create Pull Request"]
    CI_RUN["CI Pipeline Execution"]
    REVIEW["Code Review"]
end
subgraph Validation["Validation"]
    LOCAL_TEST["Local Testing"]
    FORMAT["Code Formatting"]
    LINT["Clippy Validation"]
end
subgraph Preparation["Preparation"]
    FORK["Fork Repository"]
    BRANCH["Create Feature Branch"]
    CHANGES["Implement Changes"]
end

APPROVAL --> MERGE
BRANCH --> CHANGES
CHANGES --> LOCAL_TEST
CI_RUN --> REVIEW
FORK --> BRANCH
FORMAT --> LINT
LINT --> PR_CREATE
LOCAL_TEST --> FORMAT
MERGE --> CLEANUP
PR_CREATE --> CI_RUN
REVIEW --> APPROVAL

Code Standards

  • Safety: All unsafe code must be justified and documented
  • Documentation: Public APIs require comprehensive documentation
  • Testing: New functionality should include appropriate tests
  • Compatibility: Changes must not break existing supported targets
  • Feature Flags: Optional functionality should be feature-gated appropriately

Sources: .github/workflows/ci.yml(L1 - L58)  Cargo.toml(L1 - L20) 

Building and Testing

Relevant source files

This section covers the build process, multi-target support, continuous integration pipeline, and testing procedures for the arm_pl031 crate. It provides developers with comprehensive information about how to build the crate for different target architectures, understand the automated testing infrastructure, and contribute to the project with confidence.

For information about setting up the development environment and contribution guidelines, see Development Environment. For details about the crate's API evolution and versioning, see API Evolution and Changelog.

Build System Overview

The arm_pl031 crate uses Cargo as its primary build system and is configured for cross-platform compilation with no_std compatibility. The crate supports both embedded and hosted environments through careful dependency management and feature flags.

Build Configuration

The build configuration is defined in [Cargo.toml(L1 - L20)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/Cargo.toml#L1-L20) and emphasizes embedded-first design:

ConfigurationValuePurpose
Edition2021Modern Rust features and patterns
Categoriesos,hardware-support,no-stdEmbedded systems focus
Default FeatureschronoDateTime support enabled by default
Optional Dependencieschrono = "0.4.38"DateTime integration without std
flowchart TD
subgraph subGraph3["Build Outputs"]
    LIB_NOSTD["libarm_pl031.rlib (no_std)"]
    LIB_CHRONO["libarm_pl031.rlib (with chrono)"]
    DOCS["target/doc/"]
end
subgraph subGraph2["Target Compilation"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    AARCH64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Feature Resolution"]
    NO_FEATURES["--no-default-features"]
    DEFAULT["default = [chrono]"]
    ALL_FEATURES["--all-features"]
end
subgraph subGraph0["Build Inputs"]
    CARGO["Cargo.toml"]
    SRC["src/lib.rs"]
    CHRONO_SRC["src/chrono.rs"]
end

AARCH64 --> LIB_NOSTD
CARGO --> ALL_FEATURES
CARGO --> DEFAULT
CARGO --> NO_FEATURES
CHRONO_SRC --> ALL_FEATURES
CHRONO_SRC --> DEFAULT
DEFAULT --> AARCH64
DEFAULT --> RISCV
DEFAULT --> X86_LINUX
DEFAULT --> X86_NONE
NO_FEATURES --> AARCH64
NO_FEATURES --> RISCV
NO_FEATURES --> X86_LINUX
NO_FEATURES --> X86_NONE
RISCV --> LIB_NOSTD
SRC --> DEFAULT
SRC --> NO_FEATURES
X86_LINUX --> DOCS
X86_LINUX --> LIB_CHRONO
X86_LINUX --> LIB_NOSTD
X86_NONE --> LIB_NOSTD

Build System Architecture

Sources: [Cargo.toml(L1 - L20)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/Cargo.toml#L1-L20)

Multi-Target Support

The crate is designed to work across multiple target architectures, with particular emphasis on embedded no_std environments. The CI system validates compatibility across four primary target configurations.

Supported Target Architectures

flowchart TD
subgraph subGraph5["Validation Steps"]
    FMT_CHECK["cargo fmt --all -- --check"]
    CLIPPY["cargo clippy --all-features"]
    UNIT_TEST["cargo test"]
end
subgraph subGraph4["Build Commands"]
    NO_FEAT["cargo build --no-default-features"]
    ALL_FEAT["cargo build --all-features"]
end
subgraph subGraph3["ARM64 Embedded"]
    AARCH64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph2["RISC-V Embedded"]
    RISCV["riscv64gc-unknown-none-elf"]
end
subgraph subGraph1["Bare Metal x86"]
    X86_NONE["x86_64-unknown-none"]
end
subgraph subGraph0["Hosted Targets"]
    LINUX["x86_64-unknown-linux-gnu"]
end

ALL_FEAT --> AARCH64
ALL_FEAT --> LINUX
ALL_FEAT --> RISCV
ALL_FEAT --> X86_NONE
CLIPPY --> LINUX
FMT_CHECK --> LINUX
NO_FEAT --> AARCH64
NO_FEAT --> LINUX
NO_FEAT --> RISCV
NO_FEAT --> X86_NONE
UNIT_TEST --> LINUX

Multi-Target Build Matrix

TargetPurposeFeatures TestedTest Execution
x86_64-unknown-linux-gnuHosted developmentAll features, unit testsFull test suite
x86_64-unknown-noneBare metal x86Core functionalityBuild verification
riscv64gc-unknown-none-elfRISC-V embeddedCore functionalityBuild verification
aarch64-unknown-none-softfloatARM64 embeddedCore functionalityBuild verification

Sources: [.github/workflows/ci.yml(L12)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L12-L12) [.github/workflows/ci.yml(L25 - L29)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L25-L29)

Continuous Integration Pipeline

The CI pipeline is implemented using GitHub Actions and provides comprehensive validation across multiple dimensions: code quality, build compatibility, and documentation generation.

CI Workflow Structure

flowchart TD
subgraph Documentation["Documentation"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
    DEPLOY["JamesIves/github-pages-deploy-action@v4"]
end
subgraph Testing["Testing"]
    UNIT_TEST["cargo test (x86_64-linux only)"]
end
subgraph subGraph3["Build Validation"]
    BUILD_NO["cargo build --no-default-features"]
    BUILD_ALL["cargo build --all-features"]
end
subgraph subGraph2["Quality Checks"]
    VERSION["rustc --version --verbose"]
    FORMAT["cargo fmt --all -- --check"]
    LINT["cargo clippy --all-features"]
end
subgraph subGraph1["CI Job Matrix"]
    RUST_VER["rust-toolchain: nightly"]
    TARGET_MATRIX["4 target architectures"]
end
subgraph subGraph0["Trigger Events"]
    PUSH["git push"]
    PR["pull_request"]
end

BUILD_ALL --> UNIT_TEST
BUILD_NO --> BUILD_ALL
DOC_BUILD --> DEPLOY
FORMAT --> LINT
LINT --> BUILD_NO
PR --> RUST_VER
PUSH --> RUST_VER
RUST_VER --> DOC_BUILD
RUST_VER --> TARGET_MATRIX
TARGET_MATRIX --> VERSION
VERSION --> FORMAT

GitHub Actions CI Pipeline

Toolchain and Components

The CI system uses Rust nightly toolchain with specific components required for comprehensive validation:

ComponentPurposeUsage
rust-srcSource code for cross-compilationRequired forno_stdtargets
clippyLint analysisCode quality enforcement
rustfmtCode formattingStyle consistency
nightlytoolchainLatest Rust featuresEmbedded development support

Sources: [.github/workflows/ci.yml(L17 - L19)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L17-L19)

Testing Strategy

The testing approach balances comprehensive validation with practical constraints of embedded development, where some targets cannot execute tests directly.

Test Execution Matrix

flowchart TD
subgraph subGraph2["Test Commands"]
    FMT_CMD["cargo fmt --all -- --check"]
    CLIPPY_CMD["cargo clippy --target TARGET --all-features"]
    BUILD_CMD["cargo build --target TARGET"]
    TEST_CMD["cargo test --target TARGET -- --nocapture"]
end
subgraph subGraph1["Target Scope"]
    ALL_TARGETS["All 4 targets"]
    LINUX_ONLY["x86_64-unknown-linux-gnu only"]
end
subgraph subGraph0["Test Categories"]
    FORMAT_TEST["Format Validation"]
    LINT_TEST["Clippy Analysis"]
    BUILD_TEST["Compilation Test"]
    UNIT_TEST["Unit Test Execution"]
end

ALL_TARGETS --> BUILD_CMD
ALL_TARGETS --> CLIPPY_CMD
ALL_TARGETS --> FMT_CMD
BUILD_TEST --> ALL_TARGETS
FORMAT_TEST --> ALL_TARGETS
LINT_TEST --> ALL_TARGETS
LINUX_ONLY --> TEST_CMD
UNIT_TEST --> LINUX_ONLY

Testing Architecture by Target

Quality Assurance Steps

The CI pipeline enforces multiple quality gates to ensure code reliability and maintainability:

  1. Code Formatting: Enforced via [cargo fmt --all -- --check](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/cargo fmt --all -- --check)
  2. Lint Analysis: Comprehensive clippy checks with [-A clippy](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/-A clippy#LNaN-LNaN) exception
  3. Build Verification: Both minimal and full feature builds for each target
  4. Unit Testing: Executed only on x86_64-unknown-linux-gnu with --nocapture for detailed output

Sources: [.github/workflows/ci.yml(L22 - L32)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L22-L32)

Documentation Building and Deployment

The documentation system automatically generates and deploys API documentation to GitHub Pages, ensuring developers always have access to current documentation.

Documentation Pipeline

sequenceDiagram
    participant Developer as "Developer"
    participant GitHubActions as "GitHub Actions"
    participant cargodoc as "cargo doc"
    participant GitHubPages as "GitHub Pages"

    Developer ->> GitHubActions: "Push to main branch"
    GitHubActions ->> cargodoc: "cargo doc --no-deps --all-features"
    Note over cargodoc: "RUSTDOCFLAGS=-D broken_intra_doc_links"
    cargodoc ->> cargodoc: "Generate target/doc/"
    cargodoc ->> cargodoc: "Create redirect index.html"
    GitHubActions ->> GitHubPages: "Deploy via JamesIves/github-pages-deploy-action@v4"
    Note over GitHubPages: "Single commit deployment to gh-pages branch"

Documentation Deployment Workflow

Documentation Configuration

The documentation build process includes several quality controls:

SettingValuePurpose
RUSTDOCFLAGS-D rustdoc::broken_intra_doc_links -D missing-docsEnforce documentation completeness
--no-depsSkip dependency docsFocus on crate-specific documentation
--all-featuresInclude all featuresDocument complete API surface
single-commitTrueClean deployment history

The system automatically generates a redirect index page using the crate name extracted from [cargo tree](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/cargo tree) ensuring seamless navigation to the main documentation.

Sources: [.github/workflows/ci.yml(L34 - L58)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L34-L58) [.github/workflows/ci.yml(L42)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L42-L42) [.github/workflows/ci.yml(L48 - L50)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L48-L50)

Build Commands Reference

For local development and testing, the following commands replicate the CI environment:

Basic Build Commands

# Minimal build (no chrono feature)
cargo build --no-default-features

# Full build (all features)
cargo build --all-features

# Target-specific build
cargo build --target aarch64-unknown-none-softfloat

Quality Assurance Commands

# Format check
cargo fmt --all -- --check

# Lint analysis
cargo clippy --all-features -- -A clippy::new_without_default

# Unit tests
cargo test -- --nocapture

Documentation Commands

# Generate documentation
cargo doc --no-deps --all-features

# Open documentation locally
cargo doc --no-deps --all-features --open

Sources: [.github/workflows/ci.yml(L23 - L32)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L23-L32) [.github/workflows/ci.yml(L49)&emsp;](https://github.com/arceos-org/arm_pl031/blob/8cc6761d/.github/workflows/ci.yml#L49-L49)

API Evolution and Changelog

Relevant source files

This document tracks the evolution of the arm_pl031 crate's public API across versions, documenting breaking changes, feature additions, and migration guidance. It serves as a reference for understanding how the crate has evolved and helps developers plan upgrades and maintain backward compatibility.

For information about building and testing the current version, see Building and Testing. For development environment setup, see Development Environment.

Version History Overview

The arm_pl031 crate has evolved through three major releases, each introducing significant improvements to the API design and functionality. The evolution demonstrates a progression from a basic RTC interface to a more robust, safe, and feature-rich driver.

API Evolution Timeline


Sources: CHANGELOG.md(L1 - L26) 

Breaking Changes Analysis

Version 0.2.0 Breaking Changes

Version 0.2.0 introduced several breaking changes that improved the API's safety and correctness:

flowchart TD
subgraph subGraph1["v0.2.0 API"]
    NEW_NEW["unsafe Rtc::new()"]
    NEW_GET["get_unix_timestamp() -> u32"]
    NEW_SET["set_unix_timestamp(&mut self, u32)"]
    SEND_SYNC["impl Send + Sync for Rtc"]
end
subgraph subGraph0["v0.1.0 API"]
    OLD_NEW["Rtc::new()"]
    OLD_GET["get_unix_timestamp() -> u64"]
    OLD_SET["set_unix_timestamp(&self, u64)"]
end

NEW_NEW --> SEND_SYNC
OLD_GET --> NEW_GET
OLD_NEW --> NEW_NEW
OLD_SET --> NEW_SET

Sources: CHANGELOG.md(L10 - L22) 

Timestamp Type Change (u64 → u32)

The most significant breaking change was the modification of timestamp handling functions:

Functionv0.1.0 Signaturev0.2.0 SignatureRationale
get_unix_timestampfn() -> u64fn() -> u32Match PL031 register width
set_unix_timestampfn(&self, u64)fn(&mut self, u32)Type consistency + mutability

This change aligned the API with the underlying hardware's 32-bit registers, preventing potential data truncation issues and improving type safety.

Safety Boundary Introduction

The Rtc::new() constructor was marked as unsafe to properly reflect that it requires a valid memory-mapped I/O pointer:

#![allow(unused)]
fn main() {
// v0.1.0
impl Rtc {
    pub fn new(base: *mut u8) -> Self

// v0.2.0  
impl Rtc {
    pub unsafe fn new(base: *mut u8) -> Self
}

Mutability Requirements

The set_unix_timestamp function was changed to require a mutable reference, correctly reflecting that it modifies device state:

#![allow(unused)]
fn main() {
// v0.1.0
fn set_unix_timestamp(&self, timestamp: u64)

// v0.2.0
fn set_unix_timestamp(&mut self, timestamp: u32)
}

Sources: CHANGELOG.md(L14 - L17) 

Feature Evolution

Version 0.2.1 Feature Additions

Version 0.2.1 introduced new capabilities without breaking existing APIs:

flowchart TD
subgraph subGraph1["Match & Interrupt API (v0.2.1)"]
    MATCH["Match Register Methods"]
    INT_ENABLE["enable_interrupt()"]
    INT_PENDING["interrupt_pending()"]
    INT_CLEAR["clear_interrupt()"]
    SET_MATCH["set_match_timestamp()"]
end
subgraph subGraph0["Core API (v0.2.0)"]
    BASIC["Basic RTC Operations"]
    TIMESTAMP["Unix Timestamp Methods"]
end
subgraph subGraph2["Chrono Integration (v0.2.1)"]
    CHRONO_DEP["Optional chrono dependency"]
    DATETIME["DateTime support"]
    GET_TIME["get_time()"]
    SET_TIME["set_time()"]
end

BASIC --> MATCH
CHRONO_DEP --> DATETIME
DATETIME --> GET_TIME
DATETIME --> SET_TIME
TIMESTAMP --> SET_MATCH

Sources: CHANGELOG.md(L3 - L9) 

Interrupt Management

The addition of interrupt-related methods provided comprehensive control over PL031 interrupt functionality:

  • enable_interrupt() - Enable/disable alarm interrupts
  • interrupt_pending() - Check interrupt status
  • clear_interrupt() - Clear pending interrupts
  • set_match_timestamp() - Configure alarm time

Chrono Integration

The optional chrono feature added high-level time handling:

  • Conversion between Unix timestamps and DateTime<Utc>
  • Timezone-aware time operations
  • Integration with the broader Rust ecosystem

Migration Guidance

Migrating from v0.1.0 to v0.2.0

Type Changes

// v0.1.0 code
let timestamp: u64 = rtc.get_unix_timestamp();
rtc.set_unix_timestamp(timestamp);

// v0.2.0 migration
let timestamp: u32 = rtc.get_unix_timestamp();
rtc.set_unix_timestamp(timestamp);  // Also requires &mut rtc

Safety Annotations

// v0.1.0 code
let rtc = Rtc::new(base_addr);

// v0.2.0 migration
let rtc = unsafe { Rtc::new(base_addr) };

Mutability Requirements

#![allow(unused)]
fn main() {
// v0.1.0 code
fn update_time(rtc: &Rtc, new_time: u64) {
    rtc.set_unix_timestamp(new_time);
}

// v0.2.0 migration  
fn update_time(rtc: &mut Rtc, new_time: u32) {
    rtc.set_unix_timestamp(new_time);
}
}

Migrating from v0.2.0 to v0.2.1

Version 0.2.1 is fully backward compatible. New features can be adopted incrementally:

// Existing v0.2.0 code continues to work
let timestamp = rtc.get_unix_timestamp();

// New v0.2.1 features (optional)
rtc.set_match_timestamp(timestamp + 3600);  // Alarm in 1 hour
rtc.enable_interrupt(true);

// With chrono feature enabled
#[cfg(feature = "chrono")]
{
    let datetime = rtc.get_time().unwrap();
    println!("Current time: {}", datetime);
}

Sources: CHANGELOG.md(L7 - L8) 

Thread Safety Evolution

Send + Sync Implementation

Version 0.2.0 introduced thread safety guarantees:

flowchart TD
subgraph subGraph0["Thread Safety Model"]
    RTC["Rtc struct"]
    SEND["impl Send"]
    SYNC["impl Sync"]
    VOLATILE["Volatile MMIO operations"]
    ATOMIC["Atomic hardware access"]
end

RTC --> SEND
RTC --> SYNC
SEND --> VOLATILE
SYNC --> ATOMIC

The Send and Sync implementations enable safe use in multi-threaded environments, with the underlying volatile memory operations providing the necessary synchronization primitives.

Sources: CHANGELOG.md(L21) 

Compatibility Matrix

VersionBreaking ChangesNew FeaturesRust Editionno_std Support
0.1.0N/ABasic RTC operations2021Yes
0.2.0Timestamp types, SafetySend/Sync traits2021Yes
0.2.1NoneInterrupts, chrono2021Yes

Future Compatibility Considerations

The API design demonstrates several patterns that suggest future stability:

  1. Hardware Alignment: The v0.2.0 type changes align with hardware constraints, reducing likelihood of future type changes
  2. Safety First: The unsafe boundary is clearly defined and unlikely to change
  3. Feature Flags: Optional features like chrono allow expansion without breaking core functionality
  4. Thread Safety: The Send/Sync implementation provides a stable foundation for concurrent use

The crate follows semantic versioning, ensuring that future minor versions (0.2.x) will maintain backward compatibility while major versions (0.x.0) may introduce breaking changes.

Sources: CHANGELOG.md(L1 - L26) 

Development Environment

Relevant source files

This page provides comprehensive setup instructions for developing and contributing to the arm_pl031 crate. It covers toolchain requirements, development tools configuration, build procedures, and the continuous integration pipeline that ensures code quality across multiple target architectures.

For information about the project's build system and testing procedures in production, see Building and Testing. For details about project structure and API design, see Core Driver Implementation.

Prerequisites and Toolchain

The arm_pl031 crate requires specific Rust toolchain configurations to support its embedded and cross-platform nature. The project is built exclusively with Rust nightly to access unstable features required for embedded development.

Required Rust Toolchain

The development environment requires Rust nightly with specific components:

ComponentPurpose
rust-srcRequired for building core library and no_std targets
clippyLinting and code analysis
rustfmtCode formatting enforcement

Supported Target Architectures

The crate supports multiple target architectures for cross-compilation:

TargetArchitectureEnvironment
x86_64-unknown-linux-gnux86_64Hosted Linux environment
x86_64-unknown-nonex86_64Bare metal no_std
riscv64gc-unknown-none-elfRISC-V 64-bitEmbedded no_std
aarch64-unknown-none-softfloatARM64Embedded no_std with soft float

Development Environment Setup

flowchart TD
subgraph subGraph0["Required Targets"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
end
RUSTUP["rustup toolchain install nightly"]
COMPONENTS["rustup component add rust-src clippy rustfmt"]
TARGETS["rustup target add"]
VERIFY["rustc --version --verbose"]

COMPONENTS --> TARGETS
RUSTUP --> COMPONENTS
TARGETS --> T1
TARGETS --> T2
TARGETS --> T3
TARGETS --> T4
TARGETS --> VERIFY

Sources: .github/workflows/ci.yml(L11 - L19) 

Development Tooling and Code Quality

The project enforces strict code quality standards through automated tooling integrated into the development workflow.

Code Formatting

Code formatting is enforced using rustfmt with project-specific configuration. All code must pass formatting checks before merge.

Formatting Workflow:

  • Local development: cargo fmt --all
  • CI enforcement: cargo fmt --all -- --check

Linting and Static Analysis

The project uses clippy for comprehensive linting with custom rule configuration:

Clippy Configuration:

  • Runs on all target architectures
  • Uses --all-features flag for complete analysis
  • Suppresses clippy::new_without_default warnings
  • Command: cargo clippy --target <target> --all-features -- -A clippy::new_without_default

CI Quality Gates

flowchart TD
subgraph subGraph1["Build Verification"]
    BUILD_NO_FEAT["cargo build --target TARGET --no-default-features"]
    BUILD_ALL_FEAT["cargo build --target TARGET --all-features"]
end
subgraph subGraph0["Quality Checks"]
    FORMAT["cargo fmt --all -- --check"]
    CLIPPY["cargo clippy --target TARGET --all-features"]
end
CHECKOUT["actions/checkout@v4"]
TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
TEST["cargo test --target x86_64-unknown-linux-gnu"]

BUILD_ALL_FEAT --> TEST
BUILD_NO_FEAT --> BUILD_ALL_FEAT
CHECKOUT --> TOOLCHAIN
CLIPPY --> BUILD_NO_FEAT
FORMAT --> CLIPPY
TOOLCHAIN --> FORMAT

Sources: .github/workflows/ci.yml(L22 - L32) 

Build System and Testing

The build system supports multiple feature configurations and target architectures with comprehensive testing on hosted platforms.

Build Configurations

The project supports two primary build configurations:

ConfigurationCommandPurpose
Minimalcargo build --no-default-featuresCore functionality only
Fullcargo build --all-featuresAll features including chrono integration

Testing Framework

Unit tests run exclusively on the x86_64-unknown-linux-gnu target due to hosted environment requirements:

Test Execution:

  • Target: x86_64-unknown-linux-gnu only
  • Command: cargo test --target x86_64-unknown-linux-gnu -- --nocapture
  • Output: Verbose test output with --nocapture flag

Cross-Compilation Verification

flowchart TD
subgraph subGraph1["Build Steps per Target"]
    B1["build --no-default-features"]
    B2["build --all-features"]
    T1["test (linux-gnu only)"]
end
subgraph subGraph0["Build Matrix"]
    M1["nightly + x86_64-unknown-linux-gnu"]
    M2["nightly + x86_64-unknown-none"]
    M3["nightly + riscv64gc-unknown-none-elf"]
    M4["nightly + aarch64-unknown-none-softfloat"]
end

B1 --> B2
B2 --> T1
M1 --> B1
M2 --> B1
M3 --> B1
M4 --> B1
T1 --> M1

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L26 - L32) 

Documentation Generation

The project maintains comprehensive documentation with automated generation and deployment to GitHub Pages.

Documentation Build Process

Documentation generation includes:

  • API documentation: cargo doc --no-deps --all-features
  • Intra-doc link validation: -D rustdoc::broken_intra_doc_links
  • Missing documentation detection: -D missing-docs
  • Automatic index page generation for navigation

Documentation Deployment

GitHub Pages Integration:

  • Automatic deployment from default branch
  • Single-commit deployment strategy
  • Target branch: gh-pages
  • Deploy action: JamesIves/github-pages-deploy-action@v4

Documentation Pipeline

flowchart TD
subgraph subGraph0["Documentation Flags"]
    F1["-D rustdoc::broken_intra_doc_links"]
    F2["-D missing-docs"]
end
DOC_BUILD["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate index.html redirect"]
DEPLOY_CHECK["Default branch?"]
DEPLOY["Deploy to gh-pages"]
SKIP["Skip deployment"]

DEPLOY_CHECK --> DEPLOY
DEPLOY_CHECK --> SKIP
DOC_BUILD --> INDEX_GEN
F1 --> DOC_BUILD
F2 --> DOC_BUILD
INDEX_GEN --> DEPLOY_CHECK

Sources: .github/workflows/ci.yml(L34 - L57)  .github/workflows/ci.yml(L42) 

Continuous Integration Pipeline

The CI system ensures code quality and compatibility across all supported platforms through comprehensive automated testing.

Pipeline Architecture

The CI pipeline consists of two primary jobs:

JobPurposeTrigger
ciCode quality, building, testingPush, Pull Request
docDocumentation generation and deploymentPush, Pull Request

Matrix Strategy

The CI uses a matrix build strategy for comprehensive testing:

Matrix Configuration:

  • Rust toolchain: nightly (only)
  • Fail-fast: false (continue testing other targets on failure)
  • Parallel execution across all target architectures

CI Architecture Flow

flowchart TD
subgraph subGraph2["Matrix Targets"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Doc Job"]
    DOC_START["Documentation Job"]
    DOC_BUILD["Build Documentation"]
    DOC_DEPLOY["Deploy to GitHub Pages"]
end
subgraph subGraph0["CI Job (Matrix Build)"]
    M_START["Matrix Start"]
    TOOLS["Setup Rust Nightly + Components"]
    QUALITY["Code Quality Checks"]
    BUILD["Multi-target Builds"]
    TEST["Unit Tests (linux-gnu only)"]
end
TRIGGER["Push / Pull Request"]

BUILD --> T1
BUILD --> T2
BUILD --> T3
BUILD --> T4
BUILD --> TEST
DOC_BUILD --> DOC_DEPLOY
DOC_START --> DOC_BUILD
M_START --> TOOLS
QUALITY --> BUILD
TOOLS --> QUALITY
TRIGGER --> DOC_START
TRIGGER --> M_START

Sources: .github/workflows/ci.yml(L1 - L33)  .github/workflows/ci.yml(L34 - L57) 

Contribution Workflow

Local Development Setup

  1. Install Rust Nightly:
rustup toolchain install nightly
rustup default nightly
  1. Add Required Components:
rustup component add rust-src clippy rustfmt
  1. Install Target Architectures:
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat

Pre-commit Checklist

Before submitting contributions, ensure all quality gates pass:

CheckCommandRequirement
Formatcargo fmt --all -- --checkMust pass
Lintcargo clippy --all-featuresMust pass
Build (minimal)cargo build --no-default-featuresAll targets
Build (full)cargo build --all-featuresAll targets
Testcargo testMust pass on linux-gnu

Git Configuration

The project excludes standard development artifacts:

Ignored Files:

  • /target - Rust build artifacts
  • /.vscode - VS Code configuration
  • .DS_Store - macOS system files
  • Cargo.lock - Dependency lock file (excluded for libraries)

Sources: .gitignore(L1 - L4) 

Overview

Relevant source files

Purpose and Scope

The crate_interface crate is a procedural macro library that enables cross-crate trait interfaces in Rust. It provides a solution for defining trait interfaces in one crate while allowing implementations and usage in separate crates, effectively solving circular dependency problems between crates. This document covers the high-level architecture, core concepts, and integration patterns within the ArceOS ecosystem.

For detailed usage instructions, see Getting Started. For comprehensive macro documentation, see Macro Reference. For implementation details, see Architecture and Internals.

Sources: Cargo.toml(L1 - L22)  README.md(L1 - L85) 

Core Problem and Solution

The crate addresses the fundamental challenge of circular dependencies in Rust crate ecosystems. Traditional trait definitions create tight coupling between interface definitions and their implementations, preventing modular plugin architectures.

The crate_interface solution employs a three-phase approach using procedural macros that generate extern "Rust" function declarations and implementations with specific symbol naming conventions. This allows the Rust linker to resolve cross-crate trait method calls without requiring direct crate dependencies.

flowchart TD
subgraph subGraph1["crate_interface Solution"]
    G["Generated #[export_name] functions"]
    H["Crate C: call_interface!"]
    I["Safe extern function calls"]
    subgraph subGraph0["Traditional Approach Problem"]
        D["Crate A: #[def_interface]"]
        E["Generated extern declarations"]
        F["Crate B: #[impl_interface]"]
        A["Crate A: Interface"]
        B["Crate B: Implementation"]
        C["Crate C: Consumer"]
    end
end

A --> B
A --> C
B --> C
C --> A
D --> E
E --> G
F --> G
H --> I
I --> G

Sources: README.md(L7 - L10)  Cargo.toml(L6) 

Three-Macro System Architecture

The crate implements a coordinated system of three procedural macros that work together to enable cross-crate trait interfaces:

flowchart TD
subgraph subGraph3["Runtime Linking"]
    M["Rust Linker"]
end
subgraph subGraph2["call_interface Phase"]
    I["call_interface!"]
    J["HelloIf::hello macro call"]
    K["unsafe extern function call"]
    L["__HelloIf_mod::__HelloIf_hello"]
end
subgraph subGraph1["impl_interface Phase"]
    E["#[impl_interface]"]
    F["impl HelloIf for HelloIfImpl"]
    G["#[export_name] extern fn"]
    H["__HelloIf_hello symbol"]
end
subgraph subGraph0["def_interface Phase"]
    A["#[def_interface]"]
    B["trait HelloIf"]
    C["__HelloIf_mod module"]
    D["extern fn __HelloIf_hello"]
end

A --> B
B --> C
C --> D
D --> M
E --> F
F --> G
G --> H
H --> M
I --> J
J --> K
K --> L
L --> M

Sources: README.md(L13 - L40)  README.md(L44 - L85) 

Generated Code Flow

The macro system transforms high-level trait definitions into low-level extern function interfaces that can be linked across crate boundaries:


Sources: README.md(L46 - L85) 

Integration with ArceOS Ecosystem

The crate_interface crate is designed as a foundational component within the ArceOS modular operating system architecture. It enables plugin-style extensibility and cross-crate interfaces essential for kernel modularity:

FeaturePurposeArceOS Use Case
No-std compatibilityEmbedded/kernel environmentsBare-metal kernel modules
Cross-crate interfacesPlugin architectureDevice drivers, file systems
Symbol-based linkingRuntime module loadingDynamic kernel extensions
Zero-overhead abstractionsPerformance-critical codeKernel syscall interfaces
flowchart TD
subgraph subGraph1["Build Targets"]
    J["x86_64-unknown-none"]
    K["riscv64gc-unknown-none-elf"]
    L["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["ArceOS Architecture"]
    A["Kernel Core"]
    B["crate_interface"]
    C["Driver Interface Traits"]
    D["FS Interface Traits"]
    E["Network Interface Traits"]
    F["Device Drivers"]
    G["File Systems"]
    H["Network Stack"]
    I["Applications"]
end

A --> B
B --> C
B --> D
B --> E
B --> J
B --> K
B --> L
F --> C
G --> D
H --> E
I --> C
I --> D
I --> E

Sources: Cargo.toml(L8)  Cargo.toml(L11 - L12) 

Key Design Principles

The crate follows several core design principles that make it suitable for system-level programming:

  • Symbol-based Decoupling: Uses extern function symbols instead of direct trait object vtables
  • Compile-time Safety: Procedural macros ensure type safety while generating unsafe extern calls
  • Zero Runtime Overhead: Direct function calls with no dynamic dispatch or allocations
  • Cross-platform Compatibility: Works across multiple target architectures and toolchains
  • No-std First: Designed for embedded and kernel environments without standard library dependencies

Sources: Cargo.toml(L12)  Cargo.toml(L15 - L18) 

Getting Started

Relevant source files

This document provides a quick start guide for using the crate_interface crate to define trait interfaces that can be implemented and called across crate boundaries. It covers the basic three-step workflow of defining interfaces, implementing them, and calling interface methods.

For comprehensive macro syntax and options, see Macro Reference. For understanding the underlying implementation details, see Architecture and Internals.

Purpose and Scope

The crate_interface crate solves circular dependency problems by enabling trait definitions in one crate while allowing implementations and usage in separate crates. This guide demonstrates the essential usage patterns through practical examples using the three core macros: def_interface, impl_interface, and call_interface!.

Installation

Add crate_interface to your Cargo.toml dependencies:

[dependencies]
crate_interface = "0.1.4"

The crate supports no-std environments and requires Rust 1.57 or later.

Sources: Cargo.toml(L1 - L22) 

Three-Step Workflow

The crate_interface system follows a consistent three-step pattern that maps directly to the three procedural macros:

Basic Workflow Diagram

flowchart TD
A["Step 1: Define"]
B["Step 2: Implement"]
C["Step 3: Call"]
D["#[def_interface]trait HelloIf"]
E["#[impl_interface]impl HelloIf"]
F["call_interface!HelloIf::hello"]
G["Generates __HelloIf_modextern fn declarations"]
H["Generates #[export_name]extern fn definitions"]
I["Generates unsafe__HelloIf_mod calls"]

A --> B
A --> D
B --> C
B --> E
C --> F
D --> G
E --> H
F --> I

Sources: README.md(L13 - L40)  tests/test_crate_interface.rs(L3 - L11) 

Step 1: Define Interface

Use the #[def_interface] attribute macro to define a trait interface:

#![allow(unused)]
fn main() {
#[crate_interface::def_interface]
pub trait HelloIf {
    fn hello(&self, name: &str, id: usize) -> String;
}
}

This generates the trait plus a hidden module containing extern "Rust" function declarations.

Step 2: Implement Interface

Use the #[impl_interface] attribute macro to implement the interface:

#![allow(unused)]
fn main() {
struct HelloIfImpl;

#[crate_interface::impl_interface]
impl HelloIf for HelloIfImpl {
    fn hello(&self, name: &str, id: usize) -> String {
        format!("Hello, {} {}!", name, id)
    }
}
}

This generates #[export_name] extern functions that can be linked across crates.

Step 3: Call Interface Methods

Use the call_interface! function-like macro to safely call interface methods:

use crate_interface::call_interface;

// Method-style calling
let result = call_interface!(HelloIf::hello("world", 123));

// Function-style calling  
let result = call_interface!(HelloIf::hello, "rust", 456);

Sources: README.md(L13 - L40)  tests/test_crate_interface.rs(L36 - L41) 

Generated Code Structure

Understanding what code gets generated helps debug issues and optimize usage:

Code Generation Mapping

flowchart TD
subgraph subGraph1["Generated Symbols"]
    D["trait HelloIf(unchanged)"]
    E["__HelloIf_mod module"]
    F["__HelloIf_helloextern declaration"]
    G["__HelloIf_helloextern definition"]
    H["#[export_name]attribute"]
    I["unsafe __HelloIf_mod::__HelloIf_hello call"]
end
subgraph subGraph0["Input Code"]
    A["#[def_interface]trait HelloIf"]
    B["#[impl_interface]impl HelloIf for HelloIfImpl"]
    C["call_interface!HelloIf::hello"]
end

A --> D
A --> E
A --> F
B --> G
B --> H
C --> I
F --> G
I --> F

The def_interface macro generates a module named __HelloIf_mod containing extern function declarations like __HelloIf_hello. The impl_interface macro creates matching extern function definitions with #[export_name] attributes. The call_interface! macro generates safe wrappers around unsafe extern calls.

Sources: README.md(L44 - L85) 

Advanced Usage Patterns

Default Method Implementations

Traits can include default implementations that will be preserved:

#![allow(unused)]
fn main() {
#[def_interface]
trait SimpleIf {
    fn foo() -> u32 {
        123  // Default implementation
    }
    
    fn bar(&self, a: u16, b: &[u8], c: &str);
}
}

Cross-Module Calls

Interface calls work from any module scope by using appropriate path syntax:

mod private {
    pub fn test_call_in_mod() {
        crate::call_interface!(super::SimpleIf::bar(123, &[2, 3, 5, 7, 11], "test"));
        crate::call_interface!(crate::SimpleIf::foo,);
    }
}

Method Documentation

Documentation comments are preserved in the generated code:

#![allow(unused)]
fn main() {
#[def_interface]
trait SimpleIf {
    /// Test comments for method
    fn bar(&self, a: u16, b: &[u8], c: &str);
}

#[impl_interface]  
impl SimpleIf for SimpleIfImpl {
    /// Implementation-specific documentation
    fn bar(&self, a: u16, b: &[u8], c: &str) {
        // Implementation here
    }
}
}

Sources: tests/test_crate_interface.rs(L3 - L42) 

Cross-Crate Usage

CrateRoleRequired Items
Interface CrateDefines traits#[def_interface]trait definition
Implementation CrateProvides implementations#[impl_interface]impl block + struct
Consumer CrateCalls methodscall_interface!macro calls

The implementation crate and consumer crate can be the same, different, or have multiple implementations across multiple crates.

Next Steps

Sources: README.md(L1 - L85)  tests/test_crate_interface.rs(L1 - L42)  Cargo.toml(L1 - L22) 

Macro Reference

Relevant source files

This page provides comprehensive documentation for the three core procedural macros that form the crate_interface system: def_interface, impl_interface, and call_interface. These macros work together to enable cross-crate trait interfaces by generating extern function declarations, exported implementations, and safe call wrappers.

For detailed documentation of individual macros, see def_interface Macro, impl_interface Macro, and call_interface Macro. For understanding the underlying architecture and symbol linking mechanisms, see Architecture and Internals.

Three-Macro System Overview

The crate_interface system consists of three interdependent procedural macros that implement a complete cross-crate interface solution:

Macro Interaction Flow

flowchart TD
A["#[def_interface] trait MyTrait"]
B["def_interface macro"]
C["Original trait + __MyTrait_mod"]
D["extern fn __MyTrait_method declarations"]
E["#[impl_interface] impl MyTrait"]
F["impl_interface macro"]
G["Modified impl block"]
H["#[export_name] extern fns"]
I["call_interface!(MyTrait::method)"]
J["call_interface macro"]
K["unsafe __MyTrait_mod::__MyTrait_method call"]
L["Rust Linker Symbol Table"]
M["Runtime Symbol Resolution"]

A --> B
B --> C
C --> D
D --> L
E --> F
F --> G
G --> H
H --> L
I --> J
J --> K
K --> L
L --> M

Sources: src/lib.rs(L28 - L75)  src/lib.rs(L87 - L162)  src/lib.rs(L192 - L210) 

Core Components

MacroPurposeInputOutput
def_interfaceInterface definitionTrait declarationTrait + hidden module with extern declarations
impl_interfaceInterface implementationTrait impl blockModified impl with exported extern functions
call_interfaceInterface invocationMethod call syntaxUnsafe extern function call

Generated Code Architecture

The macro system transforms high-level trait definitions into low-level extern function interfaces that can be linked across crate boundaries:

Code Generation Pipeline

flowchart TD
subgraph Linking["Linking"]
    K["Symbol: __HelloIf_hello"]
end
subgraph subGraph2["Generated Code"]
    G["__HelloIf_mod module"]
    H["extern fn __HelloIf_hello"]
    I["#[export_name] extern fn"]
    J["unsafe extern call"]
end
subgraph subGraph1["Macro Processing"]
    D["def_interface"]
    E["impl_interface"]
    F["call_interface"]
end
subgraph subGraph0["Input Code"]
    A["trait HelloIf"]
    B["impl HelloIf for HelloIfImpl"]
    C["HelloIf::hello call"]
end

A --> D
B --> E
C --> F
D --> G
D --> H
E --> I
F --> J
H --> K
I --> K
J --> K

Sources: src/lib.rs(L40 - L73)  src/lib.rs(L108 - L161)  src/lib.rs(L194 - L210)  README.md(L44 - L85) 

Naming Conventions

The macro system uses consistent naming patterns to ensure symbol uniqueness and avoid conflicts:

Symbol Generation Pattern

ElementPatternExample
Hidden module__{TraitName}_mod__HelloIf_mod
Extern function__{TraitName}_{method}__HelloIf_hello
Export symbol__{TraitName}_{method}__HelloIf_hello

Implementation Details

The naming convention implementation is found in the macro code:

Syntax Patterns

Basic Usage Syntax

#![allow(unused)]
fn main() {
// 1. Interface Definition
#[crate_interface::def_interface]
pub trait TraitName {
    fn method_name(&self, param: Type) -> ReturnType;
}

// 2. Interface Implementation  
#[crate_interface::impl_interface]
impl TraitName for StructName {
    fn method_name(&self, param: Type) -> ReturnType {
        // implementation
    }
}

// 3. Interface Invocation
call_interface!(TraitName::method_name(args))
call_interface!(TraitName::method_name, arg1, arg2)
}

Calling Syntax Variants

The call_interface macro supports two calling styles as demonstrated in the test suite:

StyleSyntaxExample
Parenthesescall_interface!(Trait::method(args))call_interface!(SimpleIf::bar(123, &[2, 3], "test"))
Comma-separatedcall_interface!(Trait::method, args)call_interface!(SimpleIf::bar, 123, &[2, 3], "test")

Sources: tests/test_crate_interface.rs(L31 - L32)  tests/test_crate_interface.rs(L38 - L39) 

Cross-Crate Symbol Resolution

Symbol Export and Import Mechanism

flowchart TD
subgraph subGraph3["Linker Resolution"]
    D1["Symbol Table"]
    D2["__MyIf_method symbol"]
end
subgraph subGraph2["Crate C: Usage"]
    C1["call_interface!(MyIf::method)"]
    C2["unsafe __MyIf_mod::__MyIf_method call"]
end
subgraph subGraph1["Crate B: Implementation"]
    B1["#[impl_interface] impl MyIf"]
    B2["#[export_name = __MyIf_method]"]
    B3["extern fn __MyIf_method definition"]
end
subgraph subGraph0["Crate A: Interface Definition"]
    A1["#[def_interface] trait MyIf"]
    A2["__MyIf_mod module"]
    A3["extern fn __MyIf_method declaration"]
end

A1 --> A2
A2 --> A3
A3 --> D1
B1 --> B2
B2 --> B3
B3 --> D1
C1 --> C2
C2 --> D1
D1 --> D2

Sources: src/lib.rs(L54 - L58)  src/lib.rs(L147 - L151)  src/lib.rs(L209) 

Macro Attributes and Visibility

Visibility Inheritance

The macro system preserves and propagates visibility modifiers:

Attribute Preservation

The implementation preserves existing attributes on trait methods and impl functions, as seen in the test examples with #[cfg(test)] and doc comments: tests/test_crate_interface.rs(L9)  tests/test_crate_interface.rs(L17)  tests/test_crate_interface.rs(L22) 

Error Handling

The macro system includes comprehensive error checking for common usage mistakes:

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L29 - L34)  src/lib.rs(L89 - L94)  src/lib.rs(L97 - L106)  src/lib.rs(L198 - L200) 

def_interface Macro

Relevant source files

The def_interface macro is a procedural attribute macro that transforms trait definitions to enable cross-crate implementations without circular dependencies. It generates extern function declarations that serve as the linking contract between trait definitions and their implementations across crate boundaries.

For information about implementing these interfaces, see impl_interface Macro. For calling interface methods, see call_interface Macro.

Purpose and Functionality

The def_interface macro solves the circular dependency problem in Rust by creating a trait interface that can be implemented in any crate. When applied to a trait definition, it generates both the original trait and a hidden module containing extern function declarations that correspond to each trait method.

flowchart TD
A["Original Trait Definition"]
B["def_interface macro"]
C["Generated Trait + Hidden Module"]
D["Original Trait"]
E["__TraitName_mod module"]
F["extern fn declarations"]
G["Symbol linking contract"]

A --> B
B --> C
C --> D
C --> E
E --> F
F --> G

def_interface Macro Processing Flow

Sources: src/lib.rs(L28 - L75)  README.md(L14 - L18) 

Syntax and Usage

The macro is applied as an attribute above trait definitions with no additional parameters:

#![allow(unused)]
fn main() {
#[crate_interface::def_interface]
pub trait TraitName {
    fn method_name(&self, param: Type) -> ReturnType;
}
}

The macro accepts only empty attributes and will generate a compile error if any parameters are provided.

Sources: src/lib.rs(L29 - L34)  tests/test_crate_interface.rs(L3 - L11) 

Generated Code Structure

When def_interface processes a trait, it generates two main components:

1. Original Trait Preservation

The original trait definition is preserved unchanged, maintaining all method signatures, documentation, and attributes.

2. Hidden Module Generation

A hidden module is created with the naming pattern __TraitName_mod containing extern function declarations.

flowchart TD
subgraph subGraph2["Extern Function Signature"]
    E["name: &str"]
    F["id: usize"]
    G["-> String"]
end
subgraph subGraph1["Generated Module"]
    A["trait HelloIf"]
    B["fn hello(&self, name: &str, id: usize)"]
    C["__HelloIf_mod"]
    D["extern fn __HelloIf_hello"]
end
subgraph subGraph0["Input Trait"]
    A["trait HelloIf"]
    B["fn hello(&self, name: &str, id: usize)"]
    C["__HelloIf_mod"]
end

A --> B
C --> D
D --> E
D --> F
D --> G

Code Generation Structure for def_interface

Sources: src/lib.rs(L36 - L74)  README.md(L47 - L58) 

Function Signature Transformation

The macro transforms trait method signatures into extern function signatures by:

  1. Name Transformation: Method names become __TraitName_methodName
  2. Self Parameter Removal: The &self parameter is stripped from extern signatures
  3. Parameter Preservation: All typed parameters are preserved in their original order
flowchart TD
subgraph subGraph1["Generated Components"]
    G["extern fn __TraitName_method"]
    H["pub visibility"]
    I["Rust ABI"]
end
subgraph subGraph0["Method Signature Processing"]
    A["sig.ident = format_ident!"]
    B["_{}{} pattern"]
    C["sig.inputs = Punctuated::new()"]
    D["Clear input parameters"]
    E["Filter FnArg::Typed"]
    F["Preserve non-self params"]
end

A --> B
B --> G
C --> D
D --> G
E --> F
F --> G
H --> G
I --> G

Function Signature Transformation Process

Sources: src/lib.rs(L40 - L58) 

Module Structure and Visibility

The generated module follows a specific structure:

ComponentPatternPurpose
Module Name__TraitName_modHidden module containing extern declarations
Module VisibilityInherits from traitMatches original trait visibility
Module Attributes#[doc(hidden)],#[allow(non_snake_case)]Hide from documentation, allow naming convention
Extern Blockextern "Rust"Rust ABI for cross-crate linking

The module includes a use super::*; statement to inherit the parent scope, ensuring type definitions remain accessible.

Sources: src/lib.rs(L61 - L73) 

Integration with Other Macros

The def_interface macro creates the foundation for the other two macros in the system:

flowchart TD
subgraph subGraph2["call_interface Usage"]
    G["References __TraitName_mod"]
    H["Calls extern functions"]
    I["Provides safe wrapper"]
end
subgraph subGraph1["impl_interface Usage"]
    D["Reads trait signature"]
    E["Generates #[export_name]"]
    F["Creates exported functions"]
end
subgraph subGraph0["def_interface Output"]
    A["Original Trait"]
    B["__TraitName_mod"]
    C["extern fn __TraitName_method"]
end

A --> D
B --> G
C --> E
C --> H
F --> H

Macro System Integration

Sources: src/lib.rs(L88 - L162)  src/lib.rs(L193 - L210) 

Error Handling and Validation

The macro performs several validation checks:

  1. Attribute Validation: Ensures no parameters are provided
  2. Trait Item Processing: Only processes TraitItem::Fn items
  3. Method Signature Extraction: Safely extracts method signatures and parameters

Invalid usage results in compile-time errors with descriptive messages.

Sources: src/lib.rs(L29 - L34)  src/lib.rs(L14 - L16) 

Examples and Test Cases

The test suite demonstrates various usage patterns:

#![allow(unused)]
fn main() {
#[def_interface]
trait SimpleIf {
    fn foo() -> u32 { 123 }
    fn bar(&self, a: u16, b: &[u8], c: &str);
}
}

This generates:

  • The original SimpleIf trait
  • Module __SimpleIf_mod with extern declarations
  • Functions __SimpleIf_foo and __SimpleIf_bar

Sources: tests/test_crate_interface.rs(L3 - L11)  README.md(L14 - L18) 

Technical Implementation Details

The macro implementation uses several key Rust procedural macro components:

ComponentPurposeUsage
syn::ItemTraitParse trait definitionsInput parsing
format_ident!Generate identifiersName transformation
quote!Code generationOutput synthesis
proc_macro2::SpanError reportingCompile-time diagnostics

The generated code uses extern "Rust" blocks to leverage Rust's symbol mangling and linking behavior, enabling cross-crate function resolution without runtime overhead.

Sources: src/lib.rs(L3 - L12)  src/lib.rs(L36 - L74) 

impl_interface Macro

Relevant source files

The impl_interface macro enables trait implementations to be used across crate boundaries by generating exported extern functions that can be linked at runtime. This macro transforms a standard trait implementation into a cross-crate compatible implementation by creating symbol exports that follow the interface contract established by def_interface.

For information about defining interfaces, see def_interface Macro. For information about calling cross-crate interfaces, see call_interface Macro.

Purpose and Functionality

The impl_interface macro serves as the implementation phase of the three-macro system. It takes a trait implementation and automatically generates the necessary extern function exports that can be linked to by other crates, eliminating the need for direct crate dependencies while maintaining type safety.

Core Functions:

  • Parses trait implementations and extracts method signatures
  • Generates #[export_name] extern functions for each trait method
  • Maintains original implementation logic while adding cross-crate compatibility
  • Handles both instance methods (&self) and static methods

Sources: src/lib.rs(L77 - L86)  README.md(L20 - L28) 

Syntax and Usage

The macro is applied as an attribute to trait implementations:

#[impl_interface]
impl TraitName for StructName {
    // method implementations
}

The macro requires:

  • A trait implementation (not a standalone impl block)
  • The trait must be previously defined with #[def_interface]
  • The implementation struct must be a named type (not anonymous)

Usage Example from Tests:

#![allow(unused)]
fn main() {
#[impl_interface]
impl SimpleIf for SimpleIfImpl {
    fn foo() -> u32 { 456 }
    fn bar(&self, a: u16, b: &[u8], c: &str) { /* implementation */ }
}
}

Sources: tests/test_crate_interface.rs(L15 - L27)  src/lib.rs(L87 - L94) 

Code Generation Process

Implementation Analysis Flow

flowchart TD
A["Parse ItemImpl AST"]
B["Extract trait_name"]
C["Extract impl_name (struct)"]
D["Iterate over impl methods"]
E["For each ImplItem::Fn"]
F["Generate extern function signature"]
G["Create export_name attribute"]
H["Generate method wrapper"]
I["Insert into original method"]
J["Return modified implementation"]

A --> B
B --> C
C --> D
D --> E
D --> J
E --> F
F --> G
G --> H
H --> I
I --> D

The macro performs the following transformations:

  1. Parse Implementation: Extracts trait name and implementing struct name from the AST
  2. Method Processing: For each method in the implementation, generates corresponding extern functions
  3. Signature Transformation: Removes self parameter from extern function signatures while preserving other parameters
  4. Export Generation: Creates #[export_name] attributes following the __TraitName_methodName convention
  5. Wrapper Injection: Injects the extern function into the original method implementation

Sources: src/lib.rs(L96 - L107)  src/lib.rs(L108 - L161) 

Generated Code Structure

flowchart TD
subgraph subGraph0["Generated Components"]
    D["Original method body"]
    E["Inline extern function"]
    F["#[export_name] attribute"]
    G["Symbol export"]
end
A["Original Implementation"]
B["impl_interface macro"]
C["Modified Implementation"]

A --> B
B --> C
C --> D
C --> E
E --> F
F --> G

Sources: src/lib.rs(L140 - L157)  README.md(L62 - L78) 

Method Signature Processing

The macro handles two types of method signatures differently:

Instance Methods (with&self)

For methods that take &self as the first parameter:

  1. Extern Function: Removes &self from the signature
  2. Call Generation: Creates an instance of the implementing struct and calls the method on it
  3. Parameter Forwarding: Passes all non-self parameters to the method call

Generated Pattern:

#[export_name = "__TraitName_methodName"]
extern "Rust" fn __TraitName_methodName(/* params without self */) -> ReturnType {
    let _impl: StructName = StructName;
    _impl.methodName(/* params */)
}

Static Methods (no&self)

For static methods that don't take &self:

  1. Direct Call: Calls the static method directly on the implementing type
  2. Parameter Preservation: Maintains all original parameters in the extern function

Generated Pattern:

#![allow(unused)]
fn main() {
#[export_name = "__TraitName_methodName"]
extern "Rust" fn __TraitName_methodName(/* all params */) -> ReturnType {
    StructName::methodName(/* params */)
}
}

Sources: src/lib.rs(L119 - L138)  tests/test_crate_interface.rs(L18 - L26) 

Symbol Export Mechanism

Export Name Generation

flowchart TD
A["trait_name: SimpleIf"]
C["Combine with separator"]
B["method_name: bar"]
D["__SimpleIf_bar"]
E["#[export_name] attribute"]
F["Linker symbol export"]
G["External crate"]
H["Symbol resolution"]
I["Runtime function call"]

A --> C
B --> C
C --> D
D --> E
E --> F
F --> H
G --> H
H --> I

The naming convention __TraitName_methodName ensures:

  • Uniqueness: Prevents symbol collisions across different traits
  • Consistency: Matches the naming pattern expected by def_interface
  • Linkability: Creates symbols that the Rust linker can resolve across crates

Sources: src/lib.rs(L113 - L116)  src/lib.rs(L148) 

Integration with Macro System

Cross-Macro Coordination

flowchart TD
subgraph subGraph2["call_interface Usage"]
    D["unsafe call to extern fn"]
end
subgraph subGraph1["impl_interface Output"]
    B["#[export_name = '__TraitName_method']"]
    C["extern fn implementation"]
end
subgraph subGraph0["def_interface Output"]
    A["extern fn __TraitName_method"]
end
E["Linker resolution"]

A --> B
C --> E
D --> E

The impl_interface macro must coordinate with the other macros:

  1. Symbol Contract: Must export functions with names matching def_interface declarations
  2. Type Compatibility: Generated extern functions must match the signatures expected by call_interface
  3. Linking Requirements: Exported symbols must be available at link time for consuming crates

Sources: src/lib.rs(L203 - L209)  README.md(L52 - L58) 

Error Handling and Validation

The macro performs several validation checks:

Input Validation:

  • Ensures the attribute has no parameters: #[impl_interface] only
  • Verifies the target is a trait implementation, not a standalone impl block
  • Requires the implementing type to be a named struct (not anonymous)

Error Cases:

// Invalid: attribute parameters
#[impl_interface(param)]  // Compile error

// Invalid: not a trait implementation  
#[impl_interface]
impl SomeStruct { }  // Compile error

// Invalid: anonymous type
#[impl_interface] 
impl SomeTrait for (u32, String) { }  // Compile error

Sources: src/lib.rs(L88 - L94)  src/lib.rs(L99 - L106) 

Complete Transformation Example

Input Code:

#![allow(unused)]
fn main() {
#[impl_interface]
impl SimpleIf for SimpleIfImpl {
    fn bar(&self, a: u16, b: &[u8], c: &str) {
        println!("{} {:?} {}", a, b, c);
    }
}
}

Generated Output (Conceptual):

impl SimpleIf for SimpleIfImpl {
    #[inline]
    fn bar(&self, a: u16, b: &[u8], c: &str) {
        {
            #[inline]
            #[export_name = "__SimpleIf_bar"]
            extern "Rust" fn __SimpleIf_bar(a: u16, b: &[u8], c: &str) {
                let _impl: SimpleIfImpl = SimpleIfImpl;
                _impl.bar(a, b, c)
            }
        }
        {
            println!("{} {:?} {}", a, b, c);
        }
    }
}

This transformation enables the implementation to be called from any crate that links to this one, without requiring direct dependency relationships.

Sources: README.md(L62 - L78)  tests/test_crate_interface.rs(L23 - L26) 

call_interface Macro

Relevant source files

The call_interface macro provides a safe wrapper for invoking methods defined by traits marked with #[def_interface] and implemented with #[impl_interface]. This procedural macro encapsulates unsafe extern function calls, enabling cross-crate trait method invocation without requiring direct dependencies between interface definitions and implementations.

For information about defining interfaces, see def_interface Macro. For implementing interfaces, see impl_interface Macro.

Purpose and Functionality

The call_interface! macro serves as the consumer-facing component of the crate_interface system. It transforms trait method calls into unsafe extern function invocations that link to implementations across crate boundaries at runtime.

Basic Syntax Patterns

The macro supports two primary calling conventions:

Syntax PatternDescriptionExample
Parenthesized ArgumentsTraditional function call syntaxcall_interface!(HelloIf::hello("world", 123))
Comma-Separated ArgumentsAlternative syntax with comma separatorcall_interface!(HelloIf::hello, "rust", 456)
No ArgumentsFunction calls without parameterscall_interface!(SimpleIf::foo)

Sources: README.md(L30 - L40)  tests/test_crate_interface.rs(L37 - L41) 

Macro Implementation Architecture

Call Interface Parsing Structure

flowchart TD
Input["call_interface!(Trait::method, arg1, arg2)"]
Parser["CallInterface::parse()"]
PathStruct["Path: Trait::method"]
ArgsStruct["Punctuated"]
TraitName["trait_name: Ident"]
MethodName["fn_name: Ident"]
ExternFnName["__TraitName_methodName"]
ModPath["__TraitName_mod"]
UnsafeCall["unsafe { __TraitName_mod::__TraitName_methodName(args) }"]

ArgsStruct --> UnsafeCall
ExternFnName --> UnsafeCall
Input --> Parser
MethodName --> ExternFnName
ModPath --> UnsafeCall
Parser --> ArgsStruct
Parser --> PathStruct
PathStruct --> MethodName
PathStruct --> TraitName
TraitName --> ExternFnName

Sources: src/lib.rs(L164 - L184)  src/lib.rs(L192 - L210) 

Code Transformation Process

The macro performs a systematic transformation from trait method calls to extern function invocations:


Sources: src/lib.rs(L194 - L210) 

Implementation Details

CallInterface Structure

The macro uses a custom CallInterface struct to parse input tokens:

FieldTypePurpose
pathPathContains the trait and method names (e.g.,HelloIf::hello)
argsPunctuated<Expr, Token![,]>Function arguments parsed as expressions

The parsing logic handles both parenthesized and comma-separated argument formats by checking for different token patterns.

Sources: src/lib.rs(L164 - L184) 

Symbol Name Generation

The macro generates extern function names using a consistent naming convention:

__<TraitName>_<MethodName>

For example, HelloIf::hello becomes __HelloIf_hello. This naming scheme ensures unique symbol names across the entire linked binary while maintaining predictable resolution patterns.

Sources: src/lib.rs(L203) 

Module Path Resolution

Generated extern functions are accessed through hidden modules created by #[def_interface]:

__<TraitName>_mod::<ExternFunctionName>

This module structure provides namespace isolation and enables the linker to resolve symbols correctly across crate boundaries.

Sources: src/lib.rs(L205 - L209) 

Usage Patterns and Examples

Standard Method Calls

The most common usage pattern involves calling trait methods with arguments:

// Interface definition (in any crate)
#[def_interface]
trait HelloIf {
    fn hello(&self, name: &str, id: usize) -> String;
}

// Usage (in any other crate)
let result = call_interface!(HelloIf::hello("world", 123));

Sources: README.md(L13 - L18)  README.md(L30 - L35) 

Alternative Calling Syntax

The macro supports comma-separated arguments as an alternative to parenthesized calls:

// Both forms are equivalent
call_interface!(HelloIf::hello("rust", 456))
call_interface!(HelloIf::hello, "rust", 456)

Sources: README.md(L36 - L39) 

Static Method Calls

Methods without &self parameters (static methods) are supported:

let value = call_interface!(SimpleIf::foo);

Sources: tests/test_crate_interface.rs(L39) 

Cross-Module Usage

The macro works correctly when called from within modules or different crate contexts:

mod private {
    pub fn test_call_in_mod() {
        crate::call_interface!(super::SimpleIf::bar(123, &[2, 3, 5, 7, 11], "test"));
        crate::call_interface!(crate::SimpleIf::foo,);
    }
}

Sources: tests/test_crate_interface.rs(L29 - L34) 

Safety and Runtime Behavior

Unsafe Encapsulation

The call_interface! macro encapsulates unsafe extern function calls, providing a safer interface while maintaining the underlying performance characteristics. The generated code always uses unsafe blocks because extern function calls are inherently unsafe in Rust.

The macro relies on the Rust linker to resolve extern function symbols at link time. If an implementation is not linked into the final binary, the program will fail to link rather than producing runtime errors.

Runtime Assumptions

The macro assumes that:

  • The target trait was defined with #[def_interface]
  • An implementation exists and was compiled with #[impl_interface]
  • All required crates are linked together in the final binary

Sources: src/lib.rs(L209)  README.md(L80 - L85) 

Integration with Interface System

Relationship to Other Macros

flowchart TD
subgraph subGraph3["Runtime Linking"]
    Linker["Rust Linker"]
end
subgraph subGraph2["Usage Phase"]
    CallMacro["call_interface!"]
    UserCall["HelloIf::hello(args)"]
    UnsafeCall["unsafe { __HelloIf_mod::__HelloIf_hello(args) }"]
end
subgraph subGraph1["Implementation Phase"]
    ImplMacro["#[impl_interface]"]
    ImplBlock["impl HelloIf for HelloIfImpl"]
    ExportedFn["#[export_name] extern fn"]
end
subgraph subGraph0["Interface Definition Phase"]
    DefMacro["#[def_interface]"]
    TraitDef["trait HelloIf"]
    HiddenMod["__HelloIf_mod"]
    ExternDecl["extern fn __HelloIf_hello"]
end

CallMacro --> UnsafeCall
CallMacro --> UserCall
DefMacro --> HiddenMod
DefMacro --> TraitDef
ExportedFn --> Linker
ExternDecl --> Linker
HiddenMod --> ExternDecl
ImplMacro --> ExportedFn
ImplMacro --> ImplBlock
UnsafeCall --> Linker

Sources: src/lib.rs(L186 - L210)  README.md(L42 - L85) 

The call_interface! macro completes the interface system by providing the consumer interface that connects to implementations through the linker's symbol resolution mechanism.

Architecture and Internals

Relevant source files

This document provides a deep technical dive into the internal architecture of the crate_interface procedural macro system. It explains how the three-phase macro transformation works, the extern symbol generation and linking mechanisms, and the cross-crate communication patterns that enable trait interfaces without circular dependencies.

For basic usage examples, see Getting Started. For detailed macro syntax and options, see Macro Reference.

Overall System Architecture

The crate_interface system implements a three-phase transformation pipeline that converts trait definitions and implementations into extern function-based cross-crate interfaces.

Three-Phase Transformation Pipeline

flowchart TD
subgraph subGraph3["Runtime[Rust Linker Resolution]"]
    L["Symbol table matching"]
end
subgraph subGraph2["Phase3[Phase 3: Interface Invocation]"]
    I["call_interface! macro"]
    J["Unsafe extern call generated"]
    K["__TraitName_mod::__TraitName_methodName invoked"]
end
subgraph subGraph1["Phase2[Phase 2: Implementation Binding]"]
    E["impl_interface macro"]
    F["Implementation preserved"]
    G["Nested extern fn with export_name"]
    H["Symbol __TraitName_methodName exported"]
end
subgraph subGraph0["Phase1[Phase 1: Interface Definition]"]
    A["def_interface macro"]
    B["Original trait preserved"]
    C["Hidden module __TraitName_mod created"]
    D["extern fn __TraitName_methodName declarations"]
end

A --> B
A --> C
C --> D
D --> L
E --> F
E --> G
G --> H
H --> L
I --> J
J --> K
K --> L

Sources: src/lib.rs(L28 - L75)  src/lib.rs(L88 - L162)  src/lib.rs(L193 - L210) 

Code Entity Mapping


Sources: README.md(L47 - L85)  src/lib.rs(L45 - L46)  src/lib.rs(L113 - L116) 

Symbol Generation and Naming Conventions

The macro system uses a consistent naming convention to generate unique extern function symbols that can be linked across crate boundaries.

Symbol Generation Process

InputGenerated SymbolPurpose
trait MyTrait+fn my_method__MyTrait_my_methodExtern function name
trait MyTrait__MyTrait_modHidden module name
Implementation#[export_name = "__MyTrait_my_method"]Linker symbol

The symbol generation follows this pattern implemented in the macros:


Sources: src/lib.rs(L45 - L46)  src/lib.rs(L61)  src/lib.rs(L113 - L116)  src/lib.rs(L203 - L206) 

Function Signature Transformation

The macros perform signature transformations to convert trait methods into extern functions:


The self parameter is removed from extern function signatures since the implementation struct is instantiated within the exported function body.

Sources: src/lib.rs(L46 - L52)  src/lib.rs(L120 - L129) 

Cross-Crate Linking Mechanism

The system leverages Rust's extern function linking to enable cross-crate trait implementations without circular dependencies.

Linking Architecture


Sources: src/lib.rs(L69 - L71)  src/lib.rs(L148 - L149)  src/lib.rs(L209) 

Hidden Module System

The def_interface macro generates hidden modules to contain extern function declarations:


The hidden module serves several purposes:

  • Namespace isolation for extern function declarations
  • Visibility control with #[doc(hidden)]
  • Snake case warnings suppression with #[allow(non_snake_case)]
  • Access to parent scope types via use super::*

Sources: src/lib.rs(L65 - L72) 

Procedural Macro Implementation Details

Each macro in the system performs specific AST transformations using the syn, quote, and proc_macro2 crates.

def_interface Implementation


Sources: src/lib.rs(L36 - L38)  src/lib.rs(L41 - L58)  src/lib.rs(L62 - L74) 

impl_interface Implementation

flowchart TD
subgraph subGraph2["Generate[Nested Function Generation]"]
    C1["Create inline extern fn with export_name"]
    C2["Embed call_impl in extern fn body"]
    C3["Preserve original method implementation"]
    C4["Wrap in method structure"]
end
subgraph subGraph1["Transform[Method Transformation]"]
    B1["Clone original method signature"]
    B2["Generate extern_fn_name string"]
    B3["Create new_sig without self parameter"]
    B4["Extract args from original signature"]
    B5["Generate call_impl based on has_self"]
end
subgraph subGraph0["Extract[Extraction Phase]"]
    A1["syn::parse_macro_input!(item as ItemImpl)"]
    A2["Extract trait_name from ast.trait_"]
    A3["Extract impl_name from ast.self_ty"]
    A4["Iterate ast.items for ImplItem::Fn"]
end

A1 --> A2
A2 --> A3
A3 --> A4
A4 --> B1
B1 --> B2
B2 --> B3
B3 --> B4
B4 --> B5
B5 --> C1
C1 --> C2
C2 --> C3
C3 --> C4

Sources: src/lib.rs(L96 - L106)  src/lib.rs(L108 - L138)  src/lib.rs(L140 - L161) 

call_interface Implementation

The call_interface macro uses a custom parser to handle multiple calling syntaxes:

flowchart TD
subgraph subGraph2["Generate[Call Generation]"]
    C1["quote! { unsafe { #path :: #extern_fn_name( #args ) } }"]
end
subgraph subGraph1["PathProcess[Path Processing]"]
    B1["Extract fn_name = path.pop()"]
    B2["Extract trait_name = path.pop()"]
    B3["Generate extern_fn_name"]
    B4["Reconstruct path with __TraitName_mod"]
end
subgraph subGraph0["Parse[Parsing Strategy]"]
    A1["Parse path: Path"]
    A2["Check for comma or parentheses"]
    A3["Parse args as Punctuated"]
end

A1 --> A2
A2 --> A3
A3 --> B1
B1 --> B2
B2 --> B3
B3 --> B4
B4 --> C1

Sources: src/lib.rs(L164 - L184)  src/lib.rs(L194 - L210) 

Memory Layout and Safety Considerations

The system maintains memory safety by carefully managing the transition between safe trait calls and unsafe extern function calls.

Safety Boundary Management


The safety guarantees rely on:

  • ABI Compatibility: All functions use extern "Rust" calling convention
  • Type Preservation: Function signatures are preserved exactly during transformation
  • Symbol Uniqueness: Naming convention prevents symbol collisions
  • Linker Validation: Missing implementations cause link-time errors, not runtime failures

Sources: src/lib.rs(L54 - L56)  src/lib.rs(L148 - L151)  src/lib.rs(L209) 

Object Instantiation Pattern

The impl_interface macro generates a specific pattern for handling self parameters:

flowchart TD
subgraph subGraph1["StaticHandling[Static Method Handling]"]
    B1["Original: fn static_method(args...)"]
    B2["Extern: fn __Trait_static_method(args...)"]
    B3["ImplStruct::static_method(args)"]
    A1["Original: fn method(&self, args...)"]
    A2["Extern: fn __Trait_method(args...)"]
    A3["let _impl: ImplStruct = ImplStruct;"]
end
subgraph subGraph0["SelfHandling[Self Parameter Handling]"]
    B1["Original: fn static_method(args...)"]
    B2["Extern: fn __Trait_static_method(args...)"]
    B3["ImplStruct::static_method(args)"]
    A1["Original: fn method(&self, args...)"]
    A2["Extern: fn __Trait_method(args...)"]
    A3["let _impl: ImplStruct = ImplStruct;"]
    A4["_impl.method(args)"]
end

A1 --> A2
A2 --> A3
A3 --> A4
B1 --> B2
B2 --> B3

This pattern ensures that:

  • Instance methods get a fresh instance of the implementing struct
  • Static methods are called directly on the implementing type
  • Memory layout is predictable and doesn't depend on external state

Sources: src/lib.rs(L131 - L138) 

Development Guide

Relevant source files

This document provides comprehensive guidance for developers contributing to the crate_interface project. It covers the development environment setup, project structure, contribution workflows, and development practices specific to this procedural macro crate.

For detailed information about testing practices, see Testing. For CI/CD pipeline specifics, see CI/CD Pipeline. For in-depth project structure analysis, see Project Structure.

Development Environment Setup

The crate_interface project is a procedural macro crate that requires specific Rust toolchain capabilities and dependencies for development.

Development Workflow

flowchart TD
subgraph subGraph0["Local Development Loop"]
    E["Implement changes"]
    L["cargo fmt"]
    M["cargo clippy"]
    N["cargo test"]
    O["cargo doc"]
end
A["Fork repository"]
B["Clone locally"]
C["Setup development environment"]
D["Create feature branch"]
F["Run local tests"]
G["Update documentation"]
H["Submit pull request"]
I["CI validation"]
J["Code review"]
K["Merge to main"]

A --> B
B --> C
C --> D
D --> E
E --> F
E --> L
F --> G
G --> H
H --> I
I --> J
J --> K
L --> M
M --> N
N --> O
O --> E

Sources: Cargo.toml(L1 - L22) 

Required Tools and Dependencies

The project uses a minimal but specific set of dependencies for procedural macro development:

ComponentVersionPurpose
proc-macro21.0Low-level token manipulation
quote1.0Code generation from templates
syn2.0Rust syntax tree parsing

The syn dependency is configured with the full feature to enable complete Rust syntax parsing capabilities Cargo.toml(L18) 

Minimum Rust Version

The project requires Rust 1.57 or later Cargo.toml(L13)  which ensures compatibility with:

  • Modern syn v2.0 APIs
  • Stable procedural macro features
  • Target architectures used in ArceOS ecosystem

Project Structure and Development Architecture

flowchart TD
subgraph subGraph4["Development Artifacts"]
    L["target/"]
    M[".vscode/"]
    N["Cargo.lock"]
end
subgraph subGraph3["CI Configuration"]
    J[".github/workflows/"]
    K["CI pipeline definitions"]
end
subgraph subGraph2["Test Structure"]
    H["tests/"]
    I["Integration tests"]
end
subgraph subGraph1["Source Structure"]
    G["src/lib.rs"]
end
subgraph subGraph0["Repository Root"]
    A["Cargo.toml"]
    B["src/"]
    C["tests/"]
    D[".github/"]
    E["README.md"]
    F[".gitignore"]
end

A --> G
B --> G
C --> I
D --> K
F --> L
F --> M
F --> N

Sources: Cargo.toml(L1 - L22)  .gitignore(L1 - L5) 

Contribution Workflow

Code Organization Principles

The crate_interface project follows a single-file library structure with all core functionality contained in src/lib.rs. This architectural decision supports:

  • Simplicity: Single point of entry for all macro definitions
  • Maintainability: Reduced complexity in navigation and understanding
  • Performance: Minimized compilation overhead for consumers

Development Practices

Procedural Macro Development

When working with the three core macros (def_interface, impl_interface, call_interface), follow these practices:

  1. Token Stream Handling: Use proc-macro2::TokenStream for all internal processing
  2. Error Reporting: Leverage syn::Error for compilation-time error messages
  3. Code Generation: Use quote! macros for generating Rust code templates

Testing Strategy

The project employs comprehensive integration testing to validate macro functionality across different usage patterns. Test cases should cover:

  • Basic interface definition and implementation scenarios
  • Cross-crate compilation patterns
  • Error conditions and edge cases
  • Target architecture compatibility

For complete testing documentation, see Testing.

Dependencies and Build Configuration

flowchart TD
subgraph subGraph2["Generated Artifacts"]
    G["TokenStream processing"]
    H["Code generation"]
    I["AST manipulation"]
end
subgraph subGraph1["Macro System"]
    D["def_interface"]
    E["impl_interface"]
    F["call_interface"]
end
subgraph subGraph0["Core Dependencies"]
    A["proc-macro2"]
    B["quote"]
    C["syn"]
end

A --> G
B --> H
C --> I
G --> D
G --> E
G --> F
H --> D
H --> E
H --> F
I --> D
I --> E
I --> F

Sources: Cargo.toml(L15 - L18) 

The build system is configured as a procedural macro crate Cargo.toml(L20 - L21)  which enables the proc_macro attribute and allows the macros to be used by consuming crates.

Version Control and Exclusions

The project maintains a minimal .gitignore configuration .gitignore(L1 - L5)  that excludes:

  • Build Artifacts: /target directory containing compilation outputs
  • Editor Configuration: .vscode directory for development environment settings
  • System Files: .DS_Store for macOS filesystem metadata
  • Lock Files: Cargo.lock which is typically excluded for library crates

This configuration ensures that only source code and essential project files are tracked in version control.

Package Metadata and Distribution

The crate is configured for distribution through multiple channels:

  • Primary Repository: GitHub at arceos-org/crate_interface
  • Documentation: Hosted on docs.rs
  • Package Registry: Available through crates.io
  • License: Triple-licensed under GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0

The package is categorized for procedural macro development tools and no-std compatibility Cargo.toml(L12)  making it discoverable for embedded and systems programming use cases.

Sources: Cargo.toml(L7 - L12) 

Testing

Relevant source files

This document covers the test suite for the crate_interface crate, including test structure, patterns used to validate macro functionality, and how to run the tests. The tests demonstrate practical usage of the three core macros (def_interface, impl_interface, and call_interface!) and verify that cross-crate trait interfaces work correctly at runtime.

For information about the CI/CD pipeline and automated testing infrastructure, see CI/CD Pipeline. For details about project structure and development setup, see Project Structure.

Test Suite Overview

The crate_interface crate uses integration tests to validate macro functionality. The test suite is designed to verify that the generated code correctly implements cross-crate trait interfaces through extern function linking.

flowchart TD
A["test_crate_interface.rs"]
B["SimpleIf trait definition"]
C["SimpleIfImpl struct"]
D["test_crate_interface_call function"]
E["#[def_interface] macro"]
F["#[impl_interface] macro"]
G["call_interface! macro"]
H["Generated extern fn declarations"]
I["Generated #[export_name] functions"]
J["Runtime function calls"]
K["Symbol linking"]
L["Test execution and validation"]

A --> B
A --> C
A --> D
B --> E
C --> F
D --> G
E --> H
F --> I
G --> J
H --> K
I --> K
J --> K
K --> L

Test Architecture Flow

Sources: tests/test_crate_interface.rs(L1 - L42) 

Test Structure and Patterns

The test file follows a specific pattern that mirrors real-world usage of the crate_interface system across multiple crates, but compressed into a single test file for validation.

Interface Definition Pattern

The test defines a trait using the #[def_interface] attribute with both default implementations and abstract methods:

flowchart TD
A["SimpleIf trait"]
B["foo() -> u32"]
C["bar(&self, a: u16, b: &[u8], c: &str)"]
D["Default implementation: 123"]
E["Abstract method"]
F["#[def_interface]"]
G["Can be overridden"]
H["Must be implemented"]

A --> B
A --> C
B --> D
C --> E
D --> G
E --> H
F --> A

Interface Definition Components

The trait includes documentation comments and various parameter types to test the macro's handling of different Rust syntax elements.

Sources: tests/test_crate_interface.rs(L3 - L11) 

Implementation Pattern

The test implements the interface using the #[impl_interface] attribute, demonstrating both method override and concrete implementation:

MethodTypeTest Purpose
foo()Static method with overrideTests default method replacement with#[cfg(test)]
bar()Instance methodTests parameter passing and assertions

The implementation includes conditional compilation (#[cfg(test)]) to test macro handling of Rust attributes.

Sources: tests/test_crate_interface.rs(L13 - L27) 

Module Visibility Testing

The test suite includes a private module to verify that interface calls work correctly across module boundaries:

flowchart TD
A["test_crate_interface_call"]
B["Direct interface calls"]
C["private::test_call_in_mod"]
D["super::SimpleIf::bar call"]
E["crate::SimpleIf::foo call"]
F["Module boundary"]
G["Path resolution testing"]

A --> B
A --> C
C --> D
C --> E
F --> D
F --> E
G --> D
G --> E

Module Testing Pattern

Sources: tests/test_crate_interface.rs(L29 - L34) 

Test Execution Patterns

The main test function demonstrates different calling syntaxes supported by the call_interface! macro:


Interface Call Testing Matrix

Call PatternSyntaxPurpose
Method with argscall_interface!(SimpleIf::bar, 123, &[2, 3, 5, 7, 11], "test")Tests parameter passing
Method return valueassert_eq!(call_interface!(SimpleIf::foo), 456)Tests return value handling
Trailing commacall_interface!(SimpleIf::foo,)Tests syntax flexibility

Sources: tests/test_crate_interface.rs(L36 - L41) 

Running Tests

The test suite can be executed using standard Rust testing commands:

cargo test                    # Run all tests
cargo test test_crate_interface_call  # Run specific test

Test Validation Points

The tests validate several critical aspects of the macro system:

flowchart TD
A["Test Execution"]
B["Macro Expansion"]
C["Symbol Linking"]
D["Runtime Behavior"]
E["def_interface generates extern declarations"]
F["impl_interface generates export functions"]
G["call_interface generates safe wrappers"]
H["Exported symbols are found"]
I["Function signatures match"]
J["Parameters pass correctly"]
K["Return values work"]
L["Assertions pass"]
M["Compilation success"]
N["Linking success"]
O["Runtime success"]

A --> B
A --> C
A --> D
B --> E
B --> F
B --> G
C --> H
C --> I
D --> J
D --> K
D --> L
E --> M
F --> M
G --> M
H --> N
I --> N
J --> O
K --> O
L --> O

Test Validation Flow

The test verifies that:

  1. The byte array parameter &[2, 3, 5, 7, 11] correctly passes through the interface
  2. The assertion assert_eq!(b[1], 3) validates parameter integrity
  3. The overridden foo() method returns 456 instead of the default 123
  4. Interface calls work from different module contexts

Sources: tests/test_crate_interface.rs(L25)  tests/test_crate_interface.rs(L39) 

Test Coverage Areas

The integration test covers the complete macro workflow in a single compilation unit, which simulates the cross-crate scenario that the macros are designed to support:

Coverage AreaTest ElementsValidation
Trait definition#[def_interface], default methods, abstract methodsMacro expansion correctness
Implementation#[impl_interface], method override, concrete implementationSymbol export generation
Interface callscall_interface!variations, parameter types, return valuesRuntime linking and execution
Module boundariesPrivate module calls, path resolutionCross-module functionality
Attribute handling#[cfg(test)], documentation commentsRust syntax compatibility

Sources: tests/test_crate_interface.rs(L1 - L42) 

CI/CD Pipeline

Relevant source files

This document covers the continuous integration and deployment pipeline for the crate_interface crate, implemented using GitHub Actions. The pipeline ensures code quality, cross-platform compatibility, and automated documentation deployment.

For information about the testing strategies and test suite organization, see Testing. For details about the overall project organization, see Project Structure.

Pipeline Overview

The CI/CD pipeline consists of three main jobs that work together to validate, test, and deploy the crate across multiple Rust toolchains and target architectures.

CI Workflow Structure

flowchart TD
subgraph Outputs["Outputs"]
    F["Quality Validation"]
    G["Cross-Platform Testing"]
    H["GitHub Pages Deployment"]
end
subgraph Jobs["Jobs"]
    C["metadata"]
    D["ci"]
    E["doc"]
end
subgraph Triggers["Triggers"]
    A["push"]
    B["pull_request"]
end

A --> C
B --> C
C --> D
C --> E
D --> F
D --> G
E --> H

Sources: .github/workflows/ci.yml(L1 - L5)  .github/workflows/ci.yml(L6 - L27)  .github/workflows/ci.yml(L28 - L58)  .github/workflows/ci.yml(L59 - L82) 

Metadata Extraction Job

The metadata job extracts essential crate information that other jobs depend on, particularly the minimum supported Rust version (MSRV).

Metadata Extraction Process

flowchart TD
subgraph Extracted_Data["Extracted_Data"]
    F["name"]
    G["version"]
    H["rust_version"]
end
subgraph metadata_job["metadata_job"]
    A["actions/checkout@v4"]
    B["dtolnay/rust-toolchain@stable"]
    C["cargo metadata --no-deps"]
    D["jq processing"]
    E["GITHUB_OUTPUT"]
end

A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
E --> H

The job uses cargo metadata --no-deps --format-version=1 to extract crate metadata and processes it with jq to output the crate name, version, and minimum Rust version. These outputs are made available to downstream jobs via GitHub Actions output variables.

Sources: .github/workflows/ci.yml(L6 - L26) 

Matrix Testing Job

The ci job implements a comprehensive matrix testing strategy across multiple Rust toolchains and target architectures, with specific focus on embedded and no-std environments.

Testing Matrix Configuration

Rust Toolchainx86_64-unknown-linux-gnux86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat
nightly
MSRV❌*

*Excluded due to rust-std component unavailability for older toolchains on x86_64-unknown-none.

Quality Gates Pipeline

flowchart TD
subgraph Conditions["Conditions"]
    H["Test only on x86_64-unknown-linux-gnu"]
    I["Test only on non-MSRV toolchain"]
end
subgraph Quality_Gates["Quality_Gates"]
    D["cargo fmt --all --check"]
    E["cargo clippy --target TARGET --all-features"]
    F["cargo build --target TARGET --all-features"]
    G["cargo test --target TARGET"]
end
subgraph Setup["Setup"]
    A["actions/checkout@v4"]
    B["dtolnay/rust-toolchain"]
    C["Install components: rust-src, clippy, rustfmt"]
end

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
G --> I

The testing process enforces several quality gates in sequence:

  • Code formatting: Ensures consistent style using cargo fmt --all -- --check
  • Linting: Validates code quality with cargo clippy across all target architectures
  • Compilation: Verifies successful builds for all target platforms
  • Unit testing: Runs tests only on x86_64-unknown-linux-gnu with non-MSRV toolchains

Sources: .github/workflows/ci.yml(L28 - L58)  .github/workflows/ci.yml(L31 - L39)  .github/workflows/ci.yml(L49 - L57) 

Documentation Job

The doc job builds and deploys comprehensive documentation to GitHub Pages using a nightly Rust toolchain.

Documentation Build and Deployment

flowchart TD
subgraph Conditions["Conditions"]
    K["Deploy only on default branch"]
end
subgraph Deployment["Deployment"]
    H["JamesIves/github-pages-deploy-action@v4"]
    I["Deploy to gh-pages branch"]
    J["GitHub Pages hosting"]
end
subgraph Quality_Controls["Quality_Controls"]
    E["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links"]
    F["RUSTDOCFLAGS: -D missing-docs"]
    G["continue-on-error for non-main branches"]
end
subgraph Build_Process["Build_Process"]
    A["actions/checkout@v4"]
    B["dtolnay/rust-toolchain@nightly"]
    C["cargo doc --no-deps --all-features"]
    D["Generate index.html redirect"]
end

A --> B
B --> C
C --> D
D --> H
E --> C
F --> C
G --> C
H --> I
H --> K
I --> J

The documentation pipeline includes:

  • Strict documentation validation: Uses RUSTDOCFLAGS to enforce complete documentation coverage and valid internal links
  • Automatic index generation: Creates a redirect from the root documentation page to the main crate documentation
  • Conditional deployment: Only deploys to GitHub Pages when building the default branch
  • Single-commit deployment: Uses single-commit: true to maintain a clean deployment history

Sources: .github/workflows/ci.yml(L59 - L82)  .github/workflows/ci.yml(L67 - L68)  .github/workflows/ci.yml(L76 - L82) 

Target Architecture Support

The pipeline specifically tests against target architectures relevant to the ArceOS ecosystem and embedded systems development:

  • x86_64-unknown-linux-gnu: Standard Linux development and testing environment
  • x86_64-unknown-none: Bare-metal x86_64 systems without OS
  • riscv64gc-unknown-none-elf: RISC-V 64-bit bare-metal systems with compressed instructions
  • aarch64-unknown-none-softfloat: ARM64 bare-metal systems with software floating-point

These targets ensure the crate functions correctly in both hosted and no-std environments, which is essential for the cross-crate interface functionality in kernel and embedded contexts.

Sources: .github/workflows/ci.yml(L35)  .github/workflows/ci.yml(L37 - L39) 

Pipeline Triggers and Permissions

The workflow triggers on all push and pull_request events, ensuring comprehensive validation for both direct commits and proposed changes. The doc job includes contents: write permissions specifically for GitHub Pages deployment.

The pipeline uses modern GitHub Actions including actions/checkout@v4 and dtolnay/rust-toolchain for reliable and up-to-date build environments.

Sources: .github/workflows/ci.yml(L1 - L3)  .github/workflows/ci.yml(L63 - L64) 

Project Structure

Relevant source files

This document covers the organization and configuration of the crate_interface repository, including its file structure, dependencies, build setup, and development environment. For information about the actual macro implementations and testing, see Testing and Macro Reference.

Repository Organization

The crate_interface project follows a standard Rust crate structure optimized for procedural macro development. The repository is intentionally minimal, focusing on a single library crate that exports three core procedural macros.

flowchart TD
subgraph Documentation["Documentation"]
    ReadmeMd["README.md"]
end
subgraph CI/CD["CI/CD"]
    GithubDir[".github/"]
    WorkflowsDir[".github/workflows/"]
    CiYml["ci.yml"]
end
subgraph Testing["Testing"]
    TestsDir["tests/"]
    TestFiles["Integration test files"]
end
subgraph subGraph1["Source Code"]
    SrcDir["src/"]
    LibRs["src/lib.rs"]
end
subgraph subGraph0["Configuration Files"]
    Root["crate_interface/"]
    CargoToml["Cargo.toml"]
    GitIgnore[".gitignore"]
end

GithubDir --> WorkflowsDir
Root --> CargoToml
Root --> GitIgnore
Root --> GithubDir
Root --> ReadmeMd
Root --> SrcDir
Root --> TestsDir
SrcDir --> LibRs
TestsDir --> TestFiles
WorkflowsDir --> CiYml

Repository Structure Overview

Sources: Cargo.toml(L1 - L22)  .gitignore(L1 - L5) 

Package Configuration

The project is configured as a procedural macro crate through its Cargo.toml manifest. The package metadata defines the crate's identity within the ArceOS ecosystem and its distribution characteristics.


Package Configuration Details

FieldValuePurpose
name"crate_interface"Crate identifier for Cargo and crates.io
version"0.1.4"Semantic versioning for API compatibility
edition"2021"Rust edition for language features
rust-version"1.57"Minimum supported Rust version
proc-macrotrueEnables procedural macro compilation

Sources: Cargo.toml(L1 - L14)  Cargo.toml(L20 - L22) 

Dependencies Architecture

The crate relies on three essential procedural macro development dependencies, each serving a specific role in the macro compilation pipeline.

flowchart TD
subgraph subGraph2["Processing Pipeline"]
    TokenStream["TokenStream manipulation"]
    AstParsing["AST parsing and analysis"]
    CodeGeneration["Code generation"]
    ExternFunctions["extern function synthesis"]
end
subgraph subGraph1["Core Macro Functions"]
    DefInterface["def_interface macro"]
    ImplInterface["impl_interface macro"]
    CallInterface["call_interface! macro"]
end
subgraph subGraph0["External Dependencies"]
    ProcMacro2["proc-macro2 v1.0"]
    Quote["quote v1.0"]
    Syn["syn v2.0 with full features"]
end

AstParsing --> CallInterface
AstParsing --> DefInterface
AstParsing --> ImplInterface
CallInterface --> ExternFunctions
CodeGeneration --> CallInterface
CodeGeneration --> DefInterface
CodeGeneration --> ImplInterface
DefInterface --> ExternFunctions
ImplInterface --> ExternFunctions
ProcMacro2 --> TokenStream
Quote --> CodeGeneration
Syn --> AstParsing
TokenStream --> CallInterface
TokenStream --> DefInterface
TokenStream --> ImplInterface

Dependency Functions

DependencyVersionPurpose
proc-macro21.0TokenStream manipulation and span preservation
quote1.0Rust code generation with interpolation
syn2.0Rust syntax tree parsing with full feature set

The syn dependency includes the "full" feature set to enable parsing of complete Rust syntax, including trait definitions, implementations, and method signatures required by the macro system.

Sources: Cargo.toml(L15 - L18) 

Build System Configuration

The crate is configured as a procedural macro library, which affects compilation behavior and usage patterns.

flowchart TD
subgraph subGraph2["Usage Context"]
    MacroExpansion["Compile-time macro expansion"]
    CodeGeneration["Generated extern functions"]
    CrossCrateLink["Cross-crate symbol linking"]
end
subgraph subGraph1["Compilation Output"]
    DylibFormat[".so/.dll/.dylib"]
    CompilerPlugin["Compiler plugin format"]
end
subgraph subGraph0["Build Target"]
    ProcMacroLib["proc-macro = true"]
    LibCrate["Library Crate Type"]
end

CodeGeneration --> CrossCrateLink
CompilerPlugin --> MacroExpansion
DylibFormat --> CompilerPlugin
LibCrate --> DylibFormat
MacroExpansion --> CodeGeneration
ProcMacroLib --> LibCrate

Build Configuration Implications

The proc-macro = true setting in [lib] configures the crate to:

  • Compile as a dynamic library for use by the Rust compiler
  • Load at compile-time during macro expansion phases
  • Generate code that becomes part of dependent crates
  • Enable cross-crate trait interface functionality

Sources: Cargo.toml(L20 - L22) 

Development Environment

The project supports multiple development workflows and target environments, as configured through the CI pipeline and package metadata.

flowchart TD
subgraph subGraph3["Environment Support"]
    StdEnv["std environments"]
    NoStdEnv["no-std environments"]
    EmbeddedTargets["Embedded targets"]
end
subgraph subGraph2["Development Commands"]
    CargoFmt["cargo fmt --check"]
    CargoClippy["cargo clippy"]
    CargoBuild["cargo build"]
    CargoTest["cargo test"]
    CargoDoc["cargo doc"]
end
subgraph subGraph1["Target Architectures"]
    X86Linux["x86_64-unknown-linux-gnu"]
    X86None["x86_64-unknown-none"]
    RiscV["riscv64gc-unknown-none-elf"]
    Aarch64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Supported Toolchains"]
    Stable["stable"]
    Beta["beta"]
    Nightly["nightly"]
    Msrv["1.57.0 (MSRV)"]
end

Aarch64 --> EmbeddedTargets
Beta --> X86None
CargoBuild --> EmbeddedTargets
CargoClippy --> NoStdEnv
CargoFmt --> StdEnv
Msrv --> Aarch64
Nightly --> RiscV
RiscV --> EmbeddedTargets
Stable --> X86Linux
X86Linux --> StdEnv
X86None --> NoStdEnv

Environment Requirements

The development environment is designed to support:

  • Standard library environments for general development
  • no-std environments for embedded and kernel development
  • Multiple architectures including x86_64, RISC-V, and ARM64
  • Cross-compilation for bare-metal targets

Sources: Cargo.toml(L12 - L13)  .gitignore(L1 - L5) 

Version Control Configuration

The .gitignore configuration excludes standard Rust development artifacts and common editor files.

Excluded Files and Directories

PatternPurpose
/targetCargo build artifacts and dependencies
/.vscodeVisual Studio Code workspace configuration
.DS_StoremacOS file system metadata
Cargo.lockDependency version lockfile (appropriate for libraries)

The exclusion of Cargo.lock follows Rust library conventions, allowing dependent crates to resolve their own dependency versions while maintaining compatibility with the specified version ranges.

Sources: .gitignore(L1 - L5) 

Overview

Relevant source files

Purpose and Scope

This document provides an overview of the lazyinit crate, a Rust library that enables thread-safe lazy initialization of static values. The material covers the crate's core purpose, key features, architecture, and usage patterns. For detailed implementation specifics of the LazyInit<T> struct, see LazyInit Implementation. For API documentation and method details, see API Reference. For project configuration and dependencies, see Project Configuration.

What is LazyInit

The lazyinit crate provides the LazyInit<T> type for initializing static values lazily in a thread-safe manner. Unlike compile-time initialization or macro-based solutions like lazy_static, LazyInit<T> allows runtime initialization with arbitrary logic while guaranteeing that initialization occurs exactly once across all threads.

The core abstraction is the LazyInit<T> struct, which wraps a value of type T and manages its initialization state through atomic operations. The value remains uninitialized until the first call to init_once or call_once, at which point it becomes permanently initialized and accessible.

Sources: README.md(L7 - L11)  Cargo.toml(L6) 

Key Features and Capabilities

FeatureDescriptionMethods
Thread-Safe InitializationGuarantees exactly one initialization across multiple threadsinit_once,call_once
Flexible InitializationSupports both direct value initialization and closure-based initializationinit_once(value), `call_once(
Safe Access PatternsProvides both safe and unsafe access methodsget(),get_mut(),get_unchecked()
State InspectionAllows checking initialization status without accessing the valueis_inited()
Direct AccessImplementsDerefandDerefMutfor transparent access after initialization*VALUE,&mut *VALUE
No-std CompatibilityWorks in embedded and kernel environments without standard libraryNo external dependencies

Sources: README.md(L15 - L57)  Cargo.toml(L12) 

Core Architecture

The following diagram illustrates the relationship between the main components and their roles in the codebase:

LazyInit Core Components and Relationships

flowchart TD
subgraph subGraph2["Memory Safety Guarantees"]
    single_init["Single Initialization Guarantee"]
    thread_safety["Thread Safety"]
    memory_ordering["Memory Ordering (Acquire/Relaxed)"]
end
subgraph subGraph1["Internal State Management"]
    AtomicBool["AtomicBool (initialization flag)"]
    UnsafeCell["UnsafeCell<MaybeUninit<T>> (data storage)"]
    compare_exchange["compare_exchange_weak operations"]
end
subgraph subGraph0["Public API Layer"]
    LazyInit["LazyInit<T>"]
    new["LazyInit::new()"]
    init_once["init_once(value: T)"]
    call_once["call_once(f: FnOnce() -> T)"]
    get["get() -> Option<&T>"]
    get_mut["get_mut() -> Option<&mut T>"]
    is_inited["is_inited() -> bool"]
    get_unchecked["get_unchecked() -> &T"]
end

AtomicBool --> compare_exchange
LazyInit --> call_once
LazyInit --> get
LazyInit --> get_mut
LazyInit --> get_unchecked
LazyInit --> init_once
LazyInit --> is_inited
LazyInit --> new
call_once --> AtomicBool
call_once --> UnsafeCell
compare_exchange --> memory_ordering
compare_exchange --> single_init
compare_exchange --> thread_safety
get --> AtomicBool
get --> UnsafeCell
get_mut --> UnsafeCell
get_unchecked --> UnsafeCell
init_once --> AtomicBool
init_once --> UnsafeCell
is_inited --> AtomicBool

Sources: README.md(L16 - L28)  README.md(L32 - L57) 

API Surface Overview

The following diagram shows the complete API surface and usage patterns supported by LazyInit<T>:

LazyInit API Methods and Usage Patterns

flowchart TD
subgraph subGraph4["Return Types"]
    option_some["Some(&T) / Some(&mut T)"]
    option_none["None"]
    panic_behavior["Panic on double init"]
    direct_ref["&T / &mut T"]
end
subgraph subGraph3["Status Methods"]
    is_inited_method["is_inited() -> bool"]
end
subgraph subGraph2["Access Methods"]
    get_method["get() -> Option<&T>"]
    get_mut_method["get_mut() -> Option<&mut T>"]
    deref_impl["Deref/DerefMut traits"]
    get_unchecked_method["get_unchecked() -> &T (unsafe)"]
end
subgraph subGraph1["Initialization Methods"]
    init_once_method["init_once(value: T)"]
    call_once_method["call_once(f: FnOnce() -> T)"]
end
subgraph Creation["Creation"]
    LazyInit_new["LazyInit::new()"]
end
true_false["true / false"]

LazyInit_new --> call_once_method
LazyInit_new --> init_once_method
call_once_method --> option_none
call_once_method --> option_some
deref_impl --> direct_ref
deref_impl --> panic_behavior
get_method --> option_none
get_method --> option_some
get_mut_method --> option_none
get_mut_method --> option_some
get_unchecked_method --> direct_ref
init_once_method --> option_some
init_once_method --> panic_behavior
is_inited_method --> true_false

Sources: README.md(L18 - L27)  README.md(L39 - L56) 

Use Cases and When to Use LazyInit

LazyInit<T> is designed for scenarios where you need lazy initialization with these characteristics:

Primary Use Cases

  1. Static Variables with Runtime Initialization: When you need a static variable that requires runtime computation or I/O to initialize
  2. Expensive Computations: When initialization is costly and should only occur if the value is actually needed
  3. Resource Initialization: When initializing system resources, file handles, or network connections that should be shared globally
  4. Multi-threaded Environments: When multiple threads might attempt to initialize the same static value concurrently

Advantages Over Alternatives

  • vs. lazy_static!: Provides more flexible initialization patterns and doesn't require macro-based declaration
  • vs. std::sync::Once: Combines synchronization with storage, providing a more ergonomic API
  • vs. std::cell::OnceCell: Offers thread-safe initialization for static contexts

Sources: README.md(L9 - L11)  Cargo.toml(L11) 

Project Characteristics

No-std Compatibility

The crate is designed for no-std environments, making it suitable for:

  • Embedded systems
  • Kernel-level code
  • WebAssembly targets
  • Bare metal programming

Licensing and Distribution

AspectDetails
LicensesTriple-licensed: GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0
Categoriesno-std,rust-patterns
Keywordslazy,initialization,static
DependenciesNone (pure Rust implementation)

Supported Targets

The crate supports multiple target architectures through CI validation, including both hosted and bare-metal targets.

Sources: Cargo.toml(L1 - L15) 

LazyInit Implementation

Relevant source files

This document provides a comprehensive analysis of the LazyInit<T> struct implementation, covering its internal architecture, state management, and the mechanisms that enable thread-safe lazy initialization. This page focuses on the core implementation details and design decisions.

For specific API documentation and method signatures, see API Reference. For detailed thread safety mechanisms and memory ordering, see Thread Safety & Memory Model. For practical usage examples and patterns, see Usage Patterns & Examples.

Core Architecture

The LazyInit<T> struct implements thread-safe lazy initialization through a two-field design that separates state tracking from data storage. The implementation leverages atomic operations and unsafe memory management to achieve both safety and performance.

Struct Layout

flowchart TD
subgraph subGraph2["Data Storage"]
    E["MaybeUninit<T>::uninit()"]
    F["MaybeUninit<T>::init(value)"]
end
subgraph subGraph1["State Tracking"]
    C["false = Uninitialized"]
    D["true = Initialized"]
end
subgraph LazyInit&lt;T&gt;["LazyInit<T>"]
    A["inited: AtomicBool"]
    B["data: UnsafeCell<MaybeUninit<T>>"]
end

A --> C
A --> D
B --> E
B --> F
C --> D
E --> F

The inited field serves as the synchronization point for all threads, while data provides the actual storage location. This separation allows atomic state transitions independent of the data type T.

Sources: src/lib.rs(L14 - L17) 

Memory Safety Guarantees

The implementation achieves memory safety through careful coordination of atomic operations and unsafe memory access:

flowchart TD
subgraph subGraph2["Atomic Coordination"]
    I["compare_exchange_weak"]
    J["load(Acquire)"]
end
subgraph subGraph1["Unsafe Internal Layer"]
    E["force_get()"]
    F["force_get_mut()"]
    G["assume_init_ref()"]
    H["write()"]
end
subgraph subGraph0["Safe API Layer"]
    A["init_once()"]
    B["call_once()"]
    C["get()"]
    D["is_inited()"]
end

A --> I
B --> I
C --> J
D --> J
E --> G
F --> G
I --> H
J --> E

All unsafe operations are gated behind atomic checks that ensure the data has been properly initialized before access.

Sources: src/lib.rs(L36 - L47)  src/lib.rs(L53 - L67)  src/lib.rs(L77 - L83)  src/lib.rs(L118 - L126) 

State Machine

The LazyInit<T> implementation follows a simple but robust state machine with three logical states:

stateDiagram-v2
[*] --> Uninitialized : "new()"
Uninitialized --> InitInProgress : "init_once() / call_once()"
InitInProgress --> Initialized : "compare_exchange success"
InitInProgress --> Uninitialized : "compare_exchange failure"
Initialized --> [*] : "drop()"
note left of Uninitialized : ['inited = false<br>data = MaybeUninit::uninit()']
note left of InitInProgress : ['Transient state during<br>compare_exchange_weak operation']
note left of Initialized : ['inited = true<br>data = valid T instance']

The state machine ensures that once initialization succeeds, the instance remains in the Initialized state for its entire lifetime. The transient InitInProgress state handles race conditions between multiple initializing threads.

Sources: src/lib.rs(L24 - L29)  src/lib.rs(L37 - L39)  src/lib.rs(L57 - L59)  src/lib.rs(L177 - L181) 

Method Categories

The LazyInit<T> API is organized into distinct categories based on functionality and safety guarantees:

CategoryMethodsSafetyPurpose
Constructionnew(),default()SafeCreate uninitialized instances
Initializationinit_once(),call_once()SafePerform one-time initialization
State Inspectionis_inited()SafeCheck initialization status
Safe Accessget(),get_mut()SafeAccess with runtime checks
Direct Accessderef(),deref_mut()Safe but panicsTransparent access toT
Unsafe Accessget_unchecked(),get_mut_unchecked()UnsafePerformance-critical access
Internalforce_get(),force_get_mut()Private unsafeImplementation details

Initialization Methods

flowchart TD
subgraph Actions["Actions"]
    F["write(data) / write(f())"]
    G["panic! / return None"]
end
subgraph Outcomes["Outcomes"]
    D["Ok(_): First to initialize"]
    E["Err(_): Lost race"]
end
subgraph subGraph1["Atomic Competition"]
    C["compare_exchange_weak(false, true)"]
end
subgraph subGraph0["Initialization Entry Points"]
    A["init_once(data: T)"]
    B["call_once<F>(f: F)"]
end

A --> C
B --> C
C --> D
C --> E
D --> F
E --> G

Both initialization methods use the same atomic compare-exchange operation but handle initialization failure differently - init_once() panics while call_once() returns None.

Sources: src/lib.rs(L36 - L47)  src/lib.rs(L53 - L67) 

Thread Safety Implementation

The thread safety of LazyInit<T> relies on the atomic inited flag and proper memory ordering:

flowchart TD
subgraph Implementation["Implementation"]
    H["AtomicBool::compare_exchange_weak"]
    I["Ordering::Acquire / Relaxed"]
    J["UnsafeCell<MaybeUninit<T>>"]
end
subgraph Guarantees["Guarantees"]
    E["Single initialization"]
    F["Memory ordering"]
    G["Data race prevention"]
end
subgraph Requirements["Requirements"]
    C["T: Send"]
    D["T: Send + Sync"]
end
subgraph subGraph0["Thread Safety Traits"]
    A["Send for LazyInit<T>"]
    B["Sync for LazyInit<T>"]
end

A --> C
B --> D
D --> E
D --> F
D --> G
E --> H
F --> I
G --> J

The unsafe impl blocks for Send and Sync establish the thread safety contract, requiring appropriate bounds on the contained type T.

Sources: src/lib.rs(L19 - L20)  src/lib.rs(L37 - L39)  src/lib.rs(L70 - L72) 

Trait Implementations

The LazyInit<T> struct implements several standard traits to provide ergonomic usage:

Deref and DerefMut Implementation

flowchart TD
subgraph Outcomes["Outcomes"]
    D["force_get() / force_get_mut()"]
    E["panic_message()"]
end
subgraph subGraph2["Safety Check"]
    C["is_inited()"]
end
subgraph subGraph1["DerefMut Trait"]
    B["deref_mut(&mut self) -> &mut T"]
end
subgraph subGraph0["Deref Trait"]
    A["deref(&self) -> &T"]
end

A --> C
B --> C
C --> D
C --> E

The Deref implementations enable transparent access to the wrapped value using the * operator, with runtime panics for uninitialized access.

Sources: src/lib.rs(L153 - L174)  src/lib.rs(L128 - L133) 

Debug and Drop Implementation

The Debug implementation provides meaningful output for both initialized and uninitialized states, while Drop ensures proper cleanup of initialized values:

flowchart TD
subgraph Debug::fmt["Debug::fmt"]
    C["None -> <uninitialized>"]
    subgraph Drop::drop["Drop::drop"]
        D["if self.is_inited()"]
        E["drop_in_place(data.as_mut_ptr())"]
        A["match self.get()"]
        B["Some(s) -> format with data"]
    end
end

A --> B
A --> C
D --> E

Sources: src/lib.rs(L136 - L144)  src/lib.rs(L176 - L182) 

Performance Considerations

The implementation includes several performance optimizations:

  1. compare_exchange_weak - Uses the weak variant for better performance on architectures where it matters
  2. Inline annotations - Critical path methods are marked #[inline] for optimization
  3. Minimal overhead - Only two words of storage overhead regardless of T
  4. Fast path optimization - Post-initialization access is a simple atomic load followed by unsafe dereference

The get_unchecked() methods provide a performance escape hatch for scenarios where the initialization state is guaranteed by external logic.

Sources: src/lib.rs(L37 - L39)  src/lib.rs(L101 - L105)  src/lib.rs(L112 - L116)  src/lib.rs(L118 - L126) 

API Reference

Relevant source files

This document provides comprehensive documentation of all public methods, traits, and behavior of the LazyInit<T> struct. It covers method signatures, safety guarantees, panic conditions, and usage semantics for thread-safe lazy initialization.

For implementation details about thread safety mechanisms and memory ordering, see Thread Safety & Memory Model. For practical usage examples and patterns, see Usage Patterns & Examples.

Method Categories and State Flow

The LazyInit<T> API is organized into distinct categories based on functionality and safety guarantees:

flowchart TD
subgraph subGraph4["Automatic Traits"]
    deref["Deref/DerefMut"]
    debug["Debug"]
    default["Default"]
    drop["Drop"]
end
subgraph subGraph2["Safe Access"]
    get["get() -> Option<&T>"]
    get_mut["get_mut() -> Option<&mut T>"]
    is_inited["is_inited() -> bool"]
end
subgraph subGraph1["Initialization Methods"]
    init_once["init_once(data: T)"]
    call_once["call_once(f: F)"]
end
subgraph Constructor["Constructor"]
    new["LazyInit::new()"]
end
subgraph subGraph3["Unsafe Access"]
    get_unchecked["get_unchecked() -> &T"]
    get_mut_unchecked["get_mut_unchecked() -> &mut T"]
end

call_once --> get
call_once --> get_mut
get --> deref
get_mut --> deref
init_once --> get
init_once --> get_mut
new --> call_once
new --> init_once

Sources: src/lib.rs(L22 - L182) 

Constructor Methods

LazyInit::new()

pub const fn new() -> Self

Creates a new uninitialized LazyInit<T> instance. This is a const function, making it suitable for static variable initialization.

PropertyValue
Thread SafetyYes
ConstYes
PanicsNever
ReturnNew uninitialized instance

The internal state is initialized with inited: false and uninitialized memory for the data field.

Sources: src/lib.rs(L23 - L29) 

Initialization Methods

init_once()

#![allow(unused)]
fn main() {
pub fn init_once(&self, data: T) -> &T
}

Initializes the value exactly once with the provided data. Uses atomic compare-and-swap to ensure only one thread can succeed.

PropertyValue
Thread SafetyYes
Atomicitycompare_exchange_weak
PanicsIf already initialized
ReturnReference to initialized value

Panic Conditions:

  • Called when value is already initialized

Memory Ordering:

  • Success: Ordering::Acquire
  • Failure: Ordering::Relaxed

Sources: src/lib.rs(L31 - L47) 

call_once()

#![allow(unused)]
fn main() {
pub fn call_once<F>(&self, f: F) -> Option<&T>
where F: FnOnce() -> T
}

Performs initialization using a closure exactly once. Returns None if already initialized, avoiding panics.

PropertyValue
Thread SafetyYes
Atomicitycompare_exchange_weak
PanicsNever (returnsNoneinstead)
ReturnSome(&T)on success,Noneif already initialized

Behavior:

  • Only one thread succeeds in initialization
  • Failed threads receive None rather than panicking
  • Closure is called exactly once across all threads

Sources: src/lib.rs(L49 - L67) 

Safe Access Methods

get()

#![allow(unused)]
fn main() {
pub fn get(&self) -> Option<&T>
}

Returns a reference to the initialized value, or None if uninitialized.

PropertyValue
Thread SafetyYes
PanicsNever
ReturnSome(&T)if initialized,Noneotherwise

Sources: src/lib.rs(L74 - L83) 

get_mut()

#![allow(unused)]
fn main() {
pub fn get_mut(&mut self) -> Option<&mut T>
}

Returns a mutable reference to the initialized value, or None if uninitialized. Requires exclusive access to the LazyInit instance.

PropertyValue
Thread SafetyYes (requires&mut self)
PanicsNever
ReturnSome(&mut T)if initialized,Noneotherwise

Sources: src/lib.rs(L85 - L94) 

is_inited()

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

Checks initialization state without accessing the value.

PropertyValue
Thread SafetyYes
Memory OrderingOrdering::Acquire
PanicsNever
Returntrueif initialized,falseotherwise

Sources: src/lib.rs(L69 - L72) 

Unsafe Access Methods

get_unchecked()

#![allow(unused)]
fn main() {
pub unsafe fn get_unchecked(&self) -> &T
}

Returns a reference to the value without checking initialization state. Provides maximum performance for hot paths.

PropertyValue
SafetyUNSAFE- Must be called after initialization
Thread SafetyYes (if preconditions met)
Debug AssertionChecksis_inited()in debug builds
PanicsUndefined behavior if uninitialized

Safety Requirements:

  • Must only be called after successful initialization
  • Caller must ensure initialization has occurred

Sources: src/lib.rs(L96 - L105) 

get_mut_unchecked()

#![allow(unused)]
fn main() {
pub unsafe fn get_mut_unchecked(&mut self) -> &mut T
}

Returns a mutable reference without checking initialization state.

PropertyValue
SafetyUNSAFE- Must be called after initialization
Thread SafetyYes (requires&mut self)
Debug AssertionChecksis_inited()in debug builds
PanicsUndefined behavior if uninitialized

Safety Requirements:

  • Must only be called after successful initialization
  • Requires exclusive access to the LazyInit instance

Sources: src/lib.rs(L107 - L116) 

State Transition Diagram

stateDiagram-v2
[*] --> Uninitialized : "new()"
Uninitialized --> Initialized : "init_once(data)"
Uninitialized --> Initialized : "call_once(f) -> Some"
Uninitialized --> Initialized
note left of Initialized : ['Deref panics if called<br>on uninitialized value']
note left of Initialized----note : ['Uninitialized']
Initialized --> Initialized
Initialized --> Initialized : "Deref/DerefMut"
Initialized --> Initialized
Initialized --> [*] : "Drop"
Uninitialized --> [*] : "Drop"
note left of Uninitialized : ['init_once() panics if called<br>on already initialized value']
note left of Initialized : ['Deref panics if called<br>on uninitialized value']

Sources: src/lib.rs(L22 - L182) 

Trait Implementations

DerefandDerefMut

#![allow(unused)]
fn main() {
impl<T> Deref for LazyInit<T> {
    type Target = T;
    fn deref(&self) -> &T
}

impl<T> DerefMut for LazyInit<T> {
    fn deref_mut(&mut self) -> &mut T
}
}

Enables direct access to the contained value using the dereference operator (*).

PropertyValue
PanicsIf value is uninitialized
Thread SafetyYes
Usage*lazy_initorlazy_init.method()

Panic Message: "Use uninitialized value: LazyInit<T>"

Sources: src/lib.rs(L153 - L174) 

Debug

impl<T: fmt::Debug> fmt::Debug for LazyInit<T>

Provides debug formatting that safely handles both initialized and uninitialized states.

Output Format:

  • Initialized: "LazyInit { data: <debug_value> }"
  • Uninitialized: "LazyInit { <uninitialized> }"

Sources: src/lib.rs(L136 - L145) 

Default

#![allow(unused)]
fn main() {
impl<T> Default for LazyInit<T> {
    fn default() -> Self { Self::new() }
}
}

Creates a new uninitialized instance, equivalent to LazyInit::new().

Sources: src/lib.rs(L147 - L151) 

Drop

impl<T> Drop for LazyInit<T>

Properly destroys the contained value if initialized, ensuring no memory leaks.

Behavior:

  • Checks initialization state before dropping
  • Only drops the contained value if initialized
  • Safe for uninitialized instances

Sources: src/lib.rs(L176 - L182) 

Thread Safety Traits

unsafe impl<T: Send + Sync> Sync for LazyInit<T> {}
unsafe impl<T: Send> Send for LazyInit<T> {}

Manual trait implementations that enable thread safety when the contained type supports it.

TraitRequirementPurpose
SendT: SendCan be transferred between threads
SyncT: Send + SyncCan be shared between threads

Sources: src/lib.rs(L19 - L20) 

Method Safety Summary

MethodSafety LevelPanicsThread Safe
new()SafeNeverYes
init_once()SafeIf already initializedYes
call_once()SafeNeverYes
get()SafeNeverYes
get_mut()SafeNeverYes
is_inited()SafeNeverYes
get_unchecked()UnsafeUB if uninitializedYes*
get_mut_unchecked()UnsafeUB if uninitializedYes*
Deref/DerefMutSafeIf uninitializedYes

*Thread safe only if safety preconditions are met.

Sources: src/lib.rs(L22 - L182) 

Thread Safety & Memory Model

Relevant source files

This document explains the synchronization mechanisms, atomic operations, and memory ordering guarantees that make LazyInit<T> thread-safe. It covers the low-level implementation details of how concurrent access is coordinated and memory safety is maintained across multiple threads.

For information about the high-level API and usage patterns, see API Reference and Usage Patterns & Examples.

Synchronization Primitives

The LazyInit<T> implementation relies on two core synchronization primitives to achieve thread safety:

ComponentTypePurpose
initedAtomicBoolTracks initialization state atomically
dataUnsafeCell<MaybeUninit>Provides interior mutability for the stored value

Atomic State Management

The initialization state is tracked using an AtomicBool that serves as the primary coordination mechanism between threads. This atomic variable ensures that only one thread can successfully transition from the uninitialized to initialized state.

flowchart TD
A["AtomicBool_inited"]
B["compare_exchange_weak(false, true)"]
C["CAS_Success"]
D["Write_data_initialize"]
E["Already_initialized"]
F["Return_reference"]
G["Handle_according_to_method"]
H["Thread_1"]
I["Thread_2"]
J["Thread_N"]

A --> B
B --> C
C --> D
C --> E
D --> F
E --> G
H --> B
I --> B
J --> B

Atomic State Coordination Diagram Sources: src/lib.rs(L15 - L16)  src/lib.rs(L37 - L46)  src/lib.rs(L57 - L66) 

Interior Mutability Pattern

The UnsafeCell<MaybeUninit<T>> provides the necessary interior mutability to allow initialization through shared references while maintaining memory safety through careful synchronization.

flowchart TD
A["UnsafeCell<MaybeUninit<T>>"]
B["get()"]
C["*mut_MaybeUninit<T>"]
D["as_mut_ptr()"]
E["write(data)"]
F["assume_init_ref()"]
G["&T"]
H["Memory_Safety"]
I["Protected_by_AtomicBool"]

A --> B
B --> C
C --> D
C --> F
D --> E
F --> G
H --> I
I --> A

Interior Mutability and Memory Safety Sources: src/lib.rs(L16)  src/lib.rs(L42)  src/lib.rs(L119 - L120) 

Memory Ordering Semantics

The implementation uses specific memory ordering guarantees to ensure correct synchronization across threads while maintaining performance.

Ordering Strategy

OperationSuccess OrderingFailure OrderingPurpose
compare_exchange_weakOrdering::AcquireOrdering::RelaxedSynchronize initialization
loadoperationsOrdering::AcquireN/AEnsure visibility of writes

Acquire-Release Semantics

sequenceDiagram
    participant Thread_1 as Thread_1
    participant inited_AtomicBool as inited_AtomicBool
    participant data_UnsafeCell as data_UnsafeCell
    participant Thread_2 as Thread_2

    Thread_1 ->> inited_AtomicBool: compare_exchange_weak(false, true, Acquire, Relaxed)
    Note over inited_AtomicBool: CAS succeeds
    inited_AtomicBool -->> Thread_1: Ok(false)
    Thread_1 ->> data_UnsafeCell: write(data)
    Note over Thread_1,data_UnsafeCell: Write becomes visible
    Thread_2 ->> inited_AtomicBool: load(Acquire)
    inited_AtomicBool -->> Thread_2: true
    Note over Thread_2: Acquire guarantees visibility of T1's write
    Thread_2 ->> data_UnsafeCell: assume_init_ref()
    data_UnsafeCell -->> Thread_2: &T (safe)

Memory Ordering Synchronization Sources: src/lib.rs(L38 - L39)  src/lib.rs(L58 - L59)  src/lib.rs(L71) 

Thread Safety Implementation

Send and Sync Bounds

The trait implementations provide precise thread safety guarantees:

flowchart TD
A["LazyInit<T>"]
B["Send_for_LazyInit<T>"]
C["Sync_for_LazyInit<T>"]
D["T:_Send"]
E["T:Send+_Sync"]
F["Rationale"]
G["Can_transfer_ownership_(Send)"]
H["Can_share_references_(Sync)"]

A --> B
A --> C
B --> D
C --> E
D --> G
E --> H
F --> G
F --> H

Thread Safety Trait Bounds Sources: src/lib.rs(L19 - L20) 

The bounds ensure that:

  • Send is implemented when T: Send, allowing transfer of ownership across threads
  • Sync is implemented when T: Send + Sync, allowing shared access from multiple threads

Race Condition Prevention

The implementation prevents common race conditions through atomic compare-and-swap operations:

flowchart TD
A["Multiple_threads_call_init_once"]
B["compare_exchange_weak"]
C["First_thread"]
D["Performs_initialization"]
E["Receives_Err"]
F["Writes_data_safely"]
G["Panics_Already_initialized"]
H["call_once_behavior"]
I["Returns_None_for_losers"]
J["No_double_initialization"]
K["Guaranteed_by_CAS"]

A --> B
B --> C
C --> D
C --> E
D --> F
D --> K
E --> G
E --> I
G --> K
H --> I
J --> K

Race Condition Prevention Mechanisms Sources: src/lib.rs(L36 - L46)  src/lib.rs(L53 - L66) 

Memory Safety Guarantees

Initialization State Tracking

The atomic boolean serves as a guard that prevents access to uninitialized memory:

MethodCheck MechanismSafety Level
get()is_inited()checkSafe - returnsOption<&T>
deref()is_inited()checkPanic on uninitialized
get_unchecked()Debug assertion onlyUnsafe - caller responsibility

Memory Layout and Access Patterns

flowchart TD
subgraph Unsafe_Access_Path["Unsafe_Access_Path"]
    H["get_unchecked()"]
    I["debug_assert"]
    J["force_get()"]
end
subgraph Safe_Access_Path["Safe_Access_Path"]
    C["is_inited()"]
    D["load(Acquire)"]
    E["true"]
    F["force_get()"]
    G["Return_None_or_Panic"]
end
subgraph LazyInit_Memory_Layout["LazyInit_Memory_Layout"]
    A["inited:_AtomicBool"]
    B["data:_UnsafeCell<MaybeUninit<T>>"]
end
K["assume_init_ref()"]
L["&T"]

A --> C
B --> F
B --> J
C --> D
D --> E
E --> F
E --> G
F --> K
H --> I
I --> J
J --> K
K --> L

Memory Access Safety Mechanisms Sources: src/lib.rs(L77 - L83)  src/lib.rs(L102 - L105)  src/lib.rs(L119 - L120) 

Performance Characteristics

Fast Path Optimization

Once initialized, access to the value follows a fast path with minimal overhead:

  1. Single atomic load - is_inited() performs one Ordering::Acquire load
  2. Direct memory access - force_get() uses assume_init_ref() without additional checks
  3. No contention - Post-initialization reads don't modify atomic state

Compare-Exchange Optimization

The use of compare_exchange_weak instead of compare_exchange provides better performance on architectures where weak CAS can fail spuriously but retry efficiently.

flowchart TD
A["compare_exchange_weak"]
B["May_fail_spuriously"]
C["Better_performance_on_some_architectures"]
D["Initialization_path"]
E["One-time_cost"]
F["Amortized_over_lifetime"]
G["Read_path"]
H["Single_atomic_load"]
I["Minimal_overhead"]

A --> B
B --> C
D --> E
E --> F
G --> H
H --> I

Performance Optimization Strategy Sources: src/lib.rs(L38 - L39)  src/lib.rs(L58 - L59)  src/lib.rs(L71) 

The memory model ensures that the expensive synchronization cost is paid only once during initialization, while subsequent accesses benefit from the acquire-release semantics without additional synchronization overhead.

Sources: src/lib.rs(L1 - L183) 

Usage Patterns & Examples

Relevant source files

This document covers common usage patterns, practical examples, and best practices for using the LazyInit<T> type. It demonstrates how to effectively leverage thread-safe lazy initialization in various scenarios, from simple static variables to complex multi-threaded applications.

For detailed API documentation, see API Reference. For information about the underlying thread safety mechanisms, see Thread Safety & Memory Model.

Core Usage Patterns

The LazyInit<T> type supports several distinct usage patterns, each optimized for different scenarios and requirements.

Static Variable Initialization Pattern

The most common pattern involves declaring static variables that are initialized lazily at runtime:

flowchart TD
A["Static Declaration"]
B["LazyInit::new()"]
C["Runtime Check"]
D["is_inited()"]
E["Initialize Once"]
F["Direct Access"]
G["init_once() or call_once()"]
H["*VALUE or VALUE.get()"]

A --> B
B --> C
C --> D
D --> E
D --> F
E --> G
F --> H
G --> F

Static Variable Initialization Flow

This pattern leverages the const fn new() method to create compile-time initialized but runtime-lazy static variables.

Sources: src/lib.rs(L24 - L29)  README.md(L15 - L28) 

Thread-Safe Initialization Pattern

For multi-threaded scenarios, LazyInit<T> provides race-condition-free initialization:

sequenceDiagram
    participant Thread1 as "Thread 1"
    participant Thread2 as "Thread 2"
    participant Thread3 as "Thread 3"
    participant LazyInitT as "LazyInit<T>"

    par Multiple Threads
        Thread1 ->> LazyInitT: call_once(|| value1)
        Thread2 ->> LazyInitT: call_once(|| value2)
        Thread3 ->> LazyInitT: call_once(|| value3)
    end
    Note over LazyInitT: compare_exchange_weak ensures<br>only one succeeds
    LazyInitT -->> Thread1: Some(&value1)
    LazyInitT -->> Thread2: None
    LazyInitT -->> Thread3: None
    par All Threads Access
        Thread1 ->> LazyInitT: get() or *deref
        Thread2 ->> LazyInitT: get() or *deref
        Thread3 ->> LazyInitT: get() or *deref
    end
    LazyInitT -->> Thread1: Same initialized value
    LazyInitT -->> Thread2: Same initialized value
    LazyInitT -->> Thread3: Same initialized value

Multi-threaded Initialization Sequence

The atomic compare_exchange_weak operation ensures exactly one thread succeeds in initialization, while others receive None from call_once.

Sources: src/lib.rs(L53 - L67)  src/lib.rs(L37 - L47)  README.md(L30 - L57) 

Initialization Method Patterns

Direct Value Initialization withinit_once

The init_once method is used when you have a concrete value ready for initialization:

CharacteristicBehavior
InputDirect value of typeT
ReturnReference to initialized value
Error HandlingPanics if already initialized
Use CaseStatic initialization with known values

This pattern is ideal for scenarios where initialization failure should be treated as a programming error.

Sources: src/lib.rs(L31 - L47) 

Closure-Based Initialization withcall_once

The call_once method enables lazy evaluation and graceful handling of race conditions:

CharacteristicBehavior
InputClosureFnOnce() -> T
ReturnOption<&T>
Error HandlingReturnsNoneif already initialized
Use CaseThread-safe lazy evaluation

This pattern provides more flexibility for complex initialization logic and better composability in concurrent contexts.

Sources: src/lib.rs(L49 - L67) 

Access Pattern Categories

Safe Access Patterns

flowchart TD
subgraph subGraph2["Safety Guarantees"]
    I["No UB"]
    J["Runtime Checks"]
    K["Clear Error Modes"]
end
subgraph subGraph1["Return Types"]
    E["bool"]
    F["Option<&T>"]
    G["Option<&mut T>"]
    H["&T or panic"]
end
subgraph subGraph0["Safe Access Methods"]
    A["is_inited()"]
    B["get()"]
    C["get_mut()"]
    D["*value (Deref)"]
end

A --> E
B --> F
C --> G
D --> H
E --> I
F --> I
G --> I
H --> J
I --> K
J --> K

Safe Access Method Categories

Safe access methods provide runtime checks and clear error semantics at the cost of slight performance overhead.

Sources: src/lib.rs(L69 - L83)  src/lib.rs(L85 - L94)  src/lib.rs(L153 - L174) 

Performance-Critical Access Patterns

For performance-critical code paths, unsafe methods bypass runtime checks:

MethodSafety RequirementPerformance Benefit
get_unchecked()Must be called after initializationEliminates bounds checking
get_mut_unchecked()Must be called after initializationEliminates bounds checking
Directforce_get()Internal use onlyMaximum performance

These methods include debug_assert! checks in debug builds while providing zero-cost access in release builds.

Sources: src/lib.rs(L96 - L116)  src/lib.rs(L118 - L126) 

Error Handling Strategies

Panic-Based Error Handling

flowchart TD
A["Method Call"]
B["Error Condition?"]
C["init_once() panic"]
D["Deref panic"]
E["Normal Operation"]
F["Program Termination"]
G["Continue Execution"]

A --> B
B --> C
B --> D
B --> E
C --> F
D --> F
E --> G

Panic-Based Error Flow

The init_once method and Deref implementations use panics to indicate programming errors that should not occur in correct code.

Sources: src/lib.rs(L35)  src/lib.rs(L45)  src/lib.rs(L160)  src/lib.rs(L128 - L133) 

Option-Based Error Handling

The call_once and get methods use Option types for graceful error handling in scenarios where races or uninitialized access might be expected.

ScenarioMethodReturn Value
Successful initializationcall_once(f)Some(&T)
Lost initialization racecall_once(f)None
Access to initialized valueget()Some(&T)
Access to uninitialized valueget()None

Sources: src/lib.rs(L53)  src/lib.rs(L77) 

Common Implementation Patterns

Singleton Pattern Implementation

Static LazyInit variables can implement thread-safe singletons:

flowchart TD
subgraph subGraph0["Singleton Pattern with LazyInit"]
    A["static INSTANCE: LazyInit"]
    B["get_instance() function"]
    C["call_once(|| ExpensiveResource::new())"]
    D["Singleton access"]
end
E["Thread-safe singleton access"]

A --> B
B --> C
C --> D
D --> E

Singleton Pattern Flow

This pattern ensures expensive resources are created only once and shared safely across threads.

Sources: src/lib.rs(L24 - L29)  src/lib.rs(L53 - L67) 

Configuration Loading Pattern

Lazy initialization of configuration data that may be loaded from files or environment variables:

flowchart TD
subgraph subGraph0["Config Loading Pattern"]
    A["static CONFIG: LazyInit"]
    B["call_once(load_config)"]
    C["File/Env Loading"]
    D["Cached Access"]
end
E["Fast subsequent access"]

A --> B
B --> C
C --> D
D --> E

Configuration Loading Flow

This pattern delays expensive I/O operations until the configuration is actually needed.

Sources: src/lib.rs(L53 - L67)  README.md(L39 - L46) 

Performance Considerations

Initialization Cost vs Access Cost

PhaseCostFrequencyOptimization Strategy
InitializationHigh (one-time)OnceUsecall_oncefor lazy evaluation
Access (Safe)LowFrequentUseget()for most cases
Access (Unsafe)MinimalCritical pathUseget_unchecked()when proven safe

The compare_exchange_weak operation provides optimal performance for the initialization phase by reducing spurious failures compared to compare_exchange.

Sources: src/lib.rs(L38 - L39)  src/lib.rs(L58 - L59)  src/lib.rs(L102 - L105) 

Memory Overhead Analysis

The LazyInit<T> wrapper adds minimal overhead:

  • AtomicBool inited: 1 byte + padding
  • UnsafeCell<MaybeUninit<T>>: Same size as T
  • Total overhead: Typically 8 bytes on 64-bit systems due to alignment

Sources: src/lib.rs(L14 - L17) 

Project Configuration

Relevant source files

This document covers the project configuration of the lazyinit crate, focusing on package metadata, licensing strategy, dependency management, and no-std compatibility. The configuration is primarily defined in Cargo.toml and establishes the crate's minimal design philosophy and broad compatibility requirements.

For information about the core implementation details, see LazyInit Implementation. For development tooling and CI configuration, see Development & Contributing.

Configuration Structure

The project configuration follows a minimal approach with clear separation of concerns between package metadata, dependencies, and build targets.

Package Configuration Hierarchy

flowchart TD
subgraph Dependencies["Dependencies"]
    N["Empty - no dependencies"]
end
subgraph subGraph1["Package Metadata"]
    C["name = lazyinit"]
    D["version = 0.2.1"]
    E["edition = 2021"]
    F["authors"]
    G["description"]
    H["license"]
    I["homepage"]
    J["repository"]
    K["documentation"]
    L["keywords"]
    M["categories"]
end
subgraph Cargo.toml["Cargo.toml"]
    A["[package]"]
    B["[dependencies]"]
end

A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
A --> I
A --> J
A --> K
A --> L
A --> M
B --> N

Sources: Cargo.toml(L1 - L15) 

Package Metadata

The package metadata defines the crate's identity, versioning, and discoverability characteristics.

FieldValuePurpose
namelazyinitCrate identifier on crates.io
version0.2.1Semantic version following SemVer
edition2021Rust edition compatibility
authorsYuekai Jia equation618@gmail.comPrimary maintainer
descriptionInitialize a static value lazily.Brief functionality summary
homepagehttps://github.com/arceos-org/arceosParent project reference
repositoryhttps://github.com/arceos-org/lazyinitSource code location
documentationhttps://docs.rs/lazyinitGenerated API documentation

Sources: Cargo.toml(L1 - L12) 

Keywords and Categories

The crate is tagged with specific keywords and categories that indicate its purpose and compatibility:

  • Keywords: lazy, initialization, static
  • Categories: no-std, rust-patterns

These classifications signal the crate's focus on lazy initialization patterns and its compatibility with no-std environments.

Sources: Cargo.toml(L11 - L12) 

Licensing Strategy

The project employs a tri-license approach providing flexibility for different use cases and legal requirements.

Multi-License Configuration

flowchart TD
subgraph subGraph2["Use Cases"]
    E["Open Source Projects"]
    F["Commercial Software"]
    G["Chinese Legal Framework"]
end
subgraph subGraph1["License Options"]
    B["GPL-3.0-or-later"]
    C["Apache-2.0"]
    D["MulanPSL-2.0"]
end
subgraph subGraph0["lazyinit Crate"]
    A["license field"]
end

A --> B
A --> C
A --> D
B --> E
C --> F
D --> G

The license string GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 allows users to choose the most appropriate license for their specific requirements:

  • GPL-3.0-or-later: Copyleft license for open source projects
  • Apache-2.0: Permissive license suitable for commercial use
  • MulanPSL-2.0: License compatible with Chinese legal frameworks

Sources: Cargo.toml(L7) 

No-std Compatibility

The configuration explicitly supports no-std environments through strategic design choices.

Zero-Dependency Architecture

flowchart TD
subgraph subGraph2["Enabled Environments"]
    G["std environments"]
    H["no-std environments"]
    I["embedded systems"]
    J["kernel modules"]
end
subgraph subGraph1["Core Rust"]
    C["core library"]
    D["AtomicBool"]
    E["UnsafeCell"]
    F["MaybeUninit"]
end
subgraph subGraph0["lazyinit Crate"]
    A["[dependencies]"]
    B["Empty section"]
end

A --> B
B --> C
C --> D
C --> E
C --> F
D --> G
D --> H
D --> I
D --> J

The empty [dependencies] section indicates that the crate relies exclusively on Rust's core library, enabling usage in:

  • Embedded systems without heap allocation
  • Kernel modules and OS development
  • WebAssembly environments
  • Any no-std compatible Rust project

Sources: Cargo.toml(L14 - L15) 

Build Target Compatibility

The configuration supports the crate's multi-target compatibility strategy, as evidenced by the CI pipeline testing multiple architectures.

Target Architecture Support

TargetEnvironmentPurpose
x86_64-unknown-linux-gnuStandard LinuxGeneral development and testing
x86_64-unknown-noneBare metal x86_64OS development and embedded
riscv64gc-unknown-none-elfRISC-V bare metalRISC-V embedded systems
aarch64-unknown-none-softfloatARM64 bare metalARM embedded systems

The configuration's minimal dependencies and no-std categorization enable this broad target support without requiring target-specific conditional compilation.

Sources: Cargo.toml(L12) 

Configuration Philosophy

The project configuration reflects several key design principles:

  1. Minimalism: Zero external dependencies reduce attack surface and compatibility issues
  2. Flexibility: Multi-licensing accommodates diverse legal requirements
  3. Universality: No-std compatibility enables use across all Rust environments
  4. Clarity: Explicit categorization and keywords aid discoverability

This configuration strategy aligns with the crate's role as a fundamental building block for lazy initialization patterns across the Rust ecosystem.

Sources: Cargo.toml(L1 - L15) 

Development & Contributing

Relevant source files

This document provides information for developers who want to contribute to the lazyinit crate or understand its development processes. It covers the contribution workflow, development environment requirements, quality assurance mechanisms, and deployment procedures.

For detailed information about the CI/CD pipeline implementation, see CI/CD Pipeline. For guidance on setting up a local development environment, see Development Environment Setup.

Development Workflow Overview

The lazyinit crate follows a standard GitHub-based development workflow with automated quality gates and multi-target validation. All contributions are validated through a comprehensive CI/CD pipeline that ensures code quality, functionality, and compatibility across multiple target architectures.

Contribution Process Flow

flowchart TD
A["Developer Fork/Clone"]
B["Local Development"]
C["cargo fmt --all -- --check"]
D["cargo clippy --all-features"]
E["cargo build --all-features"]
F["cargo test -- --nocapture"]
G["Push to Branch"]
H["Create Pull Request"]
I["GitHub Actions CI"]
J["CI Passes?"]
K["Code Review"]
L["Fix Issues"]
M["Merge to Main"]
N["Deploy Documentation"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
J --> L
K --> M
L --> B
M --> N

Sources: .github/workflows/ci.yml(L1 - L56) 

Quality Gates and Validation

The project enforces several quality gates that all contributions must pass. These are implemented as sequential steps in the CI pipeline to ensure code quality and consistency.

Quality Gate Pipeline

flowchart TD
subgraph subGraph1["CI Validation"]
    F["actions/checkout@v4"]
    G["dtolnay/rust-toolchain@nightly"]
    H["cargo fmt --all -- --check"]
    I["cargo clippy --target TARGET --all-features"]
    J["cargo build --target TARGET --all-features"]
    K["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph0["Local Development"]
    A["rustc"]
    B["rustfmt"]
    C["clippy"]
    D["build"]
    E["test"]
end

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K

The quality gates include:

GateCommandPurposeScope
Format Checkcargo fmt --all -- --checkCode formatting consistencyAll code
Lintingcargo clippy --all-featuresCode quality and best practicesAll targets
Buildcargo build --all-featuresCompilation validationAll targets
Testingcargo test -- --nocaptureFunctional validationLinux only

Sources: .github/workflows/ci.yml(L22 - L30) 

Multi-Target Architecture Support

The crate supports multiple target architectures to ensure compatibility with diverse embedded and systems programming environments. This is particularly important for a no-std compatible crate.

Target Architecture Matrix

flowchart TD
subgraph subGraph1["Validation Scope"]
    E["Format Check"]
    F["Clippy Linting"]
    G["Build Validation"]
    H["Unit Testing"]
end
subgraph subGraph0["Supported Targets"]
    A["x86_64-unknown-linux-gnu"]
    B["x86_64-unknown-none"]
    C["riscv64gc-unknown-none-elf"]
    D["aarch64-unknown-none-softfloat"]
end

E --> A
E --> B
E --> C
E --> D
F --> A
F --> B
F --> C
F --> D
G --> A
G --> B
G --> C
G --> D
H --> A

The target matrix includes:

  • x86_64-unknown-linux-gnu: Standard Linux development target with full testing
  • x86_64-unknown-none: Bare metal x86_64 target for embedded systems
  • riscv64gc-unknown-none-elf: RISC-V 64-bit embedded target
  • aarch64-unknown-none-softfloat: ARM64 embedded target with software floating point

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L25 - L30) 

Toolchain and Component Requirements

Development requires the Rust nightly toolchain with specific components for comprehensive validation and documentation generation.

Required Toolchain Components

ComponentPurposeRequired For
rust-srcSource code for cross-compilationMulti-target builds
clippyLinting and code analysisQuality gates
rustfmtCode formattingStyle consistency
nightly toolchainLatest language featuresCore functionality

Sources: .github/workflows/ci.yml(L15 - L19) 

Documentation Generation and Deployment

The project maintains automated documentation deployment to GitHub Pages with strict documentation quality enforcement.

Documentation Pipeline

sequenceDiagram
    participant Developer as "Developer"
    participant GitHubActions as "GitHub Actions"
    participant GitHubPages as "GitHub Pages"

    Developer ->> GitHubActions: "Push to main branch"
    GitHubActions ->> GitHubActions: "cargo doc --no-deps --all-features"
    Note over GitHubActions: RUSTDOCFLAGS:<br>-D rustdoc::broken_intra_doc_links<br>-D missing-docs
    GitHubActions ->> GitHubActions: "Generate index.html redirect"
    GitHubActions ->> GitHubPages: "Deploy to gh-pages branch"
    GitHubPages -->> Developer: "Documentation available"

Documentation requirements:

  • All public APIs must have documentation comments
  • No broken intra-doc links allowed (-D rustdoc::broken_intra_doc_links)
  • Missing documentation is treated as an error (-D missing-docs)
  • Automatic index page generation for navigation

Sources: .github/workflows/ci.yml(L32 - L56)  .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47 - L48) 

Development Environment Configuration

The project includes standard development environment configuration files to ensure consistent development experiences across contributors.

Excluded Artifacts

The .gitignore configuration excludes common development artifacts:

PatternPurpose
/targetRust build output directory
/.vscodeVisual Studio Code configuration
.DS_StoremacOS filesystem metadata
Cargo.lockDependency lock file (for libraries)

Sources: .gitignore(L1 - L5) 

Contribution Guidelines

  1. Fork and Clone: Create a fork of the repository and clone it locally
  2. Branch: Create a feature branch for your changes
  3. Develop: Make changes ensuring they pass all local quality gates
  4. Test: Verify functionality on the primary target (x86_64-unknown-linux-gnu)
  5. Submit: Create a pull request with clear description of changes
  6. Review: Address feedback from maintainers and automated checks

All contributions are automatically validated against the multi-target matrix to ensure compatibility across supported architectures.

Sources: .github/workflows/ci.yml(L3)  .github/workflows/ci.yml(L6 - L30) 

CI/CD Pipeline

Relevant source files

This document details the continuous integration and continuous deployment (CI/CD) pipeline for the lazyinit crate. The pipeline ensures code quality through automated testing, linting, formatting checks, and multi-target compilation, while also automating documentation deployment to GitHub Pages.

For information about setting up the development environment locally, see Development Environment Setup. For details about the core LazyInit implementation being tested by this pipeline, see LazyInit Implementation.

Pipeline Overview

The CI/CD pipeline consists of two parallel workflows defined in a single GitHub Actions configuration file. The pipeline triggers on both push events and pull requests to ensure all changes are validated.

CI/CD Workflow Architecture

flowchart TD
subgraph subGraph4["Documentation Pipeline"]
    M["cargo doc"]
    N["rustdoc validation"]
    O["GitHub Pages publish"]
end
subgraph subGraph3["Quality Gates"]
    I["cargo fmt --check"]
    J["cargo clippy"]
    K["cargo build"]
    L["cargo test"]
end
subgraph subGraph2["doc Job"]
    F["ubuntu-latest"]
    G["nightly toolchain"]
    H["GitHub Pages deployment"]
end
subgraph subGraph1["ci Job"]
    C["ubuntu-latest"]
    D["nightly toolchain"]
    E["Multi-target matrix"]
end
subgraph subGraph0["Trigger Events"]
    A["push"]
    B["pull_request"]
end

A --> C
A --> F
B --> C
B --> F
C --> E
C --> I
C --> J
C --> K
C --> L
F --> M
F --> N
F --> O

Sources: .github/workflows/ci.yml(L1 - L56) 

Target Platform Matrix

The pipeline validates the crate across multiple target platforms to ensure broad compatibility, particularly for no-std environments:

TargetPurposeTest Execution
x86_64-unknown-linux-gnuStandard Linux developmentFull test suite
x86_64-unknown-noneBare metal x86_64Build and lint only
riscv64gc-unknown-none-elfRISC-V bare metalBuild and lint only
aarch64-unknown-none-softfloatARM64 bare metalBuild and lint only

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L29 - L30) 

CI Job Workflow

The ci job implements a comprehensive quality assurance workflow that runs across all target platforms using a matrix strategy.

Quality Assurance Pipeline

flowchart TD
subgraph subGraph2["Target Matrix"]
    I["x86_64-unknown-linux-gnu"]
    J["x86_64-unknown-none"]
    K["riscv64gc-unknown-none-elf"]
    L["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Validation Steps"]
    D["rustc --version --verbose"]
    E["cargo fmt --all --check"]
    F["cargo clippy --target TARGET"]
    G["cargo build --target TARGET"]
    H["cargo test --target TARGET"]
end
subgraph subGraph0["Setup Phase"]
    A["actions/checkout@v4"]
    B["dtolnay/rust-toolchain@nightly"]
    C["rust-src, clippy, rustfmt components"]
end

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
F --> I
F --> J
F --> K
F --> L
G --> H
G --> I
G --> J
G --> K
G --> L
H --> I

Sources: .github/workflows/ci.yml(L14 - L30) 

Quality Gates Implementation

The pipeline enforces multiple quality gates in sequence:

  1. Code Formatting: cargo fmt --all -- --check ensures consistent code style
  2. Linting: cargo clippy --target ${{ matrix.targets }} --all-features with specific allowances for clippy::new_without_default
  3. Compilation: cargo build --target ${{ matrix.targets }} --all-features validates cross-platform compatibility
  4. Testing: cargo test --target ${{ matrix.targets }} runs only on x86_64-unknown-linux-gnu target

The conditional test execution pattern reflects the reality that unit tests require a hosted environment, while build validation can occur for bare metal targets.

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation Job Workflow

The doc job handles automated documentation generation and deployment with sophisticated branching logic.

Documentation Pipeline Architecture

flowchart TD
subgraph subGraph2["Deployment Conditions"]
    G["JamesIves/github-pages-deploy-action@v4"]
    H["single-commit: true"]
    I["branch: gh-pages"]
    J["folder: target/doc"]
end
subgraph subGraph1["Branch Logic"]
    D["github.ref == default-branch"]
    E["github.event_name == pull_request"]
    F["continue-on-error for non-default branches"]
end
subgraph subGraph0["Documentation Build"]
    A["cargo doc --no-deps --all-features"]
    B["RUSTDOCFLAGS validation"]
    C["Index page generation"]
end

A --> B
B --> C
C --> D
D --> G
E --> F
F --> A
G --> H
G --> I
G --> J

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Quality Controls

The documentation job implements strict quality controls through environment variables:

  • RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs treats documentation warnings as errors
  • Automated index page generation using cargo tree output for navigation
  • Conditional error handling that allows documentation build failures on non-default branches

The index page generation command creates a redirect: printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L46 - L48) 

Build Matrix Configuration

The pipeline uses a sophisticated matrix strategy to validate across multiple dimensions while maintaining efficiency.

Matrix Strategy Implementation

flowchart TD
subgraph subGraph1["Build Combinations"]
    C["nightly + x86_64-unknown-linux-gnu"]
    D["nightly + x86_64-unknown-none"]
    E["nightly + riscv64gc-unknown-none-elf"]
    F["nightly + aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Matrix Dimensions"]
    A["rust-toolchain: [nightly]"]
    B["targets: [4 platforms]"]
end
subgraph subGraph2["Execution Strategy"]
    G["fail-fast: false"]
    H["Parallel execution"]
    I["Independent failure isolation"]
end

A --> C
A --> D
A --> E
A --> F
B --> C
B --> D
B --> E
B --> F
G --> H
H --> I

The fail-fast: false configuration ensures that a failure on one target platform doesn't prevent validation of other platforms, providing comprehensive feedback on cross-platform compatibility issues.

Sources: .github/workflows/ci.yml(L8 - L12) 

Deployment and Permissions

The documentation deployment process uses specific GitHub Actions permissions and deployment strategies optimized for documentation sites.

GitHub Pages Deployment Configuration

The doc job requires contents: write permissions for GitHub Pages deployment. The deployment uses JamesIves/github-pages-deploy-action@v4 with:

  • single-commit: true for clean deployment history
  • branch: gh-pages as the deployment target
  • folder: target/doc containing the generated documentation

Deployment only occurs on the default branch (github.ref == env.default-branch), preventing documentation pollution from feature branches.

Sources: .github/workflows/ci.yml(L36 - L37)  .github/workflows/ci.yml(L49 - L55) 

Development Environment Setup

Relevant source files

This document provides a comprehensive guide for setting up a development environment for the lazyinit crate. It covers toolchain requirements, target platform configuration, development workflows, and build artifact management. The information is derived from the project's CI/CD configuration and development infrastructure files.

For information about the CI/CD pipeline and automated quality gates, see CI/CD Pipeline. For details about the core implementation and API, see LazyInit Implementation.

Toolchain Requirements

The lazyinit crate requires specific Rust toolchain components and configuration to support its no-std compatibility and multi-target architecture.

Required Rust Components

The development environment must include the nightly Rust toolchain with the following components:

ComponentPurpose
rust-srcSource code for cross-compilation targets
clippyLinting and static analysis
rustfmtCode formatting

Target Platform Support

The crate supports multiple target platforms to ensure broad compatibility across different architectures and environments:

flowchart TD
A["nightly-toolchain"]
B["x86_64-unknown-linux-gnu"]
C["x86_64-unknown-none"]
D["riscv64gc-unknown-none-elf"]
E["aarch64-unknown-none-softfloat"]
F["Standard Linux Environment"]
G["Bare Metal x86_64"]
H["RISC-V Bare Metal"]
I["ARM64 Bare Metal"]
J["Unit Testing"]
K["No-std Validation"]

A --> B
A --> C
A --> D
A --> E
B --> F
C --> G
D --> H
E --> I
F --> J
G --> K
H --> K
I --> K

Development Toolchain Setup Process

Sources: .github/workflows/ci.yml(L11 - L19) 

Development Workflow

The development workflow follows a structured approach with multiple quality gates to ensure code quality and compatibility across all supported targets.

Core Development Commands

flowchart TD
A["Source Code Changes"]
B["cargo fmt --all --check"]
C["cargo clippy --target TARGET --all-features"]
D["cargo build --target TARGET --all-features"]
E["cargo test --target TARGET"]
F["Code Ready"]
G["Format Violation"]
H["Lint Warnings"]
I["Build Errors"]
J["Test Failures"]

A --> B
B --> C
B --> G
C --> D
C --> H
D --> E
D --> I
E --> F
E --> J
G --> A
H --> A
I --> A
J --> A

Development Quality Gate Workflow

Command Reference

The following commands form the core development workflow:

CommandPurposeTarget Scope
cargo fmt --all -- --checkCode formatting validationAll code
cargo clippy --target --all-features -- -A clippy::new_without_defaultLinting with custom rulesPer target
cargo build --target --all-featuresCompilation verificationPer target
cargo test --target -- --nocaptureUnit test executionLinux only

Clippy Configuration

The project uses a specific clippy configuration that allows the new_without_default lint, which is appropriate for the LazyInit::new() constructor pattern.

Sources: .github/workflows/ci.yml(L22 - L30) 

Build Artifacts and Git Configuration

The development environment is configured to exclude specific files and directories that are generated during development or are platform-specific.

Excluded Files and Directories

flowchart TD
A[".gitignore"]
B["/target"]
C["/.vscode"]
D[".DS_Store"]
E["Cargo.lock"]
F["Build Artifacts"]
G["Documentation Output"]
H["Test Binaries"]
I["VS Code Configuration"]
J["Editor Settings"]
K["macOS System Files"]
L["Dependency Lock File"]
M["Version Pinning"]

A --> B
A --> C
A --> D
A --> E
B --> F
B --> G
B --> H
C --> I
C --> J
D --> K
E --> L
E --> M

Git Ignore Structure and Build Artifacts

Build Artifact Management

PathContentReason for Exclusion
/targetCompiled binaries, documentation, test artifactsGenerated content, large files
/.vscodeVS Code editor configurationEditor-specific, not universal
.DS_StoremacOS filesystem metadataPlatform-specific system files
Cargo.lockDependency version locksLibrary crate convention

Sources: .gitignore(L1 - L5) 

Documentation Generation

The project includes sophisticated documentation generation with specific configuration for quality and deployment.

Documentation Build Configuration

The documentation build process uses specific RUSTDOCFLAGS to enforce documentation quality:

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

These flags ensure:

  • All intra-documentation links are valid
  • All public items have documentation

Documentation Workflow

flowchart TD
A["cargo doc --no-deps --all-features"]
B["Generate Documentation"]
C["Create index.html redirect"]
D["target/doc directory"]
E["GitHub Pages Deployment"]
F["RUSTDOCFLAGS"]
G["-D rustdoc::broken_intra_doc_links"]
H["-D missing-docs"]
I["Default Branch Only"]

A --> B
B --> C
C --> D
D --> E
F --> A
G --> F
H --> F
I --> E

Documentation Generation and Deployment Process

The documentation generation creates a redirect index file using the crate name extracted from cargo tree output:

printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Sources: .github/workflows/ci.yml(L40 - L55) 

Local Development Setup

To establish a complete development environment for the lazyinit crate:

  1. Install Rust Nightly:
rustup toolchain install nightly
rustup default nightly
  1. Add Required Components:
rustup component add rust-src clippy rustfmt
  1. Add Target Platforms:
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
  1. Verify Setup:
rustc --version --verbose
cargo fmt --version
cargo clippy --version

This configuration ensures compatibility with the project's CI/CD pipeline and enables local development that matches the automated testing environment.

Sources: .github/workflows/ci.yml(L15 - L21) 

Overview

Relevant source files

This document provides an overview of the linked_list_r4l crate, a Rust library that implements intrusive linked lists with constant-time arbitrary removal capabilities. It covers the crate's purpose, key features, architecture, and position within the ArceOS ecosystem.

For detailed usage examples, see Quick Start Guide. For comprehensive API documentation, see API Reference. For advanced concepts like memory management and thread safety, see Core Concepts.

Purpose and Scope

The linked_list_r4l crate provides linked lists that support arbitrary removal of nodes in constant time O(1), regardless of the node's position in the list. This capability is achieved through an intrusive design where list metadata is embedded directly within the nodes themselves, eliminating the need to traverse the list to find and remove specific elements.

The library is designed for systems programming contexts where performance predictability is critical, particularly in kernel and embedded environments. It originated from the Rust-for-Linux project and has been adapted for use in the ArceOS operating system kernel.

Sources: Cargo.toml(L6)  README.md(L7 - L11) 

Key Features

FeatureDescriptionBenefit
Constant-time removalRemove any node in O(1) timePredictable performance for real-time systems
Thread safetyAtomic operations with insertion trackingSafe concurrent access patterns
Multiple ownership modelsSupport forBox,Arc, and&TFlexible memory management strategies
Zero-cost abstractionsCompile-time polymorphism via traitsNo runtime overhead
no-stdcompatibleWorks in embedded and kernel contextsSuitable for constrained environments

Sources: Cargo.toml(L6 - L12)  README.md(L7) 

Architecture Overview

The crate implements a three-layer architecture that balances safety, performance, and usability:

flowchart TD
subgraph Raw_Layer["Raw Unsafe Layer"]
    RawList["RawList<G: GetLinks>"]
    GetLinks["GetLinks trait"]
    Links["Links<T> struct"]
    ListEntry["ListEntry<T>"]
end
subgraph Wrapper_Layer["Safe Wrapper Layer"]
    List_G["List<G: GetLinksWrapped>"]
    GetLinksWrapped["GetLinksWrapped trait"]
    Wrapper["Wrapper trait"]
end
subgraph User_API_Layer["User API Layer"]
    def_node["def_node! macro"]
    List_T["List<T>"]
    Examples["Usage Examples"]
end

Examples --> def_node
GetLinksWrapped --> GetLinks
Links --> ListEntry
List_G --> GetLinksWrapped
List_G --> RawList
List_G --> Wrapper
List_T --> List_G
RawList --> Links
def_node --> List_T

Three-Layer Architecture

  • User API Layer: Provides convenient macros and simple interfaces for common use cases
  • Safe Wrapper Layer: Manages memory ownership and provides safe abstractions over raw operations
  • Raw Unsafe Layer: Implements core linked list algorithms using unsafe pointer operations

Sources: Architecture diagrams, README.md(L15 - L62) 

Core Components and Code Entities

The following diagram maps the main system concepts to their corresponding code entities:

flowchart TD
subgraph Operations["Core Operations"]
    push_back["push_back()"]
    push_front["push_front()"]
    remove["remove()"]
    pop_front["pop_front()"]
    iter["iter()"]
end
subgraph Node_Structure["Node Memory Layout"]
    UserData["User Data(inner: T)"]
    LinkData["Link Metadata(links: Links<Self>)"]
end
subgraph Code_Entities["Primary Code Entities"]
    Links_struct["Links<T>src/raw_list.rs"]
    GetLinks_trait["GetLinks traitsrc/raw_list.rs"]
    RawList_struct["RawList<G>src/raw_list.rs"]
    List_linked_list["List<G>src/linked_list.rs"]
    List_lib["List<T>src/lib.rs"]
    def_node_macro["def_node! macrosrc/lib.rs"]
end

GetLinks_trait --> Links_struct
Links_struct --> LinkData
List_lib --> List_linked_list
List_lib --> iter
List_lib --> pop_front
List_lib --> push_back
List_lib --> push_front
List_lib --> remove
List_linked_list --> RawList_struct
RawList_struct --> GetLinks_trait
def_node_macro --> LinkData
def_node_macro --> UserData

Key Code Entities:

  • Links<T>: Contains the intrusive link metadata embedded in each node
  • GetLinks trait: Provides access to a type's embedded Links<T> instance
  • RawList<G>: Implements core unsafe linked list operations
  • List<G> (linked_list.rs): Safe wrapper managing ownership via Wrapper trait
  • List<T> (lib.rs): User-friendly API for common cases
  • def_node! macro: Generates node structs with embedded links

Sources: README.md(L16 - L31)  Architecture diagrams

Position in ArceOS Ecosystem

The linked_list_r4l crate serves as a foundational data structure within the ArceOS operating system kernel. Its constant-time removal capability makes it suitable for:

  • Task scheduling: Efficient manipulation of process/thread queues
  • Memory management: Managing free block lists with predictable performance
  • Device drivers: Maintaining I/O request queues with guaranteed latency bounds
  • Interrupt handling: Time-critical data structure operations

The crate's no-std compatibility and zero-cost abstractions align with ArceOS's goals of providing a high-performance, safe kernel implementation.

Sources: Cargo.toml(L8 - L12)  README.md(L9 - L11) 

Getting Started

To begin using this crate:

  1. Basic usage: See Quick Start Guide for immediate examples
  2. API details: Consult API Reference for comprehensive interface documentation
  3. Advanced topics: Review Core Concepts for memory management and thread safety patterns
  4. Development: See Development Guide for contribution guidelines

The typical usage pattern involves defining nodes with the def_node! macro, creating a List<T> instance, and performing standard list operations with the guarantee of constant-time arbitrary removal.

Sources: README.md(L13 - L62) 

Quick Start Guide

Relevant source files

This guide covers the essential steps to start using the linked_list_r4l crate for basic linked list operations. It demonstrates node creation using the def_node! macro and common list operations like insertion, removal, and iteration. For detailed architecture information, see Architecture Overview. For comprehensive API documentation, see API Reference.

Prerequisites and Setup

Add the crate to your Cargo.toml:

[dependencies]
linked_list_r4l = "0.1"

The library provides two main approaches for creating list nodes:

  1. Recommended: Using the def_node! macro for automatic code generation
  2. Manual: Implementing the GetLinks trait manually

Creating Nodes with def_node! Macro

The def_node! macro is the primary way to create node types. It automatically generates the required structure and trait implementations.

Basic Node Definition

use linked_list_r4l::{def_node, List};

def_node! {
    /// A simple node containing a usize value
    struct NumberNode(usize);
    
    /// A public node with generic type
    pub struct GenericNode<T>(T);
}

The macro generates a struct with two fields: inner containing your data and links for list management. It also implements the GetLinks trait and provides helper methods.

Sources: src/lib.rs(L109 - L178) 

Node Creation and Basic Operations

// Create nodes
let node1 = Box::new(NumberNode::new(42));
let node2 = Box::new(NumberNode::new(100));

// Create list
let mut list = List::<Box<NumberNode>>::new();

// Add nodes
list.push_back(node1);
list.push_back(node2);

// Access data
for node in list.iter() {
    println!("Value: {}", *node.inner());
}

// Remove nodes
let first = list.pop_front().unwrap();
assert_eq!(first.into_inner(), 42);

Sources: src/lib.rs(L136 - L165) 

Generated Node Structure

When you use def_node!, the following structure is generated:

flowchart TD
UserMacro["def_node! { struct NumberNode(usize); }"]
GeneratedStruct["Generated Struct NumberNode• inner: usize• links: Links<Self>"]
GeneratedImpl["Generated Implementations• GetLinks trait• new() constructor• inner() accessor• into_inner() converter• Deref to inner type"]
UserCode["User Codelet node = NumberNode::new(42)"]
ListAPI["List APIlist.push_back(Box::new(node))"]

GeneratedImpl --> UserCode
GeneratedStruct --> GeneratedImpl
UserCode --> ListAPI
UserMacro --> GeneratedStruct

Sources: src/lib.rs(L12 - L58)  src/lib.rs(L60 - L107) 

List Operations Flow

The following diagram shows how common list operations map to code entities:

flowchart TD
subgraph subGraph2["Core Types"]
    ListStruct["List<G>"]
    LinksStruct["Links<T>"]
    GetLinksTrait["GetLinks trait"]
end
subgraph subGraph1["Node Management"]
    BoxWrapper["Box<NumberNode>"]
    NodeData["NumberNode.inner"]
    NodeLinks["NumberNode.links"]
end
subgraph subGraph0["User Operations"]
    CreateList["List::new()"]
    AddNode["list.push_back(node)"]
    RemoveNode["list.pop_front()"]
    IterateList["list.iter()"]
end

AddNode --> BoxWrapper
BoxWrapper --> NodeData
BoxWrapper --> NodeLinks
CreateList --> ListStruct
IterateList --> GetLinksTrait
LinksStruct --> GetLinksTrait
ListStruct --> LinksStruct
NodeLinks --> LinksStruct
RemoveNode --> BoxWrapper

Sources: src/lib.rs(L6)  src/lib.rs(L7)  src/lib.rs(L19 - L26) 

Complete Example

Here's a comprehensive example showing typical usage patterns:

use linked_list_r4l::{def_node, List};

def_node! {
    /// Task node for a simple scheduler
    pub struct TaskNode(String);
    
    /// Priority task with generic data
    pub struct PriorityTask<T>(T);
}

fn main() {
    // Create task list
    let mut task_queue = List::<Box<TaskNode>>::new();
    
    // Add tasks
    task_queue.push_back(Box::new(TaskNode::new("initialize".to_string())));
    task_queue.push_back(Box::new(TaskNode::new("process".to_string())));
    task_queue.push_back(Box::new(TaskNode::new("cleanup".to_string())));
    
    // Process tasks
    while let Some(task) = task_queue.pop_front() {
        println!("Processing: {}", task.inner());
        // Task is automatically dropped here
    }
    
    // Generic example
    let mut numbers = List::<Box<PriorityTask<i32>>>::new();
    numbers.push_back(Box::new(PriorityTask::new(1)));
    numbers.push_back(Box::new(PriorityTask::new(2)));
    
    // Iterate without removing
    for (index, task) in numbers.iter().enumerate() {
        println!("Task {}: {}", index, task.inner());
    }
}

Sources: src/lib.rs(L125 - L165) 

Memory Management

The library uses Box<T> for heap allocation by default, but supports other ownership models:

Wrapper TypeUse CaseExample
BoxSingle ownership, heap allocatedList::<Box>::new()
ArcShared ownership, reference countedList::<Arc>::new()
&NodeBorrowed referencesList::<&MyNode>::new()

Sources: src/lib.rs(L6) 

Manual Node Implementation (Alternative)

For advanced use cases, you can implement the GetLinks trait manually instead of using the macro:

#![allow(unused)]
fn main() {
use linked_list_r4l::{GetLinks, Links, List};

pub struct CustomNode {
    pub data: String,
    links: Links<Self>,
}

impl GetLinks for CustomNode {
    type EntryType = Self;
    
    fn get_links(t: &Self) -> &Links<Self> {
        &t.links
    }
}

impl CustomNode {
    fn new(data: String) -> Self {
        Self {
            data,
            links: Links::new(),
        }
    }
}
}

Sources: README.md(L15 - L44) 

Next Steps

Architecture Overview

Relevant source files

This document explains the three-layer architecture of the linked_list_r4l crate, detailing how the unsafe raw list implementation, safe wrapper layer, and user-friendly API work together to provide constant-time arbitrary removal with memory safety guarantees.

For specific API usage examples, see Quick Start Guide. For detailed trait and struct documentation, see API Reference. For memory safety and thread safety concepts, see Core Concepts.

Three-Layer Architecture

The linked_list_r4l crate implements a sophisticated three-layer architecture that separates concerns between performance, safety, and usability:

flowchart TD
subgraph subGraph2["Layer 1: Raw Unsafe Layer"]
    RawListStruct["RawList<G: GetLinks>"]
    GetLinksTrait["GetLinks trait"]
    LinksStruct["Links<T> struct"]
    ListEntryStruct["ListEntry<T> struct"]
    AtomicOps["AtomicBool operations"]
end
subgraph subGraph1["Layer 2: Safe Wrapper Layer"]
    WrappedList["List<G: GetLinksWrapped>"]
    WrapperTrait["Wrapper<T> trait"]
    GetLinksWrappedTrait["GetLinksWrapped trait"]
    BoxImpl["Box<T> implementation"]
    ArcImpl["Arc<T> implementation"]
    RefImpl["&T implementation"]
end
subgraph subGraph0["Layer 3: User-Friendly API"]
    DefNodeMacro["def_node! macro"]
    SimpleList["List<T> type alias"]
    GeneratedNodes["Generated Node Types"]
end

DefNodeMacro --> GeneratedNodes
GeneratedNodes --> GetLinksTrait
GetLinksWrappedTrait --> GetLinksTrait
LinksStruct --> AtomicOps
LinksStruct --> ListEntryStruct
RawListStruct --> GetLinksTrait
RawListStruct --> LinksStruct
SimpleList --> WrappedList
WrappedList --> GetLinksWrappedTrait
WrappedList --> RawListStruct
WrappedList --> WrapperTrait
WrapperTrait --> ArcImpl
WrapperTrait --> BoxImpl
WrapperTrait --> RefImpl

Layer 1 (Raw Unsafe): Provides maximum performance through direct pointer manipulation and atomic operations. The RawList<G: GetLinks> struct handles all unsafe linked list operations.

Layer 2 (Safe Wrapper): Manages memory ownership and provides safe abstractions over the raw layer. The List<G: GetLinksWrapped> struct ensures memory safety through the Wrapper<T> trait.

Layer 3 (User-Friendly): Offers convenient APIs and code generation. The def_node! macro automatically generates node types that implement required traits.

Sources: src/lib.rs(L1 - L179)  src/linked_list.rs(L1 - L355)  src/raw_list.rs(L1 - L596) 

Type System and Trait Architecture

The crate's type system uses traits to enable flexibility while maintaining type safety:

flowchart TD
subgraph subGraph3["Wrapper Implementations"]
    BoxWrapper["impl Wrapper<T> for Box<T>"]
    ArcWrapper["impl Wrapper<T> for Arc<T>"]
    RefWrapper["impl Wrapper<T> for &T"]
end
subgraph subGraph2["Generated Types"]
    MacroGenerated["def_node! generated• inner: T• links: Links<Self>• implements GetLinks"]
end
subgraph subGraph1["Data Structures"]
    Links["Links<T> struct• inserted: AtomicBool• entry: UnsafeCell"]
    ListEntry["ListEntry<T> struct• next: Option<NonNull<T>>• prev: Option<NonNull<T>>"]
    RawList["RawList<G> struct• head: Option<NonNull>"]
    List["List<G> struct• list: RawList<G>"]
end
subgraph subGraph0["Core Traits"]
    GetLinks["GetLinks trait• type EntryType• fn get_links()"]
    GetLinksWrapped["GetLinksWrapped trait• type Wrapped• extends GetLinks"]
    Wrapper["Wrapper<T> trait• fn into_pointer()• fn from_pointer()• fn as_ref()"]
end

ArcWrapper --> Wrapper
BoxWrapper --> Wrapper
GetLinksWrapped --> GetLinks
GetLinksWrapped --> Wrapper
Links --> ListEntry
List --> GetLinksWrapped
List --> RawList
MacroGenerated --> GetLinks
RawList --> GetLinks
RawList --> Links
RefWrapper --> Wrapper

The GetLinks trait (src/raw_list.rs(L23 - L29) ) allows any type to participate in linked lists by providing access to embedded Links<T>. The Wrapper<T> trait (src/linked_list.rs(L18 - L31) ) abstracts over different ownership models, enabling the same list implementation to work with Box<T>, Arc<T>, and &T.

Sources: src/raw_list.rs(L23 - L29)  src/linked_list.rs(L18 - L31)  src/linked_list.rs(L85 - L121)  src/lib.rs(L11 - L107) 

Memory Management Architecture

The crate implements a controlled boundary between safe and unsafe code to manage memory ownership:

flowchart TD
subgraph subGraph2["Unsafe Pointer Zone"]
    NonNullPtrs["NonNull<T>Raw pointers"]
    RawListOps["RawList<G>Unsafe operations"]
    AtomicInsertion["Links<T>.insertedAtomicBool tracking"]
    PointerArithmetic["ListEntry<T>next/prev pointers"]
end
subgraph subGraph1["Safety Boundary"]
    WrapperLayer["Wrapper<T> traitinto_pointer() / from_pointer()"]
    GetLinksWrappedLayer["GetLinksWrapped traittype Wrapped"]
    ListLayer["List<G> structSafe operations"]
end
subgraph subGraph0["Safe Memory Zone"]
    UserCode["User Code"]
    OwnedBox["Box<Node>Heap ownership"]
    SharedArc["Arc<Node>Reference counting"]
    BorrowedRef["&NodeBorrowed reference"]
end

BorrowedRef --> WrapperLayer
GetLinksWrappedLayer --> ListLayer
ListLayer --> NonNullPtrs
NonNullPtrs --> RawListOps
OwnedBox --> WrapperLayer
RawListOps --> AtomicInsertion
RawListOps --> PointerArithmetic
SharedArc --> WrapperLayer
UserCode --> BorrowedRef
UserCode --> OwnedBox
UserCode --> SharedArc
WrapperLayer --> GetLinksWrappedLayer

The safety boundary ensures that:

  • Owned objects are converted to raw pointers only when inserted
  • Raw pointers are converted back to owned objects when removed
  • Atomic tracking prevents double-insertion and use-after-free
  • Memory lifetime is managed by the wrapper type

Sources: src/linked_list.rs(L33 - L83)  src/raw_list.rs(L35 - L66)  src/raw_list.rs(L74 - L86) 

Data Flow and Operation Architecture

Operations flow through the abstraction layers with ownership transformations at each boundary:


The data flow demonstrates how:

  1. The macro generates boilerplate GetLinks implementations
  2. Ownership transfers happen at wrapper boundaries
  3. Atomic operations ensure thread-safe insertion tracking
  4. Constant-time removal works through direct pointer manipulation

Sources: src/lib.rs(L11 - L107)  src/linked_list.rs(L153 - L162)  src/linked_list.rs(L201 - L208)  src/raw_list.rs(L57 - L65)  src/raw_list.rs(L199 - L235) 

Cursor and Iterator Architecture

The crate provides both immutable iteration and mutable cursor-based traversal:

flowchart TD
subgraph subGraph2["Raw Layer Implementation"]
    RawIterator["raw_list::Iterator<G>• cursor_front: Cursor• cursor_back: Cursor"]
    RawCursor["raw_list::Cursor<G>• current() -> &T• move_next/prev()"]
    RawCursorMut["raw_list::CursorMut<G>• current() -> &mut T• remove_current()• peek operations"]
    CommonCursor["CommonCursor<G>• cur: Option<NonNull>• move_next/prev logic"]
end
subgraph subGraph1["Cursor-Based Mutation"]
    CursorMut["List<G>::cursor_front_mut()Returns CursorMut<G>"]
    CursorMutOps["CursorMut operations• current() -> &mut T• remove_current()• peek_next/prev()• move_next()"]
end
subgraph subGraph0["High-Level Iteration"]
    ListIter["List<G>::iter()Returns Iterator<G>"]
    ListIterImpl["impl Iterator for Iterator<G>Delegates to raw_list::Iterator"]
end

CursorMut --> CursorMutOps
CursorMutOps --> RawCursorMut
ListIter --> ListIterImpl
ListIterImpl --> RawIterator
RawCursor --> CommonCursor
RawCursorMut --> CommonCursor
RawIterator --> RawCursor

Cursors enable safe mutable access to list elements while maintaining the linked list invariants. The CursorMut<G> (src/linked_list.rs(L238 - L276) ) provides ownership-aware removal that properly converts raw pointers back to owned objects.

Sources: src/linked_list.rs(L139 - L142)  src/linked_list.rs(L220 - L276)  src/raw_list.rs(L104 - L106)  src/raw_list.rs(L270 - L423)  src/raw_list.rs(L433 - L464) 

API Reference

Relevant source files

This document provides comprehensive documentation of all public APIs in the linked_list_r4l crate, organized by abstraction level. The crate provides three distinct API layers: a user-friendly macro-based interface for typical usage, an advanced ownership management interface for complex scenarios, and a low-level unsafe interface for maximum performance.

For conceptual information about memory management and thread safety, see Core Concepts. For practical usage examples, see Quick Start Guide.

API Architecture Overview

The following diagram illustrates the complete API structure and relationships between the three abstraction layers:

flowchart TD
subgraph subGraph2["Low-Level API Layer (raw_list.rs)"]
    GetLinksTrait["GetLinks trait"]
    LinksStruct["Links<T> struct"]
    ListEntryStruct["ListEntry<T> struct"]
    RawListStruct["RawList<G: GetLinks>"]
    RawCursor["Cursor<G>"]
    RawCursorMut["CursorMut<G>"]
    RawIterator["Iterator<G>"]
end
subgraph subGraph1["Advanced API Layer (linked_list.rs)"]
    WrapperTrait["Wrapper<T> trait"]
    GetLinksWrappedTrait["GetLinksWrapped trait"]
    AdvancedList["List<G: GetLinksWrapped>"]
    AdvancedCursor["CursorMut<G>"]
    AdvancedIterator["Iterator<G>"]
    BoxImpl["Box<T> impl"]
    ArcImpl["Arc<T> impl"]
    RefImpl["&T impl"]
end
subgraph subGraph0["User-Friendly API Layer (lib.rs)"]
    DefNodeMacro["def_node! macro"]
    GeneratedStruct["Generated Node Struct"]
    SimpleList["List<Box<Node>>"]
end

AdvancedCursor --> RawCursorMut
AdvancedIterator --> RawIterator
AdvancedList --> RawListStruct
DefNodeMacro --> GeneratedStruct
GeneratedStruct --> SimpleList
GetLinksTrait --> LinksStruct
GetLinksWrappedTrait --> GetLinksTrait
GetLinksWrappedTrait --> WrapperTrait
LinksStruct --> ListEntryStruct
RawListStruct --> GetLinksTrait
RawListStruct --> RawCursor
RawListStruct --> RawCursorMut
RawListStruct --> RawIterator
SimpleList --> AdvancedList
WrapperTrait --> ArcImpl
WrapperTrait --> BoxImpl
WrapperTrait --> RefImpl

Sources: src/lib.rs(L1 - L179)  src/linked_list.rs(L1 - L355)  src/raw_list.rs(L1 - L596) 

Core Type Hierarchy and Trait System

This diagram shows the relationships between the main traits and types, mapping directly to code entities:

flowchart TD
subgraph subGraph3["Wrapper Implementations"]
    BoxWrapper["impl Wrapper<T> for Box<T>"]
    ArcWrapper["impl Wrapper<T> for Arc<T>"]
    RefWrapper["impl Wrapper<T> for &T"]
    BoxGetLinksWrapped["impl GetLinksWrapped for Box<T>"]
    ArcGetLinksWrapped["impl GetLinksWrapped for Arc<T>"]
end
subgraph subGraph2["Generated Types"]
    NodeStruct["Generated Node Struct"]
    NodeGetLinks["impl GetLinks for Node"]
end
subgraph subGraph1["Core Data Structures"]
    LinksT["Links<T>"]
    ListEntryT["ListEntry<T>"]
    RawListT["RawList<G>"]
    ListT["List<G>"]
end
subgraph subGraph0["Trait Definitions"]
    GetLinksT["GetLinks"]
    GetLinksWrappedT["GetLinksWrapped"]
    WrapperT["Wrapper<T>"]
end

ArcGetLinksWrapped --> GetLinksWrappedT
ArcWrapper --> WrapperT
BoxGetLinksWrapped --> GetLinksWrappedT
BoxWrapper --> WrapperT
GetLinksWrappedT --> GetLinksT
GetLinksWrappedT --> WrapperT
LinksT --> ListEntryT
ListT --> GetLinksWrappedT
ListT --> RawListT
NodeGetLinks --> GetLinksT
NodeStruct --> NodeGetLinks
RawListT --> GetLinksT
RefWrapper --> WrapperT

Sources: src/raw_list.rs(L23 - L29)  src/linked_list.rs(L18 - L89)  src/lib.rs(L11 - L107) 

User-Friendly API

def_node!Macro

The def_node! macro generates complete node types suitable for use in linked lists.

Syntax:

def_node! {
    /// Optional documentation
    [visibility] struct NodeName(inner_type);
    /// Generic version
    [visibility] struct GenericNode<T>(T);
}

Generated Interface: Each generated node type includes:

MethodDescriptionReturn Type
new(inner: T)Constructs a new nodeSelf
inner(&self)Returns reference to wrapped value&T
into_inner(self)Consumes node, returns wrapped valueT

The macro also implements GetLinks, Deref, and provides embedded Links<Self> for list operations.

Sources: src/lib.rs(L109 - L178)  src/lib.rs(L11 - L107) 

Simple List Type

For basic usage with generated nodes, the crate re-exports List from the advanced API:

pub use linked_list::List;

This allows simple usage patterns like List<Box<MyNode>> without requiring knowledge of the underlying trait system.

Sources: src/lib.rs(L6) 

Advanced API

WrapperTrait

The Wrapper<T> trait abstracts over different ownership models, enabling the same list implementation to work with Box<T>, Arc<T>, and &T.

#![allow(unused)]
fn main() {
pub trait Wrapper<T: ?Sized> {
    fn into_pointer(self) -> NonNull<T>;
    unsafe fn from_pointer(ptr: NonNull<T>) -> Self;
    fn as_ref(&self) -> &T;
}
}

Implementations:

Typeinto_pointerfrom_pointeras_ref
BoxBox::into_rawBox::from_rawAsRef::as_ref
ArcArc::into_rawArc::from_rawAsRef::as_ref
&TNonNull::fromDereference pointerIdentity

Sources: src/linked_list.rs(L18 - L83) 

GetLinksWrappedTrait

The GetLinksWrapped trait combines GetLinks functionality with wrapper ownership management:

pub trait GetLinksWrapped: GetLinks {
    type Wrapped: Wrapper<Self::EntryType>;
}

This trait is automatically implemented for Box<T> and Arc<T> when T implements GetLinks.

Sources: src/linked_list.rs(L86 - L121) 

List<G: GetLinksWrapped>

The main list type that manages owned elements through the wrapper system.

Core Methods:

MethodDescriptionSafety Requirements
new()Creates empty listNone
is_empty()Checks if list is emptyNone
push_back(data)Adds element to endNone
push_front(data)Adds element to beginningNone
pop_front()Removes and returns first elementNone
insert_after(existing, data)Inserts after existing elementexistingmust be on this list
remove(data)Removes specific elementdatamust be on this list or no list
iter()Returns iteratorNone
cursor_front_mut()Returns mutable cursorNone

Sources: src/linked_list.rs(L127 - L223) 

CursorMut<'a, G>

Provides mutable cursor functionality for advanced list manipulation:

MethodDescription
current()Returns mutable reference to current element
remove_current()Removes current element and advances cursor
peek_next()Returns reference to next element
peek_prev()Returns reference to previous element
move_next()Advances cursor to next element

Sources: src/linked_list.rs(L238 - L276) 

Iterator<'a, G>

Standard iterator implementation supporting both forward and backward iteration:

  • Implements Iterator with Item = &'a G::EntryType
  • Implements DoubleEndedIterator for reverse iteration

Sources: src/linked_list.rs(L279 - L303) 

Low-Level API

GetLinksTrait

The fundamental trait that allows any type to be stored in a linked list:

#![allow(unused)]
fn main() {
pub trait GetLinks {
    type EntryType: ?Sized;
    fn get_links(data: &Self::EntryType) -> &Links<Self::EntryType>;
}
}

Types implementing this trait must provide access to embedded Links<T> for list management.

Sources: src/raw_list.rs(L23 - L29) 

LinksStructure

The core structure embedded in list elements, managing insertion state and list pointers:

pub struct Links<T: ?Sized> {
    inserted: AtomicBool,
    entry: UnsafeCell<ListEntry<T>>,
}

Key Methods:

MethodVisibilityPurpose
new()PublicCreates new uninserted Links
acquire_for_insertion()PrivateAtomically marks as inserted
release_after_removal()PrivateAtomically marks as not inserted

The AtomicBool ensures thread-safe insertion tracking, while UnsafeCell<ListEntry<T>> holds the actual list pointers.

Sources: src/raw_list.rs(L35 - L72) 

ListEntryStructure

Internal structure containing the actual linked list pointers:

struct ListEntry<T: ?Sized> {
    next: Option<NonNull<T>>,
    prev: Option<NonNull<T>>,
}

This structure is private and managed entirely by the RawList implementation.

Sources: src/raw_list.rs(L74 - L86) 

The low-level unsafe list implementation providing maximum performance:

Core Operations:

MethodSafety RequirementsReturns
new()NoneEmpty list
is_empty()Nonebool
push_back(entry)Entry must be valid and live longer than listbool(insertion success)
push_front(entry)Entry must be valid and live longer than listbool(insertion success)
insert_after(existing, new)Both entries must be valid, existing must be on listbool(insertion success)
remove(entry)Entry must be on this list or no listbool(removal success)
pop_front()NoneOption<NonNull<G::EntryType>>

Sources: src/raw_list.rs(L93 - L284) 

Low-Level Cursors and Iterators

The raw list provides cursor and iterator types for traversal:

Cursor<'a, G> (read-only):

  • current() - Returns current element reference
  • move_next() - Advances to next element
  • move_prev() - Moves to previous element

CursorMut<'a, G> (mutable):

  • current() - Returns mutable reference to current element
  • remove_current() - Removes current element and advances
  • peek_next() / peek_prev() - Look at adjacent elements
  • move_next() - Advances cursor

Iterator<'a, G>:

  • Implements standard Iterator and DoubleEndedIterator traits
  • Used by higher-level list types

Sources: src/raw_list.rs(L340 - L464) 

Thread Safety Characteristics

All types implement Send and Sync conditionally based on their entry types:

  • RawList<G> is Send + Sync when G::EntryType: Send + Sync
  • Links<T> is Send + Sync when T: Send + Sync
  • Atomic operations in Links<T> ensure safe concurrent access

Sources: src/raw_list.rs(L40 - L46)  src/raw_list.rs(L331 - L337) 

User-Friendly API

Relevant source files

This page documents the user-friendly API layer of the linked_list_r4l crate, which provides the simplest interface for typical usage scenarios. This layer consists of the def_node! macro for automatic node generation and the simple List<T> interface for basic list operations.

For advanced ownership management and custom wrapper types, see Advanced API. For low-level unsafe operations and performance-critical usage, see Low-Level API.

The def_node! Macro

The def_node! macro is the primary entry point for users to create list-compatible node types. It automatically generates the necessary boilerplate code to make any type work with the linked list system.

Macro Syntax

The macro supports two syntax forms:

FormExampleUse Case
Simple structstruct NodeName(inner_type);Non-generic wrapped types
Generic structstruct NodeName(inner_type);Generic wrapped types

Generated Node Structure

When you invoke def_node!, it generates a complete node structure with the following components:

flowchart TD
subgraph subGraph3["List Integration"]
    ListUsage["List>"]
end
subgraph subGraph2["Generated Implementations"]
    GetLinksImpl["GetLinks implementation"]
    NodeMethods["Node methods:• new()• inner()• into_inner()"]
    DerefImpl["Deref implementation"]
end
subgraph subGraph1["Generated Node Structure"]
    NodeStruct["MyNode struct"]
    InnerField["inner: i32"]
    LinksField["links: Links"]
end
subgraph subGraph0["def_node! Macro Input"]
    UserStruct["struct MyNode(i32)"]
end

NodeStruct --> DerefImpl
NodeStruct --> GetLinksImpl
NodeStruct --> InnerField
NodeStruct --> LinksField
NodeStruct --> ListUsage
NodeStruct --> NodeMethods
UserStruct --> NodeStruct

Sources: src/lib.rs(L11 - L107)  src/lib.rs(L168 - L178) 

Node Methods

Each generated node provides these essential methods:

MethodSignaturePurpose
new()pub const fn new(inner: T) -> SelfCreates a new node wrapping the inner value
inner()pub const fn inner(&self) -> &TReturns a reference to the wrapped value
into_inner()pub fn into_inner(self) -> TConsumes the node and returns the wrapped value

The generated nodes also implement Deref, allowing direct access to the inner type's methods through the node.

Sources: src/lib.rs(L28 - L48)  src/lib.rs(L76 - L96)  src/lib.rs(L50 - L57)  src/lib.rs(L98 - L105) 

Code Generation Flow

The macro expansion process transforms user declarations into fully functional list nodes:

flowchart TD
subgraph subGraph3["Usage Ready"]
    BoxedUsage["Box"]
    ListUsage["List>"]
end
subgraph subGraph2["Generated Code Components"]
    StructDef["struct MyNode {inner: i32,links: Links}"]
    GetLinksImpl["impl GetLinks for MyNode"]
    Methods["impl MyNode {new(), inner(), into_inner()}"]
    DerefImpl["impl Deref for MyNode"]
end
subgraph subGraph1["Macro Processing"]
    DefNodeMacro["def_node! macro"]
    InternalMacro["__def_node_internal! macro"]
end
subgraph subGraph0["User Code"]
    UserDecl["def_node! {struct MyNode(i32);}"]
end

BoxedUsage --> ListUsage
DefNodeMacro --> InternalMacro
InternalMacro --> DerefImpl
InternalMacro --> GetLinksImpl
InternalMacro --> Methods
InternalMacro --> StructDef
StructDef --> BoxedUsage
UserDecl --> DefNodeMacro

Sources: src/lib.rs(L168 - L178)  src/lib.rs(L11 - L107) 

Simple List Interface

The user-friendly API provides a straightforward List<T> interface that works with any boxed node type generated by def_node!.

Basic Operations

The simple list interface supports these fundamental operations:

OperationMethodDescription
CreationList::new()Creates an empty list
Insertionpush_back(node),push_front(node)Adds nodes to the list
Removalpop_back(),pop_front()Removes and returns nodes
Iterationiter()Provides iterator over list elements

Typical Usage Pattern

Here's the standard workflow for using the user-friendly API:


Sources: src/lib.rs(L124 - L165)  README.md(L15 - L62) 

Complete Usage Example

The macro documentation includes a comprehensive example showing both simple and generic node usage:

  1. Node Definition: Using def_node! to create various node types with different visibility modifiers
  2. List Creation: Instantiating typed lists for specific node types
  3. List Operations: Adding, iterating, and removing nodes
  4. Value Access: Using inner() to access wrapped values and into_inner() to extract them

The example demonstrates the seamless integration between the macro-generated nodes and the list operations, showing how the user-friendly API hides the complexity of the underlying link management.

Sources: src/lib.rs(L124 - L165)  README.md(L46 - L61) 

Integration with Type System

The user-friendly API integrates cleanly with Rust's type system:

  • Ownership: Nodes are typically owned through Box<T> for heap allocation
  • Type Safety: The list type List<Box<NodeType>> ensures only compatible nodes can be inserted
  • Generic Support: Both the nodes and lists can be generic over inner types
  • Deref Coercion: Nodes automatically dereference to their inner type for convenient method access

This design allows users to work with linked lists using familiar Rust patterns while benefiting from the high-performance constant-time removal capabilities of the underlying implementation.

Sources: src/lib.rs(L6)  src/lib.rs(L50 - L57)  src/lib.rs(L98 - L105) 

Advanced API

Relevant source files

This document covers the advanced API layer of the linked_list_r4l crate, which provides safe ownership management and flexible pointer wrapping capabilities. This API sits between the user-friendly interface (see User-Friendly API) and the low-level unsafe operations (see Low-Level API).

The advanced API is designed for users who need control over memory ownership models while maintaining memory safety guarantees. It centers around the List<G: GetLinksWrapped> type and the Wrapper trait system that abstracts over different pointer types like Box<T>, Arc<T>, and &T.

Core Trait System

The advanced API is built on two key traits that work together to provide type-safe ownership management:

Wrapper Trait

The Wrapper<T> trait abstracts over different pointer types and ownership models, providing a uniform interface for converting between owned objects and raw pointers.

flowchart TD
WrapperTrait["Wrapper<T> trait"]
IntoPointer["into_pointer() -> NonNull<T>"]
FromPointer["from_pointer(NonNull<T>) -> Self"]
AsRef["as_ref() -> &T"]
BoxImpl["Box<T> impl"]
ArcImpl["Arc<T> impl"]
RefImpl["&T impl"]
BoxIntoRaw["Box::into_raw()"]
BoxFromRaw["Box::from_raw()"]
ArcIntoRaw["Arc::into_raw()"]
ArcFromRaw["Arc::from_raw()"]
NonNullFrom["NonNull::from()"]
PtrDeref["ptr.as_ptr() deref"]

ArcImpl --> ArcFromRaw
ArcImpl --> ArcIntoRaw
ArcImpl --> WrapperTrait
BoxImpl --> BoxFromRaw
BoxImpl --> BoxIntoRaw
BoxImpl --> WrapperTrait
RefImpl --> NonNullFrom
RefImpl --> PtrDeref
RefImpl --> WrapperTrait
WrapperTrait --> AsRef
WrapperTrait --> FromPointer
WrapperTrait --> IntoPointer

Wrapper Trait Implementations

The trait is implemented for three fundamental pointer types:

TypeOwnership ModelUse Case
BoxUnique ownershipSingle-threaded owned data
ArcShared ownershipMulti-threaded reference counting
&TBorrowed referenceTemporary list membership

Sources: src/linked_list.rs(L18 - L83) 

GetLinksWrapped Trait

The GetLinksWrapped trait extends GetLinks to specify which wrapper type should be used for list elements:

flowchart TD
GetLinksWrapped["GetLinksWrapped trait"]
GetLinks["extends GetLinks"]
WrappedType["type Wrapped: Wrapper<Self::EntryType>"]
BoxGetLinksWrapped["Box<T> impl GetLinksWrapped"]
ArcGetLinksWrapped["Arc<T> impl GetLinksWrapped"]
BoxWrapped["type Wrapped = Box<T::EntryType>"]
ArcWrapped["type Wrapped = Arc<T::EntryType>"]

ArcGetLinksWrapped --> ArcWrapped
ArcGetLinksWrapped --> GetLinksWrapped
BoxGetLinksWrapped --> BoxWrapped
BoxGetLinksWrapped --> GetLinksWrapped
GetLinksWrapped --> GetLinks
GetLinksWrapped --> WrappedType

Sources: src/linked_list.rs(L85 - L121) 

List<G: GetLinksWrapped> Interface

The List<G: GetLinksWrapped> struct provides the main interface for ownership-managed linked lists. It wraps the lower-level RawList<G> and handles ownership transfer automatically.

Core Operations


List Creation and Basic Operations

MethodPurposeSafety
new()Create empty listSafe
push_back(data)Add to endSafe, auto-cleanup on failure
push_front(data)Add to beginningSafe, auto-cleanup on failure
pop_front()Remove from beginningSafe, returns ownership
is_empty()Check if emptySafe

Sources: src/linked_list.rs(L131 - L147)  src/linked_list.rs(L149 - L177)  src/linked_list.rs(L210 - L217) 

Advanced Operations

The interface also provides more sophisticated operations for precise list manipulation:

flowchart TD
AdvancedOps["Advanced Operations"]
InsertAfter["insert_after(existing, data)"]
Remove["remove(data)"]
CursorMut["cursor_front_mut()"]
UnsafeBlock["unsafe block required"]
AsRefCall["Wrapper::as_ref(existing)"]
OwnershipReturn["Option<G::Wrapped>"]
CursorMutStruct["CursorMut<'a, G>"]
CurrentAccess["current()"]
RemoveCurrent["remove_current()"]
Navigation["move_next(), peek_next(), peek_prev()"]

AdvancedOps --> CursorMut
AdvancedOps --> InsertAfter
AdvancedOps --> Remove
CursorMut --> CursorMutStruct
CursorMutStruct --> CurrentAccess
CursorMutStruct --> Navigation
CursorMutStruct --> RemoveCurrent
InsertAfter --> AsRefCall
InsertAfter --> UnsafeBlock
Remove --> OwnershipReturn
Remove --> UnsafeBlock

Unsafe Operations Requirements

MethodSafety Requirement
insert_after(existing, data)existingmust be valid entry on this list
remove(data)datamust be on this list or no list

Sources: src/linked_list.rs(L178 - L208) 

Cursors and Iterators

The advanced API provides two mechanisms for list traversal with different capabilities:

CursorMut

The CursorMut<'a, G> provides mutable access and modification capabilities during traversal:

flowchart TD
CursorMutStruct["CursorMut<'a, G>"]
RawCursor["raw_list::CursorMut<'a, G>"]
CurrentMethod["current() -> Option<&mut G::EntryType>"]
RemoveCurrentMethod["remove_current() -> Option<G::Wrapped>"]
PeekNext["peek_next() -> Option<&mut G::EntryType>"]
PeekPrev["peek_prev() -> Option<&mut G::EntryType>"]
MoveNext["move_next()"]
OwnershipReturn["Returns owned object"]
MutableAccess["Allows mutation"]

CurrentMethod --> MutableAccess
CursorMutStruct --> CurrentMethod
CursorMutStruct --> MoveNext
CursorMutStruct --> PeekNext
CursorMutStruct --> PeekPrev
CursorMutStruct --> RawCursor
CursorMutStruct --> RemoveCurrentMethod
RemoveCurrentMethod --> OwnershipReturn

Sources: src/linked_list.rs(L237 - L276) 

Iterator

The Iterator<'a, G> provides immutable forward and backward traversal:

flowchart TD
IteratorStruct["Iterator<'a, G>"]
RawIterator["raw_list::Iterator<'a, G>"]
StdIterator["impl Iterator"]
DoubleEnded["impl DoubleEndedIterator"]
NextMethod["next() -> Option<&G::EntryType>"]
NextBackMethod["next_back() -> Option<&G::EntryType>"]
ImmutableRef["Immutable reference"]

DoubleEnded --> NextBackMethod
IteratorStruct --> DoubleEnded
IteratorStruct --> RawIterator
IteratorStruct --> StdIterator
NextBackMethod --> ImmutableRef
NextMethod --> ImmutableRef
StdIterator --> NextMethod

Sources: src/linked_list.rs(L278 - L303) 

Ownership Management

The advanced API's key strength is its automatic ownership management across different pointer types:

Ownership Transfer Flow

flowchart TD
subgraph subGraph0["Memory Safety"]
    ListStorage["List internal storage"]
    RawPointers["NonNull<T> storage"]
    AtomicOps["Atomic operations"]
end
UserCode["User Code"]
WrappedObject["G::Wrapped object"]
IntoPointer["into_pointer()"]
RemovalOp["Removal operation"]
FromPointer["from_pointer()"]
ReturnedObject["Owned object returned"]
InsertionFail["Insertion failure"]
AutoCleanup["Automatic cleanup"]

AutoCleanup --> FromPointer
FromPointer --> ReturnedObject
InsertionFail --> AutoCleanup
IntoPointer --> ListStorage
ListStorage --> RawPointers
ListStorage --> RemovalOp
RawPointers --> AtomicOps
RemovalOp --> FromPointer
UserCode --> WrappedObject
WrappedObject --> InsertionFail
WrappedObject --> IntoPointer

Ownership Transfer Guarantees

ScenarioBehaviorMemory Safety
Successful insertionOwnership transferred to listGuaranteed byinto_pointer()
Failed insertionObject automatically reconstructedEnsured byfrom_pointer()call
Successful removalOwnership returned to callerGuaranteed byfrom_pointer()
List dropAll elements properly droppedHandled byDropimplementation

Sources: src/linked_list.rs(L153 - L162)  src/linked_list.rs(L168 - L176)  src/linked_list.rs(L231 - L235) 

Drop Behavior

The List<G> implements Drop to ensure all remaining elements are properly cleaned up:

flowchart TD
DropImpl["List<G> Drop"]
PopLoop["while pop_front().is_some()"]
FromPointer["G::Wrapped::from_pointer()"]
WrapperDrop["Wrapper's Drop impl"]
BoxDrop["Box: heap deallocation"]
ArcDrop["Arc: reference count decrement"]
RefDrop["&T: no action needed"]

DropImpl --> PopLoop
FromPointer --> WrapperDrop
PopLoop --> FromPointer
WrapperDrop --> ArcDrop
WrapperDrop --> BoxDrop
WrapperDrop --> RefDrop

Sources: src/linked_list.rs(L231 - L235) 

Thread Safety Considerations

While the advanced API provides memory safety guarantees, thread safety depends on the underlying RawList implementation and the specific wrapper type used:

Wrapper TypeThread Safety Notes
BoxSingle-threaded ownership, no inherent thread safety
ArcReference counting is thread-safe, but list operations are not
&TDepends on the referenced data's thread safety

For multi-threaded usage, external synchronization (e.g., Mutex, RwLock) around the List<G> is required.

Sources: src/linked_list.rs(L50 - L66) 

Low-Level API

Relevant source files

This page documents the unsafe, low-level API provided by the RawList<G: GetLinks> interface for advanced users who need direct control over memory management and performance-critical operations. This layer operates with raw pointers and requires careful attention to memory safety invariants.

For safe ownership management and wrapper abstractions, see Advanced API. For the user-friendly macro-generated interface, see User-Friendly API.

Core Types and Traits

The low-level API is built around three primary types that work together to provide intrusive linked list functionality.

The GetLinks trait serves as the fundamental abstraction that allows any type to be stored in a linked list by providing access to embedded link fields.

flowchart TD
subgraph subGraph1["Implementation Requirements"]
    UserStruct["User Struct(contains Links field)"]
    LinksField["links: Links"]
    ImplBlock["impl GetLinksfor UserStruct"]
end
subgraph subGraph0["GetLinks Trait System"]
    GetLinksTrait["GetLinkstrait"]
    EntryType["type EntryType: ?Sized"]
    GetLinksMethod["get_links(&EntryType)-> &Links"]
end

GetLinksMethod --> LinksField
GetLinksTrait --> EntryType
GetLinksTrait --> GetLinksMethod
ImplBlock --> GetLinksTrait
UserStruct --> ImplBlock
UserStruct --> LinksField

Core GetLinks Implementation Pattern

The trait defines two key components: an associated type EntryType that represents the type of objects stored in the list, and a method get_links() that returns a reference to the embedded Links<T> structure.

Sources: src/raw_list.rs(L23 - L29) 

The Links<T> structure contains the actual linked list pointers and provides atomic insertion tracking to prevent double-insertion and enable constant-time removal.

flowchart TD
subgraph subGraph2["Atomic Operations"]
    AcquireInsert["acquire_for_insertion()compare_exchange(false, true)"]
    ReleaseRemove["release_after_removal()store(false, Release)"]
end
subgraph subGraph1["ListEntry Contents"]
    NextPtr["next: Option>"]
    PrevPtr["prev: Option>"]
end
subgraph subGraph0["Links Memory Layout"]
    LinksStruct["Links"]
    InsertedFlag["inserted: AtomicBool(tracks insertion state)"]
    EntryCell["entry: UnsafeCell>(contains pointers)"]
end

EntryCell --> NextPtr
EntryCell --> PrevPtr
InsertedFlag --> AcquireInsert
InsertedFlag --> ReleaseRemove
LinksStruct --> EntryCell
LinksStruct --> InsertedFlag

Atomic Insertion Tracking

The inserted field uses atomic compare-and-swap operations to ensure thread-safe insertion tracking. The acquire_for_insertion() method attempts to atomically change the state from false to true, preventing double-insertion.

Sources: src/raw_list.rs(L35 - L66)  src/raw_list.rs(L74 - L86) 

RawList Structure

The RawList<G: GetLinks> provides the core linked list operations using raw pointer manipulation.

flowchart TD
subgraph subGraph3["Iteration Support"]
    Iter["iter() -> Iterator"]
    CursorFront["cursor_front_mut()-> CursorMut"]
end
subgraph subGraph2["Query Operations"]
    IsEmpty["is_empty() -> bool"]
    Front["front() -> Option>"]
    Back["back() -> Option>"]
end
subgraph subGraph1["Primary Operations"]
    PushBack["push_back(&EntryType)-> bool"]
    PushFront["push_front(&EntryType)-> bool"]
    Remove["remove(&EntryType)-> bool"]
    PopFront["pop_front()-> Option>"]
    InsertAfter["insert_after(&existing, &new)-> bool"]
end
subgraph subGraph0["RawList"]
    RawListStruct["RawList"]
    HeadPtr["head: Option>"]
end

RawListStruct --> Back
RawListStruct --> CursorFront
RawListStruct --> Front
RawListStruct --> HeadPtr
RawListStruct --> InsertAfter
RawListStruct --> IsEmpty
RawListStruct --> Iter
RawListStruct --> PopFront
RawListStruct --> PushBack
RawListStruct --> PushFront
RawListStruct --> Remove

Sources: src/raw_list.rs(L88 - L284) 

List Operations

Insertion Operations

The RawList provides several insertion methods that work with raw references and return boolean values indicating success or failure.

OperationMethodSafety RequirementsReturn Value
Back Insertionpush_back(&EntryType)Reference must remain valid while on listbool- true if inserted, false if already on list
Front Insertionpush_front(&EntryType)Reference must remain valid while on listbool- true if inserted, false if already on list
Positional Insertioninsert_after(&existing, &new)Both references valid, existing must be on listbool- true if inserted, false if new already on list

Insertion Safety Model

All insertion operations use the atomic acquire_for_insertion() mechanism to prevent double-insertion. The operations are marked unsafe because the caller must ensure reference validity for the lifetime of list membership.

Sources: src/raw_list.rs(L153 - L197)  src/raw_list.rs(L135 - L151) 

Removal Operations

Removal operations provide constant-time complexity by updating surrounding pointers directly without traversal.

flowchart TD
subgraph subGraph0["Removal Process"]
    CheckInsertion["Check insertion state(entry.next != None)"]
    UpdatePointers["Update prev.nextand next.prev"]
    UpdateHead["Update head ifremoving head element"]
    ResetLinks["Reset entry.nextand entry.prev to None"]
    ReleaseState["release_after_removal()store(false, Release)"]
end

CheckInsertion --> UpdatePointers
ResetLinks --> ReleaseState
UpdateHead --> ResetLinks
UpdatePointers --> UpdateHead

Constant-Time Removal Implementation

The remove_internal() method achieves O(1) complexity by directly accessing the target element's links rather than traversing the list. The atomic release operation ensures thread-safe state management.

Sources: src/raw_list.rs(L199 - L245)  src/raw_list.rs(L247 - L257) 

Cursor System

The cursor system provides controlled traversal and modification capabilities for the linked list.

Cursor Types

Cursor TypeMutabilityCapabilities
Cursor<'a, G>ImmutableRead-only traversal, element inspection
CursorMut<'a, G>MutableTraversal, element modification, removal

Cursor Operations

flowchart TD
subgraph subGraph2["Mutable Cursor"]
    CursorMut["CursorMut<'a, G>"]
    CurrentMut["current() -> Option<&mut EntryType>"]
    RemoveCurrent["remove_current()-> Option>"]
    PeekNext["peek_next() -> Option<&mut EntryType>"]
    PeekPrev["peek_prev() -> Option<&mut EntryType>"]
end
subgraph subGraph1["Read-Only Cursor"]
    Cursor["Cursor<'a, G>"]
    Current["current() -> Option<&EntryType>"]
end
subgraph subGraph0["Cursor Navigation"]
    CommonCursor["CommonCursor"]
    MoveNext["move_next(&RawList)"]
    MovePrev["move_prev(&RawList)"]
    CurrentPos["cur: Option>"]
end

CommonCursor --> CurrentPos
CommonCursor --> MoveNext
CommonCursor --> MovePrev
Cursor --> CommonCursor
Cursor --> Current
CursorMut --> CommonCursor
CursorMut --> CurrentMut
CursorMut --> PeekNext
CursorMut --> PeekPrev
CursorMut --> RemoveCurrent

Cursor Safety Model

Cursors maintain lifetime relationships with the underlying list to prevent use-after-free scenarios. The mutable cursor provides safe removal operations that automatically advance the cursor position.

Sources: src/raw_list.rs(L339 - L423)  src/raw_list.rs(L286 - L329) 

Iterator Implementation

The iterator system provides both forward and backward traversal capabilities.

Iterator Structure

flowchart TD
subgraph subGraph2["IntoIterator Support"]
    IntoIter["impl IntoIterator for &RawListinto_iter() -> Iterator"]
end
subgraph subGraph1["Iterator Traits"]
    StdIterator["impl Iteratornext() -> Option<&EntryType>"]
    DoubleEnded["impl DoubleEndedIteratornext_back() -> Option<&EntryType>"]
end
subgraph subGraph0["Iterator Implementation"]
    IteratorStruct["Iterator<'a, G>"]
    CursorFront["cursor_front: Cursor<'a, G>"]
    CursorBack["cursor_back: Cursor<'a, G>"]
end

IntoIter --> IteratorStruct
IteratorStruct --> CursorBack
IteratorStruct --> CursorFront
IteratorStruct --> DoubleEnded
IteratorStruct --> StdIterator

Bidirectional Iteration

The iterator uses two cursors to enable efficient bidirectional traversal, supporting both Iterator and DoubleEndedIterator traits for compatibility with standard Rust iteration patterns.

Sources: src/raw_list.rs(L434 - L464)  src/raw_list.rs(L425 - L431) 

Memory Layout and Safety Invariants

Thread Safety Model

The RawList implements thread safety through atomic operations and careful ordering constraints.

ComponentThread Safety MechanismOrdering
Insertion TrackingAtomicBoolwith compare-exchangeAcquireon insert,Relaxedon failure
Removal StateAtomicBoolstore operationReleaseordering
Pointer AccessUnsafeCellwith controlled accessSynchronized through insertion state

Safety Invariants

  1. Link Ownership: Once successfully inserted, the list owns the links until removal
  2. Reference Validity: Callers must ensure references remain valid while on the list
  3. Single List Membership: An element can only be on one list at a time (enforced by atomic insertion tracking)
  4. Removal Safety: Only elements currently on the list may be removed

Sources: src/raw_list.rs(L40 - L46)  src/raw_list.rs(L331 - L337) 

Memory Layout Diagram

flowchart TD
subgraph subGraph3["List Head"]
    RawListHead["RawList.headOption>"]
end
subgraph subGraph2["ListEntry Pointers"]
    NextPointer["next: Option>(8 bytes on 64-bit)"]
    PrevPointer["prev: Option>(8 bytes on 64-bit)"]
end
subgraph subGraph1["Links Internal Layout"]
    AtomicBool["inserted: AtomicBool(1 byte + padding)"]
    UnsafeCell["entry: UnsafeCell(contains raw pointers)"]
end
subgraph subGraph0["Node Memory Structure"]
    UserData["User Data Fields(application-specific)"]
    LinksField["links: Links"]
end

LinksField --> AtomicBool
LinksField --> UnsafeCell
NextPointer --> UserData
PrevPointer --> UserData
RawListHead --> UserData
UnsafeCell --> NextPointer
UnsafeCell --> PrevPointer
UserData --> LinksField

Sources: src/raw_list.rs(L35 - L38)  src/raw_list.rs(L74 - L77)  src/raw_list.rs(L93 - L95) 

Core Concepts

Relevant source files

This page provides a deep dive into the fundamental concepts that enable the linked_list_r4l crate to provide intrusive linked lists with constant-time arbitrary removal and thread safety. It covers the trait system, memory layout, atomic operations, and safety architecture that form the foundation of the library.

For basic usage patterns, see Quick Start Guide. For detailed API documentation, see API Reference. For memory management specifics, see Memory Management. For thread safety implementation details, see Thread Safety.

The core of the linked list architecture is built around two key traits that enable intrusive linking while maintaining type safety and flexible ownership models.

The GetLinks trait is the fundamental abstraction that allows any type to participate in a linked list by providing access to embedded linking metadata.

flowchart TD
GetLinks["GetLinks trait"]
EntryType["type EntryType: ?Sized"]
GetLinksMethod["fn get_links(&EntryType) -> &Links"]
UserStruct["User-defined struct"]
LinksField["links: Links"]
UserImpl["impl GetLinks for UserStruct"]

GetLinks --> EntryType
GetLinks --> GetLinksMethod
GetLinksMethod --> LinksField
UserImpl --> GetLinks
UserStruct --> LinksField
UserStruct --> UserImpl

GetLinks Trait Definition and Usage

The trait defines two essential components:

  • EntryType: The type of objects that will be stored in the list
  • get_links(): A function that returns a reference to the Links struct embedded in the entry

This design enables a type to be in multiple different lists simultaneously by implementing GetLinks multiple times with different associated types.

GetLinksWrapped Trait

The GetLinksWrapped trait extends GetLinks to add ownership management through the Wrapper trait, enabling safe memory management across different allocation strategies.

flowchart TD
GetLinksWrapped["GetLinksWrapped trait"]
GetLinksBase["extends GetLinks"]
WrappedType["type Wrapped: Wrapper"]
BoxImpl["impl GetLinksWrapped for Box"]
ArcImpl["impl GetLinksWrapped for Arc"]
BoxWrapper["Box: Wrapper"]
ArcWrapper["Arc: Wrapper"]
RefWrapper["&T: Wrapper"]
Wrapper["Wrapper trait"]
IntoPtr["fn into_pointer(self) -> NonNull"]
FromPtr["unsafe fn from_pointer(NonNull) -> Self"]
AsRef["fn as_ref(&self) -> &T"]

ArcImpl --> ArcWrapper
ArcImpl --> GetLinksWrapped
ArcWrapper --> Wrapper
BoxImpl --> BoxWrapper
BoxImpl --> GetLinksWrapped
BoxWrapper --> Wrapper
GetLinksWrapped --> GetLinksBase
GetLinksWrapped --> WrappedType
RefWrapper --> Wrapper
Wrapper --> AsRef
Wrapper --> FromPtr
Wrapper --> IntoPtr

Trait Relationship and Implementation

Sources: src/raw_list.rs(L23 - L29)  src/linked_list.rs(L86 - L96)  src/linked_list.rs(L18 - L31) 

The Links struct contains the core linking infrastructure that enables intrusive list membership with atomic insertion tracking.

Memory Layout and Components

flowchart TD
subgraph subGraph2["ListEntry Structure"]
    ListEntry["ListEntry"]
    Next["next: Option>"]
    Prev["prev: Option>"]
end
subgraph subGraph1["Links Structure"]
    Links["Links"]
    Inserted["inserted: AtomicBool"]
    Entry["entry: UnsafeCell>"]
end
subgraph subGraph0["Node Memory Layout"]
    Node["User Node Struct"]
    UserData["user data fields"]
    LinksField["links: Links"]
end

Entry --> ListEntry
Links --> Entry
Links --> Inserted
LinksField --> Links
ListEntry --> Next
ListEntry --> Prev
Node --> LinksField
Node --> UserData

Links Structure Components

The Links<T> struct contains two critical fields:

  1. inserted: AtomicBool - Atomic flag tracking whether the node is currently inserted in any list
  2. entry: UnsafeCell<ListEntry<T>> - Contains the actual next/prev pointers wrapped in UnsafeCell for interior mutability

The ListEntry<T> struct contains the doubly-linked list pointers:

  • next: Option<NonNull<T>> - Pointer to the next node in the list
  • prev: Option<NonNull<T>> - Pointer to the previous node in the list

Atomic Insertion Tracking

The atomic insertion flag provides thread-safe tracking of list membership and prevents double-insertion:

OperationAtomic OperationMemory Ordering
Insertioncompare_exchange(false, true, Acquire, Relaxed)Acquire on success
Removalstore(false, Release)Release
Checkload(Relaxed)Relaxed

Sources: src/raw_list.rs(L35 - L66)  src/raw_list.rs(L74 - L86) 

Constant-Time Arbitrary Removal

The intrusive design enables O(1) removal of any node from the list without requiring traversal to find the node's neighbors.

Removal Algorithm


Key Algorithmic Properties

  1. No Traversal Required: Because each node contains its own links, removal requires no list traversal
  2. Atomic Safety: The inserted flag prevents concurrent modifications during removal
  3. Neighbor Updates: Only the immediate neighbors need pointer updates
  4. Head Management: Special handling when removing the head node

The algorithm handles three cases:

  • Single node: Set head = None
  • Head node: Update head to point to next node
  • Interior node: Update neighbor pointers only

Insertion Tracking and Race Prevention


Race Condition Prevention

The atomic inserted flag prevents several race conditions:

  • Double insertion: Only one thread can successfully set inserted from false to true
  • Insertion during removal: Removal always succeeds once a node is inserted
  • Memory ordering: Acquire on insertion synchronizes with Release on removal

Sources: src/raw_list.rs(L57 - L65)  src/raw_list.rs(L199 - L235)  src/raw_list.rs(L140 - L151) 

Memory Safety Architecture

The library implements a layered safety model that transitions from safe ownership management to unsafe pointer operations.

Safety Boundary and Wrapper Abstraction

flowchart TD
subgraph subGraph2["Unsafe Zone"]
    NonNullPtrs["NonNull"]
    RawList["RawList operations"]
    PtrArithmetic["Pointer manipulation"]
end
subgraph subGraph1["Wrapper Boundary"]
    WrapperTrait["Wrapper trait"]
    IntoPtr["into_pointer() -> NonNull"]
    FromPtr["from_pointer(NonNull) -> Self"]
    AsRef["as_ref() -> &T"]
end
subgraph subGraph0["Safe Zone"]
    UserCode["User Code"]
    BoxOwned["Box"]
    ArcShared["Arc"]
    RefBorrowed["&Node"]
end

ArcShared --> WrapperTrait
BoxOwned --> WrapperTrait
FromPtr --> NonNullPtrs
IntoPtr --> NonNullPtrs
NonNullPtrs --> RawList
RawList --> PtrArithmetic
RefBorrowed --> WrapperTrait
UserCode --> ArcShared
UserCode --> BoxOwned
UserCode --> RefBorrowed
WrapperTrait --> AsRef
WrapperTrait --> FromPtr
WrapperTrait --> IntoPtr

Ownership Model Abstraction

The Wrapper trait abstracts over different ownership models:

Wrapper TypeOwnership ModelUse Case
BoxUnique ownershipSingle-threaded exclusive access
ArcShared ownershipMulti-threaded shared access
&TBorrowed referenceStack-allocated or external lifetime management

Safe-to-Unsafe Transition

The transition from safe to unsafe code follows a specific protocol:

  1. Entry: Safe wrapper objects are converted to raw pointers via into_pointer()
  2. Operation: Unsafe RawList operations manipulate the raw pointers
  3. Exit: Raw pointers are converted back to safe wrappers via from_pointer()

This ensures that:

  • Memory safety is maintained at the boundaries
  • Ownership transfer is explicit and controlled
  • Unsafe operations are isolated to the core implementation

Sources: src/linked_list.rs(L18 - L83)  src/linked_list.rs(L86 - L89)  src/linked_list.rs(L127 - L162) 

Cursors and Iteration

The library provides cursor-based iteration that enables both inspection and mutation of list elements during traversal.

Cursor Implementation Architecture

flowchart TD
subgraph subGraph2["List Integration"]
    RawList["**RawList"]
    ListHead["head: Option>"]
    CircularLinks["Circular next/prev pointers"]
end
subgraph subGraph1["Common Operations"]
    CommonCursor["**CommonCursor"]
    MoveNext["move_next()"]
    MovePrev["move_prev()"]
    Current["cur: Option>"]
end
subgraph subGraph0["Cursor Types"]
    Cursor["Cursor<'a, G>"]
    CursorMut["CursorMut<'a, G>"]
    Iterator["Iterator<'a, G>"]
end

CommonCursor --> Current
CommonCursor --> MoveNext
CommonCursor --> MovePrev
Cursor --> CommonCursor
CursorMut --> CommonCursor
Iterator --> Cursor
MoveNext --> RawList
MovePrev --> RawList
RawList --> CircularLinks
RawList --> ListHead

Cursor Navigation Logic

Cursors navigate the circular list structure by:

  1. Forward movement: Follow next pointers until reaching the head again
  2. Backward movement: Follow prev pointers until reaching the head again
  3. Boundary detection: Stop iteration when returning to the starting position

The circular structure simplifies navigation logic and eliminates special cases for list boundaries.

Mutable Cursor Operations

CursorMut provides additional operations for list modification during iteration:

OperationDescriptionSafety Requirements
current()Get mutable reference to current elementElement must remain valid
remove_current()Remove current element and advance cursorReturns ownership of removed element
peek_next()Get mutable reference to next elementNo cursor advancement
peek_prev()Get mutable reference to previous elementNo cursor advancement

Sources: src/raw_list.rs(L286 - L329)  src/raw_list.rs(L339 - L423)  src/raw_list.rs(L433 - L464) 

Memory Management

Relevant source files

This document explains how the linked_list_r4l crate manages memory safety, ownership models, and the boundary between safe and unsafe code. It covers the ownership abstraction through the Wrapper trait, atomic memory operations, and the embedded links structure that enables constant-time removal. For information about thread safety and atomic operations, see Thread Safety.

Ownership Models and the Wrapper Trait

The library supports multiple ownership models through the Wrapper<T> trait, which abstracts over different ways of managing object lifetime. This allows the same linked list implementation to work with heap-allocated objects, reference-counted objects, and borrowed references.

Wrapper Trait Implementation

classDiagram
class Wrapper~T~ {
    <<trait>>
    
    +into_pointer() NonNull~T~
    +from_pointer(NonNull~T~) Self
    +as_ref() &T
}

class BoxUnsupported markdown: del {
    
    +into_pointer() NonNull~T~
    +from_pointer(NonNull~T~) Box~T~
    +as_ref() &T
}

class ArcUnsupported markdown: del {
    
    +into_pointer() NonNull~T~
    +from_pointer(NonNull~T~) Arc~T~
    +as_ref() &T
}

class &T {
    
    +into_pointer() NonNull~T~
    +from_pointer(NonNull~T~) &T
    +as_ref() &T
}

Wrapper  ..|>  Wrapper
Wrapper  ..|>  Wrapper
Wrapper  ..|>  Wrapper

The Wrapper<T> trait provides three key operations:

  • into_pointer() converts owned objects to raw pointers for list storage
  • from_pointer() reconstructs ownership when removing from the list
  • as_ref() provides safe access without transferring ownership

Sources: src/linked_list.rs(L18 - L31)  src/linked_list.rs(L33 - L83) 

Ownership Transfer Flow

sequenceDiagram
    participant UserCode as "User Code"
    participant ListG as "List~G~"
    participant WrapperT as "Wrapper~T~"
    participant RawListG as "RawList~G~"

    Note over UserCode,RawListG: Insertion Flow - Ownership Transfer
    UserCode ->> ListG: "push_back(owned_object)"
    ListG ->> WrapperT: "into_pointer()"
    WrapperT -->> ListG: "NonNull~T~"
    ListG ->> RawListG: "push_back(&T)"
    Note over RawListG: "Stores raw pointer,<br>ownership transferred"
    Note over UserCode,RawListG: Removal Flow - Ownership Recovery
    UserCode ->> ListG: "pop_front()"
    ListG ->> RawListG: "pop_front()"
    RawListG -->> ListG: "Some(NonNull~T~)"
    ListG ->> WrapperT: "from_pointer(ptr)"
    WrapperT -->> ListG: "Owned Object"
    ListG -->> UserCode: "Some(owned_object)"

This flow ensures that ownership is properly transferred to the list during insertion and recovered during removal, preventing memory leaks and use-after-free errors.

Sources: src/linked_list.rs(L153 - L162)  src/linked_list.rs(L213 - L217) 

Each node in the linked list contains an embedded Links<T> structure that manages the actual list pointers and insertion state. This design enables constant-time arbitrary removal without requiring traversal.

Node Memory Layout

flowchart TD
subgraph NodeMemory["Node Memory Layout"]
    subgraph LinksStruct["LinksUnsupported markdown: del Structure"]
        NotInserted["false: Not on any list"]
        Inserted["true: Currently on a list"]
        NextPtr["next: OptionUnsupported markdown: delT~~"]
        InnerField["inner: T"]
        InsertedFlag["inserted: AtomicBool"]
        EntryCell["entry: UnsafeCellUnsupported markdown: delT~~"]
    end
end
subgraph ListEntryMemory["ListEntryUnsupported markdown: del Memory"]
    NotInserted["false: Not on any list"]
    NextPtr["next: OptionUnsupported markdown: delT~~"]
    PrevPtr["prev: OptionUnsupported markdown: delT~~"]
    InnerField["inner: T"]
    subgraph AtomicStates["Atomic States"]
        Inserted["true: Currently on a list"]
        subgraph UserData["User Data"]
            NotInserted["false: Not on any list"]
            NextPtr["next: OptionUnsupported markdown: delT~~"]
            InnerField["inner: T"]
        end
    end
end

The Links<T> structure contains:

  • inserted: An AtomicBool tracking whether the node is currently on a list
  • entry: An UnsafeCell<ListEntry<T>> containing the actual forward/backward pointers

Sources: src/raw_list.rs(L35 - L38)  src/raw_list.rs(L74 - L86) 

The GetLinks trait provides access to the embedded Links<T> structure within user-defined types:

classDiagram
class GetLinks~T~ {
    <<trait>>
    +type EntryType
    +get_links(data: &EntryType) &Links~EntryType~
}

class UserNode {
    +inner: SomeType
    +links: Links~Self~
    
}

class Links~T~ {
    +inserted: AtomicBool
    +entry: UnsafeCell~ListEntry~T~~
    
}

class ListEntry~T~ {
    +next: Option~NonNull~T~~
    +prev: Option~NonNull~T~~
    
}

Links  ..|>  UserNode : implements
UserNode  *--  Links : contains
Links  *--  ListEntry : contains

Sources: src/raw_list.rs(L23 - L29)  src/raw_list.rs(L35 - L55) 

Safety Boundaries and Abstraction Layers

The library maintains memory safety through a carefully designed abstraction hierarchy that isolates unsafe operations to the lowest layer.

Safety Layer Architecture

flowchart TD
subgraph UnsafeZone["Unsafe Zone - Raw Pointer Operations"]
    RawListOps["RawListUnsupported markdown: delRaw pointer operations"]
    AtomicOps["Atomic Links Management"]
    PtrArithmetic["Pointer arithmetic & linking"]
end
subgraph SafeZone["Safe Zone - Memory Ownership"]
    UserAPI["User CodeListUnsupported markdown: del API"]
    OwnershipMgmt["Ownership ManagementListUnsupported markdown: del"]
    WrapperBoundary["Wrapper Trait Boundary"]
end

OwnershipMgmt --> WrapperBoundary
RawListOps --> AtomicOps
RawListOps --> PtrArithmetic
UserAPI --> OwnershipMgmt
WrapperBoundary --> RawListOps

Memory Safety Guarantees

The safety boundaries provide these guarantees:

LayerSafety MechanismGuarantees
User APIRust ownership systemNo use-after-free, no double-free
Wrapper BoundaryControlled pointer conversionOwnership tracking across boundaries
Raw OperationsUnsafe blocks with invariantsPointer validity maintained
Atomic OperationsMemory ordering constraintsThread-safe state transitions

Sources: src/linked_list.rs(L127 - L235)  src/raw_list.rs(L97 - L284) 

Atomic Memory Management

The Links<T> structure uses atomic operations to prevent double-insertion and ensure thread-safe state transitions.

Atomic Insertion Protocol


The atomic protocol ensures:

  • Only one thread can successfully insert a node at a time
  • Nodes cannot be inserted twice without being removed first
  • Memory ordering prevents reordering of pointer updates

Sources: src/raw_list.rs(L57 - L65) 

Atomic Operations Implementation

The key atomic methods in Links<T>:

#![allow(unused)]
fn main() {
// From src/raw_list.rs:57-65
fn acquire_for_insertion(&self) -> bool {
    self.inserted
        .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
        .is_ok()
}

fn release_after_removal(&self) {
    self.inserted.store(false, Ordering::Release);
}
}
  • acquire_for_insertion() uses compare-and-swap with Acquire ordering
  • release_after_removal() uses Release ordering for proper synchronization
  • Failed insertion attempts indicate the node is already on a list

Sources: src/raw_list.rs(L57 - L65) 

Raw Pointer Operations and Memory Invariants

The RawList<G> layer performs all raw pointer manipulation while maintaining critical memory safety invariants.

RawList Memory Invariants


Critical Memory Operations

The most complex memory operations involve updating multiple pointers atomically:

OperationMemory UpdatesSafety Requirements
push_back()Update head, link new nodeNode not already inserted
insert_after()Update 3 nodes' pointersExisting node on this list
remove()Update 2 neighbors, reset nodeNode on this list
pop_front()Update head, unlink nodeList not empty

Each operation maintains the circular doubly-linked structure while ensuring that partially-updated states are never visible to other threads.

Sources: src/raw_list.rs(L113 - L284) 

Thread Safety

Relevant source files

This document covers the thread safety mechanisms, atomic operations, and concurrent access patterns implemented by the linked_list_r4l library. The focus is on how the library uses atomic operations to ensure safe concurrent insertion and removal operations while maintaining performance.

For information about memory management and ownership models, see Memory Management. For details about the overall architecture and abstraction layers, see Architecture Overview.

Atomic Insertion Tracking

The core thread safety mechanism in linked_list_r4l is the atomic insertion tracking system implemented in the Links<T> struct. This system prevents data races when multiple threads attempt to insert or remove the same node concurrently.

Insertion State Management

The Links<T> struct contains an AtomicBool inserted field that tracks whether a node is currently inserted in any list:


Atomic State Transitions in Links

The insertion tracking prevents double-insertion and ensures that only one thread can successfully insert a node at a time. The acquire_for_insertion() method uses compare-and-swap semantics to atomically transition from false to true, while release_after_removal() unconditionally sets the state back to false.

Sources: src/raw_list.rs(L35 - L66) 

Compare-and-Swap Operations

The atomic operations use specific memory ordering to ensure correctness:

OperationMemory OrderingPurpose
compare_exchange(false, true, Acquire, Relaxed)AcquireSynchronizes with release operations from other threads
store(false, Release)ReleaseEnsures all preceding writes are visible to other threads

The Acquire ordering on successful insertion ensures that any subsequent operations on the node happen-after the insertion completes. The Release ordering on removal ensures that all modifications to the node's links are visible before the node becomes available for re-insertion.

Sources: src/raw_list.rs(L57 - L65) 

Thread Safety Boundaries

The library implements thread safety through a layered approach with explicit unsafe boundaries:

flowchart TD
subgraph subGraph2["Boundary Layer"]
    Links["Links<T>"]
    SendSync["Send + Sync Implementations"]
end
subgraph subGraph1["Unsafe Zone"]
    UnsafeCell["UnsafeCell<ListEntry>"]
    RawPointers["NonNull<T> Raw Pointers"]
    UnsafeMethods["Unsafe Method Implementations"]
end
subgraph subGraph0["Safe Zone"]
    SafeAPI["Safe API Methods"]
    AtomicOps["Atomic Operations"]
end

AtomicOps --> Links
Links --> SendSync
Links --> UnsafeCell
RawPointers --> UnsafeMethods
SafeAPI --> Links
SendSync --> UnsafeCell
UnsafeCell --> RawPointers

Thread Safety Architecture

The Links<T> struct serves as the boundary between safe atomic operations and unsafe raw pointer manipulation. The UnsafeCell<ListEntry<T>> provides interior mutability for the actual linked list pointers while the atomic boolean provides synchronization.

Sources: src/raw_list.rs(L35 - L46)  src/raw_list.rs(L331 - L337) 

Send and Sync Implementations

The library provides conditional Send and Sync implementations that propagate thread safety requirements:

// SAFETY: Links can be safely sent to other threads but we restrict it to being Send
// only when the list entries it points to are also Send.
unsafe impl<T: ?Sized> Send for Links<T> {}

// SAFETY: Links is usable from other threads via references but we restrict it to being Sync  
// only when the list entries it points to are also Sync.
unsafe impl<T: ?Sized> Sync for Links<T> {}

RawList Thread Safety

// SAFETY: The list is itself can be safely sent to other threads but we restrict it to being Send
// only when its entries are also Send.
unsafe impl<G: GetLinks> Send for RawList<G> where G::EntryType: Send {}

// SAFETY: The list is itself usable from other threads via references but we restrict it to being Sync
// only when its entries are also Sync.
unsafe impl<G: GetLinks> Sync for RawList<G> where G::EntryType: Sync {}

These implementations ensure that thread safety is only available when the contained data types are themselves thread-safe, following Rust's standard library patterns.

Sources: src/raw_list.rs(L40 - L46)  src/raw_list.rs(L331 - L337) 

Concurrent Access Patterns

Safe Concurrent Operations


Concurrent Insertion Protocol

The atomic insertion tracking ensures that even if multiple threads attempt to insert the same node simultaneously, only one will succeed. The losing threads receive a false return value and can handle the case appropriately.

Sources: src/raw_list.rs(L140 - L151)  src/raw_list.rs(L153 - L179) 

Unsafe Raw Pointer Operations

While the insertion tracking is thread-safe, the actual pointer manipulation within RawList operations is not thread-safe. The library relies on the caller to ensure exclusive access to the list structure during modifications:

MethodThread SafetyRequirements
push_back()Not thread-safeRequires exclusive access to&mut RawList
remove()Not thread-safeRequires exclusive access to&mut RawList
iter()Thread-safeCan be called with shared reference&RawList

The mutable reference requirement (&mut RawList<G>) for modification operations provides compile-time guarantees that no other thread can access the list structure during modifications.

Sources: src/raw_list.rs(L186 - L197)  src/raw_list.rs(L243 - L245) 

Memory Ordering Guarantees

The library uses acquire-release semantics to establish happens-before relationships:

flowchart TD
subgraph subGraph1["Thread B"]
    B1["release_after_removal()"]
    B2["Release ordering"]
    B3["observe node data changes"]
end
subgraph subGraph0["Thread A"]
    A1["modify node data"]
    A2["acquire_for_insertion()"]
    A3["Acquire ordering"]
end

A1 --> A2
A2 --> A3
A3 --> B2
B1 --> B3
B2 --> B1

Memory Ordering Relationships

The acquire ordering on insertion ensures that any writes to the node data that occurred before the insertion become visible to threads that subsequently observe the insertion. The release ordering on removal ensures that all modifications to the node are visible before the node becomes available for re-use.

Sources: src/raw_list.rs(L57 - L65) 

Safety Invariants

Insertion Tracking Invariants

  1. Single Insertion: A node with inserted == true is guaranteed to be in exactly one list
  2. Atomic Transitions: State changes between inserted/not-inserted are atomic and visible to all threads
  3. Exclusive Modification: Only the thread that successfully acquires for insertion can modify the node's list links

Raw Pointer Safety

  1. Validity Duration: Raw pointers in ListEntry<T> are valid only while the corresponding nodes remain in the list
  2. Exclusive List Access: Modification operations require exclusive access to prevent data races on the list structure
  3. Node Lifetime: Nodes must outlive their presence in the list to prevent use-after-free

Sources: src/raw_list.rs(L88 - L95)  src/raw_list.rs(L199 - L235) 

The thread safety model of linked_list_r4l provides atomic insertion tracking while requiring higher-level synchronization for list structure modifications. This design enables efficient constant-time removal while maintaining memory safety in concurrent environments.

Development Guide

Relevant source files

Purpose and Scope

This guide provides essential information for developers contributing to the linked_list_r4l crate. It covers build setup, testing procedures, CI/CD pipeline configuration, and project structure. For information about the library's APIs and usage patterns, see API Reference. For architectural concepts and design principles, see Architecture Overview and Core Concepts.

Prerequisites and Environment Setup

The linked_list_r4l crate requires specific toolchain components and supports multiple target architectures for embedded and systems programming use cases.

Required Toolchain

ComponentVersionPurpose
Rust ToolchainnightlyRequired for advanced features
rust-srcLatestSource code for cross-compilation
clippyLatestLinting and code analysis
rustfmtLatestCode formatting

Supported Target Architectures

The project supports multiple target architectures as defined in the CI pipeline:

flowchart TD
subgraph subGraph1["Development Activities"]
    UnitTests["Unit Testingcargo test"]
    CrossBuild["Cross Compilationcargo build"]
    Clippy["Static Analysiscargo clippy"]
    Format["Code Formattingcargo fmt"]
end
subgraph subGraph0["Supported Targets"]
    HostTarget["x86_64-unknown-linux-gnu\Host Development\"]
    BareMetal["x86_64-unknown-none\Bare Metal x86_64\"]
    RiscV["riscv64gc-unknown-none-elf\RISC-V Embedded\"]
    ARM["aarch64-unknown-none-softfloat\ARM64 Embedded\"]
end

ARM --> CrossBuild
BareMetal --> CrossBuild
Clippy --> Format
CrossBuild --> Clippy
HostTarget --> CrossBuild
HostTarget --> UnitTests
RiscV --> CrossBuild
UnitTests --> Clippy

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L15 - L19) 

Project Structure

The linked_list_r4l crate follows a standard Rust library structure with specialized CI/CD configuration for embedded systems development.

Repository Layout

flowchart TD
subgraph subGraph2["Build Outputs"]
    DocDir["target/doc/\Generated Documentation\"]
    BuildDir["target/[target]/\Compiled Artifacts\"]
end
subgraph subGraph1["GitHub Integration"]
    WorkflowsDir[".github/workflows/\CI/CD Configuration\"]
    CIYml["ci.yml\Build Pipeline\"]
    GHPages["gh-pages\Documentation Deployment\"]
end
subgraph subGraph0["Root Directory"]
    CargoToml["Cargo.toml\Package Configuration\"]
    GitIgnore[".gitignore\Version Control\"]
    SrcDir["src/\Source Code\"]
    TargetDir["target/\Build Artifacts\"]
end

CIYml --> DocDir
CIYml --> TargetDir
CargoToml --> SrcDir
DocDir --> GHPages
TargetDir --> BuildDir
WorkflowsDir --> CIYml

Sources: Cargo.toml(L1 - L15)  .gitignore(L1 - L5)  .github/workflows/ci.yml(L1) 

Build System Configuration

The crate is configured as a no-std library with no external dependencies, making it suitable for embedded environments.

Package Metadata

The project configuration emphasizes systems programming and embedded compatibility:

PropertyValueSignificance
namelinked_list_r4lCrate identifier
version0.2.1Current release version
edition2021Rust edition with latest features
categories["no-std", "rust-patterns"]Embedded and pattern library
licenseGPL-2.0-or-laterOpen source license

Dependency Management

flowchart TD
subgraph subGraph2["Internal Modules"]
    LibRS["lib.rs\Public API\"]
    LinkedListRS["linked_list.rs\High-Level Interface\"]
    RawListRS["raw_list.rs\Low-Level Operations\"]
end
subgraph subGraph1["Standard Library"]
    NoStd["#![no_std]\Core Library Only\"]
    CoreCrate["core::\Essential Types\"]
end
subgraph subGraph0["External Dependencies"]
    NoDeps["[dependencies]\No External Crates\"]
end

CoreCrate --> LibRS
LibRS --> LinkedListRS
LibRS --> RawListRS
NoDeps --> NoStd
NoStd --> CoreCrate

Sources: Cargo.toml(L14 - L15)  Cargo.toml(L12) 

Testing Framework

The testing infrastructure supports both unit testing and cross-compilation verification across multiple target architectures.

Test Execution Strategy

sequenceDiagram
    participant Developer as "Developer"
    participant CIPipeline as "CI Pipeline"
    participant BuildMatrix as "Build Matrix"
    participant x86_64unknownlinuxgnu as "x86_64-unknown-linux-gnu"
    participant EmbeddedTargets as "Embedded Targets"

    Developer ->> CIPipeline: "Push/PR Trigger"
    CIPipeline ->> BuildMatrix: "Initialize Build Matrix"
    Note over BuildMatrix,EmbeddedTargets: Parallel Execution
    BuildMatrix ->> x86_64unknownlinuxgnu: "cargo test --target x86_64-unknown-linux-gnu"
    BuildMatrix ->> EmbeddedTargets: "cargo build --target [embedded-target]"
    x86_64unknownlinuxgnu ->> x86_64unknownlinuxgnu: "Run Unit Tests with --nocapture"
    EmbeddedTargets ->> EmbeddedTargets: "Verify Compilation Only"
    x86_64unknownlinuxgnu -->> CIPipeline: "Test Results"
    EmbeddedTargets -->> CIPipeline: "Build Verification"
    CIPipeline -->> Developer: "Pipeline Status"

Test Configuration

Unit tests are executed only on the host target (x86_64-unknown-linux-gnu) while other targets verify compilation compatibility:

# Host target with unit tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

# Cross-compilation verification
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features  
cargo build --target aarch64-unknown-none-softfloat --all-features

Sources: .github/workflows/ci.yml(L28 - L30)  .github/workflows/ci.yml(L26 - L27) 

CI/CD Pipeline Architecture

The continuous integration system implements a comprehensive quality assurance workflow with parallel job execution and automated documentation deployment.

Pipeline Workflow

flowchart TD
subgraph subGraph3["Documentation Pipeline"]
    DocBuild["cargo doc --no-deps --all-features"]
    DocDeploy["JamesIves/github-pages-deploy-action@v4"]
    GHPages["GitHub Pages Deployment"]
end
subgraph subGraph2["Quality Gates"]
    Checkout["actions/checkout@v4"]
    Setup["dtolnay/rust-toolchain@nightly"]
    Format["cargo fmt --all -- --check"]
    Clippy["cargo clippy --target TARGET --all-features"]
    Build["cargo build --target TARGET --all-features"]
    Test["cargo test --target TARGET -- --nocapture"]
end
subgraph subGraph1["CI Job Matrix"]
    Toolchain["Rust Toolchain: nightly"]
    Target1["x86_64-unknown-linux-gnu"]
    Target2["x86_64-unknown-none"]
    Target3["riscv64gc-unknown-none-elf"]
    Target4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Trigger Events"]
    PushEvent["git push"]
    PREvent["Pull Request"]
end

Build --> Test
Checkout --> Setup
Clippy --> Build
DocBuild --> DocDeploy
DocDeploy --> GHPages
Format --> Clippy
PREvent --> Toolchain
PushEvent --> Toolchain
Setup --> Format
Target1 --> Checkout
Target1 --> DocBuild
Target2 --> Checkout
Target3 --> Checkout
Target4 --> Checkout
Toolchain --> Target1
Toolchain --> Target2
Toolchain --> Target3
Toolchain --> Target4

Pipeline Configuration Details

StageCommandTarget FilterPurpose
Format Checkcargo fmt --all -- --checkAll targetsCode style consistency
Lintingcargo clippy --target TARGET --all-featuresAll targetsStatic analysis
Buildcargo build --target TARGET --all-featuresAll targetsCompilation verification
Testingcargo test --target TARGET -- --nocaptureHost onlyUnit test execution
Documentationcargo doc --no-deps --all-featuresDefaultAPI documentation

Sources: .github/workflows/ci.yml(L5 - L31)  .github/workflows/ci.yml(L32 - L55) 

Code Quality Standards

The project enforces strict code quality standards through automated tooling and custom configuration.

Linting Configuration

The clippy configuration includes specific allowances for library design patterns:

cargo clippy --target TARGET --all-features -- -A clippy::new_without_default

This configuration allows constructors that don't implement Default, which is appropriate for specialized data structures like linked lists.

Documentation Standards

Documentation generation includes strict validation:

RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"

This configuration:

  • Treats broken documentation links as compilation errors (-D rustdoc::broken_intra_doc_links)
  • Requires documentation for all public APIs (-D missing-docs)

Documentation Deployment

flowchart TD
subgraph subGraph1["Deployment Process"]
    BranchCheck["Check: refs/heads/default_branch"]
    SingleCommit["single-commit: true"]
    GHPagesBranch["branch: gh-pages"]
    TargetDoc["folder: target/doc"]
end
subgraph subGraph0["Documentation Build"]
    CargoDoc["cargo doc --no-deps --all-features"]
    IndexGen["Generate index.html redirect"]
    TreeCmd["cargo tree | head -1 | cut -d' ' -f1"]
end

BranchCheck --> SingleCommit
CargoDoc --> IndexGen
GHPagesBranch --> TargetDoc
IndexGen --> BranchCheck
SingleCommit --> GHPagesBranch
TreeCmd --> IndexGen

Sources: .github/workflows/ci.yml(L25)  .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L55) 

Contributing Workflow

Development Environment Setup

  1. Install Rust Nightly Toolchain
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt
  1. Add Target Architectures
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
  1. Verify Installation
rustc --version --verbose

Pre-commit Validation

Before submitting changes, run the complete validation suite locally:

# Format check
cargo fmt --all -- --check

# Linting
cargo clippy --all-features -- -A clippy::new_without_default

# Cross-compilation verification
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features

# Unit tests
cargo test -- --nocapture

# Documentation build
cargo doc --no-deps --all-features

This local validation mirrors the CI pipeline and helps catch issues before pushing to the repository.

Sources: .github/workflows/ci.yml(L20 - L30)  .github/workflows/ci.yml(L44 - L47) 

Overview

Relevant source files

The arm_pl011 crate provides type-safe register definitions and basic operations for the ARM PL011 UART controller. This library serves as a hardware abstraction layer for embedded systems and operating system kernels that need to interface with PL011 UART hardware found in ARM-based systems.

The crate is designed for no_std environments and integrates with the ArceOS operating system ecosystem, though it can be used independently in any Rust embedded project requiring PL011 UART support. For detailed hardware specifications and register layouts, see Hardware Reference. For specific implementation details, see Core Implementation.

System Context

The following diagram illustrates how arm_pl011 fits within the broader embedded systems software stack:

System Integration Context

flowchart TD
subgraph subGraph2["Hardware Layer"]
    MMIO["Memory-mapped I/O"]
    PL011_HW["PL011 UART Controller"]
    SerialPort["Physical Serial Port"]
end
subgraph subGraph1["Hardware Abstraction"]
    arm_pl011["arm_pl011 crate"]
    Pl011Uart["Pl011Uart struct"]
    tock_registers["tock-registers"]
end
subgraph subGraph0["Application Layer"]
    UserApp["User Applications"]
    Kernel["ArceOS Kernel"]
    Bootloader["Bootloader"]
end

Bootloader --> arm_pl011
Kernel --> arm_pl011
MMIO --> PL011_HW
PL011_HW --> SerialPort
Pl011Uart --> tock_registers
UserApp --> arm_pl011
arm_pl011 --> Pl011Uart
tock_registers --> MMIO

Sources: Cargo.toml(L8 - L12)  README.md(L7 - L9) 

Crate Structure and Code Entities

The crate follows a minimal, focused design with clear separation between public API and internal implementation:

Code Organization and Key Entities

flowchart TD
subgraph subGraph3["Build Targets"]
    linux_gnu["x86_64-unknown-linux-gnu"]
    x86_bare["x86_64-unknown-none"]
    riscv64["riscv64gc-unknown-none-elf"]
    aarch64["aarch64-unknown-none-softfloat"]
end
subgraph Dependencies["Dependencies"]
    tock_registers_dep["tock-registers = 0.8"]
    no_std["#![no_std]"]
    const_features["const_option, const_nonnull_new"]
end
subgraph subGraph1["Core Implementation"]
    pl011_mod["src/pl011.rs"]
    Pl011Uart_struct["Pl011Uart struct"]
    Pl011UartRegs["Pl011UartRegs struct"]
    methods["UART methods"]
end
subgraph subGraph0["Public API"]
    lib_rs["src/lib.rs"]
    Pl011Uart_export["pub use Pl011Uart"]
end

Pl011Uart_struct --> Pl011UartRegs
Pl011Uart_struct --> methods
lib_rs --> Pl011Uart_export
lib_rs --> aarch64
lib_rs --> const_features
lib_rs --> linux_gnu
lib_rs --> no_std
lib_rs --> pl011_mod
lib_rs --> riscv64
lib_rs --> x86_bare
pl011_mod --> Pl011Uart_struct
pl011_mod --> tock_registers_dep

Sources: src/lib.rs(L1 - L8)  Cargo.toml(L14 - L15) 

Key Features

The arm_pl011 crate provides the following core capabilities:

FeatureDescriptionCode Entity
Type-safe register accessMemory-mapped register operations with compile-time safetytock-registersintegration
UART initializationHardware setup and configurationPl011Uart::init()
Character I/OBlocking send/receive operationsputchar(),getchar()
Interrupt managementInterrupt status checking and acknowledgmentis_receive_interrupt(),ack_interrupts()
Multi-target supportCross-compilation for embedded and hosted environmentsno_stdcompatibility
Const operationsCompile-time initialization supportconst_option,const_nonnull_new

Sources: Cargo.toml(L1 - L12)  src/lib.rs(L2 - L3) 

Target Use Cases

The crate is designed for several specific embedded development scenarios:

  • Operating System Kernels: Provides console and debug output for kernel development, particularly within the ArceOS ecosystem
  • Bootloaders: Early-stage system initialization requiring minimal dependencies and no_std compatibility
  • Embedded Applications: Direct hardware control in bare-metal ARM systems with PL011 UART controllers
  • Cross-platform Development: Testing and development on hosted systems before deployment to embedded targets

The multi-target build support enables development workflows that span from Linux-based development environments to bare-metal embedded deployment, making it suitable for both prototyping and production use.

Sources: Cargo.toml(L8 - L12)  README.md(L1 - L9) 

Architecture

Relevant source files

This page describes the architectural design of the arm_pl011 crate, covering how it abstracts the PL011 UART hardware controller into a safe, type-checked Rust interface. The architecture demonstrates a layered approach that bridges low-level hardware registers to high-level embedded system interfaces.

For specific implementation details of UART operations, see 2.2. For hardware register specifications, see 5.

Architectural Overview

The arm_pl011 crate implements a three-layer architecture that provides safe access to PL011 UART hardware through memory-mapped I/O registers.

System Architecture

flowchart TD
subgraph subGraph3["Hardware Layer"]
    MMIO["Memory-Mapped I/O"]
    PL011Hardware["PL011 UART Controller"]
end
subgraph subGraph2["Register Abstraction"]
    TockRegs["tock-registers"]
    RegisterStructs["register_structs! macro"]
    TypeSafety["ReadWrite, ReadOnly"]
end
subgraph subGraph1["arm_pl011 Crate"]
    LibEntry["lib.rs"]
    Pl011Uart["Pl011Uart struct"]
    UartMethods["UART Methodsinit(), putchar(), getchar()"]
    Pl011UartRegs["Pl011UartRegs struct"]
end
subgraph subGraph0["Application Layer"]
    App["Application Code"]
    OS["Operating System / ArceOS"]
end

App --> LibEntry
LibEntry --> Pl011Uart
MMIO --> PL011Hardware
OS --> LibEntry
Pl011Uart --> Pl011UartRegs
Pl011Uart --> UartMethods
Pl011UartRegs --> RegisterStructs
RegisterStructs --> TockRegs
TockRegs --> TypeSafety
TypeSafety --> MMIO

Sources: src/lib.rs(L1 - L9)  src/pl011.rs(L9 - L32)  src/pl011.rs(L42 - L44)  Cargo.toml(L14 - L15) 

Core Components

The architecture centers around two primary structures that encapsulate hardware access and provide safe interfaces.

Component Relationships

flowchart TD
subgraph subGraph2["Register Definition Layer"]
    Pl011UartRegsStruct["Pl011UartRegs struct"]
    DrReg["dr: ReadWrite"]
    FrReg["fr: ReadOnly"]
    CrReg["cr: ReadWrite"]
    ImscReg["imsc: ReadWrite"]
    IcrReg["icr: WriteOnly"]
    RegsMethod["regs() -> &Pl011UartRegs"]
end
subgraph subGraph1["Implementation Layer"]
    Pl011Mod["pl011.rs module"]
    Pl011UartStruct["Pl011Uart struct"]
    BasePointer["base: NonNull"]
    UartNew["new(base: *mut u8)"]
    UartInit["init()"]
    UartPutchar["putchar(c: u8)"]
    UartGetchar["getchar() -> Option"]
    UartIsReceiveInterrupt["is_receive_interrupt() -> bool"]
    UartAckInterrupts["ack_interrupts()"]
end
subgraph subGraph0["Public API Layer"]
    LibRs["lib.rs"]
    PublicApi["pub use pl011::Pl011Uart"]
end

BasePointer --> Pl011UartRegsStruct
LibRs --> PublicApi
Pl011UartRegsStruct --> CrReg
Pl011UartRegsStruct --> DrReg
Pl011UartRegsStruct --> FrReg
Pl011UartRegsStruct --> IcrReg
Pl011UartRegsStruct --> ImscReg
Pl011UartStruct --> BasePointer
Pl011UartStruct --> UartAckInterrupts
Pl011UartStruct --> UartGetchar
Pl011UartStruct --> UartInit
Pl011UartStruct --> UartIsReceiveInterrupt
Pl011UartStruct --> UartNew
Pl011UartStruct --> UartPutchar
PublicApi --> Pl011UartStruct
RegsMethod --> Pl011UartRegsStruct
UartAckInterrupts --> RegsMethod
UartGetchar --> RegsMethod
UartInit --> RegsMethod
UartIsReceiveInterrupt --> RegsMethod
UartPutchar --> RegsMethod

Sources: src/lib.rs(L6 - L8)  src/pl011.rs(L42 - L44)  src/pl011.rs(L49 - L103)  src/pl011.rs(L9 - L32) 

Register Abstraction Model

The crate uses the tock-registers library to provide type-safe, zero-cost abstractions over hardware registers. This approach ensures compile-time verification of register access patterns.

Register Memory Layout

flowchart TD
subgraph subGraph2["Type Safety Layer"]
    ReadWriteType["ReadWrite"]
    ReadOnlyType["ReadOnly"]
    WriteOnlyType["WriteOnly"]
end
subgraph subGraph1["Memory Space"]
    BaseAddr["Base Address(base: NonNull)"]
    subgraph subGraph0["Register Offsets"]
        DR["0x00: drReadWrite"]
        Reserved0["0x04-0x14: _reserved0"]
        FR["0x18: frReadOnly"]
        Reserved1["0x1c-0x2c: _reserved1"]
        CR["0x30: crReadWrite"]
        IFLS["0x34: iflsReadWrite"]
        IMSC["0x38: imscReadWrite"]
        RIS["0x3c: risReadOnly"]
        MIS["0x40: misReadOnly"]
        ICR["0x44: icrWriteOnly"]
        End["0x48: @END"]
    end
end

BaseAddr --> CR
BaseAddr --> DR
BaseAddr --> FR
BaseAddr --> ICR
BaseAddr --> IMSC
CR --> ReadWriteType
DR --> ReadWriteType
FR --> ReadOnlyType
ICR --> WriteOnlyType
IMSC --> ReadWriteType

Sources: src/pl011.rs(L9 - L32)  src/pl011.rs(L51 - L54)  src/pl011.rs(L57 - L59) 

Memory Safety Architecture

The architecture implements several safety mechanisms to ensure correct hardware access in embedded environments.

Safety MechanismImplementationPurpose
Type Safetytock-registerstypesPrevents invalid register access patterns
Pointer SafetyNonNullGuarantees non-null base address
Const Constructionconst fn new()Enables compile-time initialization
Thread SafetySend + SynctraitsAllows multi-threaded access
Memory Layoutregister_structs!macroEnforces correct register offsets

Sources: src/pl011.rs(L46 - L47)  src/pl011.rs(L51 - L55)  src/pl011.rs(L9 - L32) 

Data Flow Architecture

flowchart TD
subgraph subGraph3["Interrupt Flow"]
    InterruptCheck["is_receive_interrupt()"]
    MisRead["Read mis register"]
    AckCall["ack_interrupts()"]
    IcrWrite["Write to icr register"]
end
subgraph subGraph2["Read Operation Flow"]
    GetcharCall["uart.getchar()"]
    RxFifoCheck["Check fr register bit 4"]
    DrRead["Read from dr register"]
    ReturnOption["Return Option"]
end
subgraph subGraph1["Write Operation Flow"]
    PutcharCall["uart.putchar(byte)"]
    TxFifoCheck["Check fr register bit 5"]
    DrWrite["Write to dr register"]
end
subgraph subGraph0["Initialization Flow"]
    UserCode["User Code"]
    NewCall["Pl011Uart::new(base_ptr)"]
    InitCall["uart.init()"]
    RegisterSetup["Register Configuration"]
end

AckCall --> IcrWrite
DrRead --> ReturnOption
GetcharCall --> RxFifoCheck
InitCall --> RegisterSetup
InterruptCheck --> MisRead
NewCall --> InitCall
PutcharCall --> TxFifoCheck
RxFifoCheck --> DrRead
TxFifoCheck --> DrWrite
UserCode --> AckCall
UserCode --> GetcharCall
UserCode --> InterruptCheck
UserCode --> NewCall
UserCode --> PutcharCall

Sources: src/pl011.rs(L64 - L76)  src/pl011.rs(L79 - L82)  src/pl011.rs(L85 - L91)  src/pl011.rs(L94 - L102) 

Integration Points

The architecture provides clean integration points for embedded systems and operating system kernels:

  • Const Construction: The new() function is const, enabling static initialization in bootloaders and kernels
  • No-std Compatibility: All code operates without standard library dependencies
  • Zero-cost Abstractions: Register access compiles to direct memory operations
  • Multi-target Support: Architecture works across x86, ARM64, and RISC-V platforms

The design allows the crate to function as a foundational component in embedded systems, providing reliable UART functionality while maintaining the performance characteristics required for system-level programming.

Sources: src/lib.rs(L1 - L3)  src/pl011.rs(L51)  Cargo.toml(L12 - L13) 

Getting Started

Relevant source files

This page provides a practical guide for integrating and using the arm_pl011 crate in your embedded systems project. It covers adding the crate as a dependency, basic initialization, and fundamental UART operations.

For detailed hardware register specifications, see Hardware Reference. For comprehensive API documentation, see API Reference.

Prerequisites

The arm_pl011 crate is designed for embedded systems development with the following requirements:

RequirementDetails
Rust Edition2021 or later
Target Environmentno_stdcompatible
HardwareARM-based system with PL011 UART controller
Memory ManagementAccess to memory-mapped I/O addresses
Dependenciestock-registersfor type-safe register access

Target Architecture Support:

  • aarch64-unknown-none-softfloat (primary target)
  • x86_64-unknown-linux-gnu (development/testing)
  • riscv64gc-unknown-none-elf (cross-platform compatibility)

Sources: Cargo.toml(L1 - L16) 

Adding the Crate to Your Project

Add arm_pl011 to your Cargo.toml dependencies:

[dependencies]
arm_pl011 = "0.1.0"

The crate automatically includes the tock-registers dependency for register manipulation safety.

Sources: Cargo.toml(L14 - L15) 

Basic Usage Flow

Initialization and Operation Sequence

flowchart TD
Start["Start"]
GetBase["Obtain UART Base Address"]
NewUart["Pl011Uart::new(base_ptr)"]
InitUart["uart.init()"]
Ready["UART Ready for Operations"]
TxPath["Transmit Path"]
RxPath["Receive Path"]
IntPath["Interrupt Path"]
Putchar["uart.putchar(byte)"]
TxDone["Character Transmitted"]
Getchar["uart.getchar()"]
CheckRx["Data Available?"]
RxSuccess["Character Received"]
RxEmpty["No Data"]
CheckInt["uart.is_receive_interrupt()"]
AckInt["uart.ack_interrupts()"]
IntHandled["Interrupt Processed"]

AckInt --> IntHandled
CheckInt --> AckInt
CheckRx --> RxEmpty
CheckRx --> RxSuccess
GetBase --> NewUart
Getchar --> CheckRx
InitUart --> Ready
IntPath --> CheckInt
NewUart --> InitUart
Putchar --> TxDone
Ready --> IntPath
Ready --> RxPath
Ready --> TxPath
RxPath --> Getchar
Start --> GetBase
TxPath --> Putchar

Sources: src/pl011.rs(L51 - L103) 

Code Entity Mapping

flowchart TD
Hardware["PL011 Hardware Controller"]
BaseAddr["Memory-mapped Base Address"]
NewMethod["Pl011Uart::new(base: *mut u8)"]
UartStruct["Pl011Uart struct"]
RegStruct["Pl011UartRegs"]
InitMethod["init()"]
PutcharMethod["putchar(c: u8)"]
GetcharMethod["getchar() -> Option"]
IntCheckMethod["is_receive_interrupt()"]
AckMethod["ack_interrupts()"]
DataReg["dr: ReadWrite"]
FlagReg["fr: ReadOnly"]
ControlReg["cr: ReadWrite"]
IntMaskReg["imsc: ReadWrite"]
IntClearReg["icr: WriteOnly"]

BaseAddr --> NewMethod
Hardware --> BaseAddr
NewMethod --> UartStruct
RegStruct --> ControlReg
RegStruct --> DataReg
RegStruct --> FlagReg
RegStruct --> IntClearReg
RegStruct --> IntMaskReg
UartStruct --> AckMethod
UartStruct --> GetcharMethod
UartStruct --> InitMethod
UartStruct --> IntCheckMethod
UartStruct --> PutcharMethod
UartStruct --> RegStruct

Sources: src/pl011.rs(L42 - L44)  src/pl011.rs(L11 - L32)  src/pl011.rs(L49 - L103) 

Simple Usage Examples

Basic UART Setup

use arm_pl011::Pl011Uart;

// Obtain the base address for your PL011 UART
// This is platform-specific and typically provided by your BSP
let uart_base = 0x0900_0000 as *mut u8;

// Create UART instance
let mut uart = Pl011Uart::new(uart_base);

// Initialize the UART with default settings
uart.init();

Character Transmission

The putchar method blocks until the transmit FIFO has space:

// Send a single character
uart.putchar(b'H');

// Send a string
for byte in b"Hello, World!\n" {
    uart.putchar(*byte);
}

Character Reception

The getchar method returns immediately with an Option:

// Check for received data
match uart.getchar() {
    Some(byte) => {
        // Process received byte
        uart.putchar(byte); // Echo back
    }
    None => {
        // No data available
    }
}

Interrupt Handling

// Check if receive interrupt occurred
if uart.is_receive_interrupt() {
    // Handle the interrupt
    if let Some(byte) = uart.getchar() {
        // Process received data
    }
    
    // Clear interrupts
    uart.ack_interrupts();
}

Sources: src/pl011.rs(L51 - L55)  src/pl011.rs(L64 - L76)  src/pl011.rs(L79 - L82)  src/pl011.rs(L85 - L91)  src/pl011.rs(L94 - L102) 

Memory Safety Considerations

The Pl011Uart struct implements Send and Sync traits, enabling safe usage across thread boundaries:

Safety FeatureImplementation
Memory SafetyUsesNonNullfor guaranteed non-null pointers
Thread SafetyManualSend + Syncimplementation for multi-threaded environments
Register Safetytock-registersprovides compile-time type safety for register access

Sources: src/pl011.rs(L46 - L47)  src/pl011.rs(L43) 

Configuration Details

The init() method configures the UART with these default settings:

SettingValueRegisterPurpose
FIFO Trigger1/8 leveliflsInterrupt timing
RX InterruptEnabledimscReceive notifications
UART EnableYescrOverall operation
TX EnableYescrTransmission capability
RX EnableYescrReception capability

Sources: src/pl011.rs(L64 - L76) 

Next Steps

After completing basic setup:

  1. Advanced Configuration: See UART Operations for FIFO management and custom interrupt handling
  2. Register Details: See Register Definitions for low-level register manipulation
  3. Thread Safety: See Thread Safety and Memory Safety for multi-threaded usage patterns
  4. Hardware Integration: See Hardware Reference for platform-specific considerations

For comprehensive method documentation, proceed to Pl011Uart Methods.

Sources: src/pl011.rs(L1 - L104)  Cargo.toml(L1 - L16) 

Core Implementation

Relevant source files

This document covers the core implementation details of the Pl011Uart driver, including the main driver struct, register abstraction layer, memory safety mechanisms, and fundamental design patterns. This page focuses on the internal architecture and implementation strategies rather than usage examples.

For detailed register specifications and hardware mappings, see Register Definitions. For comprehensive method documentation and API usage, see API Reference.

Pl011Uart Structure and Architecture

The core driver implementation centers around the Pl011Uart struct, which serves as the primary interface for all UART operations. The driver follows a layered architecture that separates hardware register access from high-level UART functionality.

Core Driver Architecture

flowchart TD
App["Application Code"]
Pl011Uart["Pl011Uart struct"]
base["base: NonNull"]
regs_method["regs() method"]
Pl011UartRegs["Pl011UartRegs struct"]
dr["dr: ReadWrite"]
fr["fr: ReadOnly"]
cr["cr: ReadWrite"]
imsc["imsc: ReadWrite"]
icr["icr: WriteOnly"]
others["ifls, ris, mis registers"]
Hardware["Memory-mapped Hardware Registers"]

App --> Pl011Uart
Pl011Uart --> base
Pl011Uart --> regs_method
Pl011UartRegs --> cr
Pl011UartRegs --> dr
Pl011UartRegs --> fr
Pl011UartRegs --> icr
Pl011UartRegs --> imsc
Pl011UartRegs --> others
cr --> Hardware
dr --> Hardware
fr --> Hardware
icr --> Hardware
imsc --> Hardware
others --> Hardware
regs_method --> Pl011UartRegs

Sources: src/pl011.rs(L34 - L44)  src/pl011.rs(L9 - L32) 

The Pl011Uart struct maintains a single field base of type NonNull<Pl011UartRegs> that points to the memory-mapped register structure. This design provides type-safe access to hardware registers while maintaining zero-cost abstractions.

Register Access Pattern

flowchart TD
Method["UART Method"]
regs_call["self.regs()"]
unsafe_deref["unsafe { self.base.as_ref() }"]
RegisterStruct["&Pl011UartRegs"]
register_access["register.get() / register.set()"]
MMIO["Memory-mapped I/O"]

Method --> regs_call
RegisterStruct --> register_access
register_access --> MMIO
regs_call --> unsafe_deref
unsafe_deref --> RegisterStruct

Sources: src/pl011.rs(L57 - L59) 

The regs() method provides controlled access to the register structure through a const function that dereferences the NonNull pointer. This pattern encapsulates the unsafe memory access within a single, well-defined boundary.

Memory Safety and Thread Safety

The driver implements explicit safety markers to enable usage in multi-threaded embedded environments while maintaining Rust's safety guarantees.

Safety TraitImplementationPurpose
Sendunsafe impl Send for Pl011Uart {}Allows transfer between threads
Syncunsafe impl Sync for Pl011Uart {}Allows shared references across threads

Sources: src/pl011.rs(L46 - L47) 

These unsafe implementations are justified because:

  • The PL011 UART controller is designed for single-threaded access per instance
  • Memory-mapped I/O operations are atomic at the hardware level
  • The NonNull pointer provides guaranteed non-null access
  • Register operations through tock_registers are inherently safe

Constructor and Initialization Pattern

The driver follows a two-phase initialization pattern that separates object construction from hardware configuration.

Construction Phase

flowchart TD
base_ptr["*mut u8"]
new_method["Pl011Uart::new()"]
nonnull_new["NonNull::new(base).unwrap()"]
cast_operation[".cast()"]
Pl011Uart_instance["Pl011Uart { base }"]

base_ptr --> new_method
cast_operation --> Pl011Uart_instance
new_method --> nonnull_new
nonnull_new --> cast_operation

Sources: src/pl011.rs(L51 - L55) 

The new() constructor is marked as const fn, enabling compile-time initialization in embedded contexts. The constructor performs type casting from a raw byte pointer to the structured register layout.

Hardware Initialization Sequence

The init() method configures the hardware through a specific sequence of register operations:

StepRegisterOperationPurpose
1icrset(0x7ff)Clear all pending interrupts
2iflsset(0)Set FIFO trigger levels (1/8 depth)
3imscset(1 << 4)Enable receive interrupts
4crset((1 << 0) | (1 << 8) | (1 << 9))Enable TX, RX, and UART

Sources: src/pl011.rs(L64 - L76) 

Data Flow and Operation Methods

The driver implements fundamental UART operations through direct register manipulation with appropriate status checking.

Character Transmission Flow

flowchart TD
putchar_call["putchar(c: u8)"]
tx_ready_check["while fr.get() & (1 << 5) != 0"]
dr_write["dr.set(c as u32)"]
hardware_tx["Hardware transmission"]

dr_write --> hardware_tx
putchar_call --> tx_ready_check
tx_ready_check --> dr_write

Sources: src/pl011.rs(L79 - L82) 

The transmission method implements busy-waiting on the Transmit FIFO Full flag (bit 5) in the Flag Register before writing data.

Character Reception Flow

flowchart TD
getchar_call["getchar()"]
rx_empty_check["fr.get() & (1 << 4) == 0"]
read_data["Some(dr.get() as u8)"]
no_data["None"]
return_char["Return Option"]

getchar_call --> rx_empty_check
no_data --> return_char
read_data --> return_char
rx_empty_check --> no_data
rx_empty_check --> read_data

Sources: src/pl011.rs(L85 - L91) 

The reception method checks the Receive FIFO Empty flag (bit 4) and returns Option<u8> to handle the absence of available data without blocking.

Interrupt Handling Architecture

The driver provides interrupt status checking and acknowledgment methods that integrate with higher-level interrupt management systems.

Interrupt Status Detection

flowchart TD
is_receive_interrupt["is_receive_interrupt()"]
mis_read["mis.get()"]
mask_check["pending & (1 << 4) != 0"]
bool_result["bool"]

is_receive_interrupt --> mis_read
mask_check --> bool_result
mis_read --> mask_check

Sources: src/pl011.rs(L94 - L97) 

The interrupt detection reads the Masked Interrupt Status register and specifically checks for receive interrupts (bit 4).

Interrupt Acknowledgment

The ack_interrupts() method clears all interrupt conditions by writing to the Interrupt Clear Register with a comprehensive mask value 0x7ff, ensuring no stale interrupt states remain.

Sources: src/pl011.rs(L100 - L102) 

This implementation pattern provides a clean separation between interrupt detection, handling, and acknowledgment, enabling integration with various interrupt management strategies in embedded operating systems.

Register Definitions

Relevant source files

This document details the PL011 UART register definitions implemented in the arm_pl011 crate, including the memory-mapped register structure, individual register specifications, and the type-safe abstractions provided by the tock-registers library. This covers the hardware abstraction layer that maps PL011 UART controller registers to Rust data structures.

For information about how these registers are used in UART operations, see UART Operations. For the complete API methods that interact with these registers, see Pl011Uart Methods.

Register Structure Overview

The PL011 UART registers are defined using the register_structs! macro from the tock-registers crate, which provides compile-time memory layout verification and type-safe register access. The Pl011UartRegs structure maps the complete PL011 register set to their hardware-defined memory offsets.

Register Memory Layout

flowchart TD
subgraph subGraph0["Pl011UartRegs Structure"]
    DR["dr: ReadWrite0x00 - Data Register"]
    RES0["_reserved00x04-0x17"]
    FR["fr: ReadOnly0x18 - Flag Register"]
    RES1["_reserved10x1C-0x2F"]
    CR["cr: ReadWrite0x30 - Control Register"]
    IFLS["ifls: ReadWrite0x34 - Interrupt FIFO Level"]
    IMSC["imsc: ReadWrite0x38 - Interrupt Mask Set Clear"]
    RIS["ris: ReadOnly0x3C - Raw Interrupt Status"]
    MIS["mis: ReadOnly0x40 - Masked Interrupt Status"]
    ICR["icr: WriteOnly0x44 - Interrupt Clear"]
    END["@END0x48"]
end

CR --> IFLS
DR --> RES0
FR --> RES1
ICR --> END
IFLS --> IMSC
IMSC --> RIS
MIS --> ICR
RES0 --> FR
RES1 --> CR
RIS --> MIS

Sources: src/pl011.rs(L9 - L32) 

Individual Register Definitions

The register structure defines eight functional registers with specific access patterns and purposes:

RegisterOffsetTypeDescription
dr0x00ReadWriteData Register - transmit/receive data
fr0x18ReadOnlyFlag Register - UART status flags
cr0x30ReadWriteControl Register - UART configuration
ifls0x34ReadWriteInterrupt FIFO Level Select Register
imsc0x38ReadWriteInterrupt Mask Set Clear Register
ris0x3CReadOnlyRaw Interrupt Status Register
mis0x40ReadOnlyMasked Interrupt Status Register
icr0x44WriteOnlyInterrupt Clear Register

Data Register (dr)

The Data Register at offset 0x00 provides bidirectional data access for character transmission and reception. It supports both read and write operations for handling UART data flow.

Flag Register (fr)

The Flag Register at offset 0x18 provides read-only status information about the UART state, including transmit/receive FIFO status and busy indicators.

Control Register (cr)

The Control Register at offset 0x30 configures UART operational parameters including enable/disable states for transmission, reception, and the overall UART controller.

Sources: src/pl011.rs(L9 - L32) 

Type Safety and Memory Mapping

The register definitions leverage the tock-registers library to provide compile-time guarantees about register access patterns and memory safety:

Register Access Type Safety

flowchart TD
subgraph subGraph2["Compile-time Safety"]
    TYPE_CHECK["Type Checking"]
    ACCESS_CONTROL["Access Control"]
    MEMORY_LAYOUT["Memory Layout Verification"]
end
subgraph subGraph1["Tock-Registers Traits"]
    READABLE["Readable Interface"]
    WRITEABLE["Writeable Interface"]
end
subgraph subGraph0["Access Patterns"]
    RO["ReadOnlyfr, ris, mis"]
    RW["ReadWritedr, cr, ifls, imsc"]
    WO["WriteOnlyicr"]
end

ACCESS_CONTROL --> MEMORY_LAYOUT
READABLE --> TYPE_CHECK
RO --> READABLE
RW --> READABLE
RW --> WRITEABLE
TYPE_CHECK --> MEMORY_LAYOUT
WO --> WRITEABLE
WRITEABLE --> ACCESS_CONTROL

The register_structs! macro ensures that:

  • Register offsets match PL011 hardware specifications
  • Reserved memory regions are properly handled
  • Access patterns prevent invalid operations (e.g., reading write-only registers)
  • Memory layout is validated at compile time

Sources: src/pl011.rs(L3 - L7)  src/pl011.rs(L9 - L32) 

Register Access Implementation

The Pl011Uart structure contains a NonNull<Pl011UartRegs> pointer that provides safe access to the memory-mapped registers. The regs() method returns a reference to the register structure for performing hardware operations.

Register Access Flow

flowchart TD
subgraph subGraph2["Hardware Interface"]
    MMIO["Memory-Mapped I/O"]
    PL011_HW["PL011 Hardware"]
end
subgraph subGraph1["Register Operations"]
    READ["Register Read Operations"]
    WRITE["Register Write Operations"]
    SET["Register Set Operations"]
    GET["Register Get Operations"]
end
subgraph subGraph0["Pl011Uart Structure"]
    BASE["base: NonNull"]
    REGS_METHOD["regs() -> &Pl011UartRegs"]
end

BASE --> REGS_METHOD
GET --> MMIO
MMIO --> PL011_HW
READ --> SET
REGS_METHOD --> READ
REGS_METHOD --> WRITE
SET --> MMIO
WRITE --> GET

The implementation ensures memory safety through:

  • Const construction via NonNull::new().unwrap().cast()
  • Unsafe register access contained within safe method boundaries
  • Type-safe register operations through tock-registers interfaces

Sources: src/pl011.rs(L42 - L59) 

UART Operations

Relevant source files

This document covers the core UART operations provided by the Pl011Uart implementation, including initialization, character transmission and reception, status monitoring, and interrupt handling. These operations form the fundamental interface for communicating with PL011 UART hardware controllers.

For details about the underlying register structure and memory layout, see Register Definitions. For complete API documentation and method signatures, see Pl011Uart Methods.

Operation Categories

The Pl011Uart struct provides five main categories of operations implemented through distinct methods that directly manipulate hardware registers:

Operation CategoryMethodsPurpose
Initializationinit()Configure UART for operation
Character Outputputchar()Transmit single characters
Character Inputgetchar()Receive single characters
Interrupt Detectionis_receive_interrupt()Check for receive interrupts
Interrupt Handlingack_interrupts()Clear pending interrupts

Initialization Process

init() Method Operation Flow

flowchart TD
subgraph subGraph0["Register Operations"]
    ICR["icr: Interrupt Clear Register"]
    IFLS["ifls: FIFO Level Select"]
    IMSC["imsc: Interrupt Mask"]
    CR["cr: Control Register"]
end
Start["init() called"]
ClearIRQ["Clear all interruptsicr.set(0x7ff)"]
SetFIFO["Set FIFO trigger levelsifls.set(0)"]
EnableRxInt["Enable RX interruptimsc.set(1 << 4)"]
EnableUART["Enable UART componentscr.set(tx|rx|uart enable)"]
Complete["UART ready for operation"]

ClearIRQ --> SetFIFO
EnableRxInt --> EnableUART
EnableUART --> Complete
SetFIFO --> EnableRxInt
Start --> ClearIRQ

The init() method src/pl011.rs(L64 - L76)  performs a four-step hardware configuration sequence:

  1. Interrupt Clearing: Sets icr register to 0x7ff to clear all pending interrupts
  2. FIFO Configuration: Sets ifls register to 0 for 1/8 RX FIFO and 1/8 TX FIFO trigger levels
  3. Interrupt Enabling: Sets imsc register bit 4 to enable receive interrupts (rxim)
  4. UART Activation: Sets cr register bits 0, 8, and 9 to enable transmission, reception, and overall UART operation

Sources: src/pl011.rs(L61 - L76) 

Character Transmission

putchar() Operation Flow

flowchart TD
subgraph subGraph0["Register Access"]
    FR["fr: Flag Register (Read-Only)"]
    DR["dr: Data Register (Read-Write)"]
end
PutChar["putchar(c: u8) called"]
CheckTXFull["Poll fr registerCheck bit 5 (TXFF)"]
IsTXFull["TX FIFO Full?"]
Wait["Wait - continue polling"]
WriteDR["Write character to dr registerdr.set(c as u32)"]
Complete["Character transmitted"]

CheckTXFull --> IsTXFull
IsTXFull --> Wait
IsTXFull --> WriteDR
PutChar --> CheckTXFull
Wait --> CheckTXFull
WriteDR --> Complete

The putchar() method src/pl011.rs(L79 - L82)  implements blocking character transmission:

  1. Status Polling: Continuously reads the fr (Flag Register) and checks bit 5 (TXFF - Transmit FIFO Full)
  2. Busy Waiting: If bit 5 is set, the transmit FIFO is full and the method continues polling
  3. Character Writing: When bit 5 is clear, writes the character to the dr (Data Register) as a 32-bit value

Sources: src/pl011.rs(L78 - L82) 

Character Reception

getchar() Operation Flow

flowchart TD
subgraph subGraph0["Register Access"]
    FR["fr: Flag Register (Read-Only)"]
    DR["dr: Data Register (Read-Write)"]
end
GetChar["getchar() called"]
CheckRXEmpty["Read fr registerCheck bit 4 (RXFE)"]
IsRXEmpty["RX FIFO Empty?"]
ReturnNone["Return None"]
ReadDR["Read character from dr registerdr.get() as u8"]
ReturnSome["Return Some(character)"]

CheckRXEmpty --> IsRXEmpty
GetChar --> CheckRXEmpty
IsRXEmpty --> ReadDR
IsRXEmpty --> ReturnNone
ReadDR --> ReturnSome

The getchar() method src/pl011.rs(L85 - L91)  provides non-blocking character reception:

  1. Status Check: Reads the fr (Flag Register) and examines bit 4 (RXFE - Receive FIFO Empty)
  2. Conditional Read: If bit 4 is clear (FIFO not empty), reads from the dr (Data Register)
  3. Return Logic: Returns Some(u8) if data is available, None if FIFO is empty

Sources: src/pl011.rs(L84 - L91) 

Interrupt Handling

Interrupt Detection and Acknowledgment

flowchart TD
subgraph subGraph2["Register Types"]
    MIS["mis: Masked Interrupt Status (Read-Only)"]
    ICR["icr: Interrupt Clear Register (Write-Only)"]
    AckInt["ack_interrupts() called"]
    CheckInt["is_receive_interrupt() called"]
end
subgraph subGraph0["Interrupt Detection"]
    ReturnBool["Return boolean result"]
    subgraph subGraph1["Interrupt Acknowledgment"]
        MIS["mis: Masked Interrupt Status (Read-Only)"]
        AckInt["ack_interrupts() called"]
        ClearICR["Write 0x7ff to icr register(clears all interrupt types)"]
        IntCleared["All interrupts cleared"]
        CheckInt["is_receive_interrupt() called"]
        ReadMIS["Read mis register(Masked Interrupt Status)"]
        CheckBit4["Check bit 4 (receive interrupt)"]
    end
end

AckInt --> ClearICR
CheckBit4 --> ReturnBool
CheckInt --> ReadMIS
ClearICR --> IntCleared
ReadMIS --> CheckBit4

The interrupt handling operations src/pl011.rs(L94 - L102)  provide status checking and clearing functionality:

Interrupt Detection (is_receive_interrupt()):

  • Reads the mis (Masked Interrupt Status) register
  • Checks bit 4 to determine if a receive interrupt is pending
  • Returns boolean indicating interrupt status

Interrupt Acknowledgment (ack_interrupts()):

  • Writes 0x7ff to the icr (Interrupt Clear Register)
  • Clears all interrupt types simultaneously
  • Used after handling interrupts to reset hardware state

Sources: src/pl011.rs(L93 - L102) 

FIFO Management

The PL011 UART controller includes internal FIFOs for both transmission and reception. The implementation configures and interacts with these FIFOs through specific register operations:

FIFO AspectRegisterConfigurationPurpose
Trigger LevelsiflsSet to01/8 depth triggers for both TX/RX
TX Statusfrbit 5Read-onlyIndicates TX FIFO full condition
RX Statusfrbit 4Read-onlyIndicates RX FIFO empty condition
Data TransferdrRead/WriteSingle entry point for FIFO access

The FIFO configuration ensures efficient interrupt-driven operation while preventing data loss during high-throughput scenarios.

Sources: src/pl011.rs(L68 - L69)  src/pl011.rs(L80)  src/pl011.rs(L86) 

Register Access Patterns

All UART operations follow consistent patterns for hardware register access through the regs() method src/pl011.rs(L57 - L59) :

flowchart TD
subgraph subGraph0["Access Types"]
    ReadOnly["ReadOnly (fr, ris, mis)"]
    WriteOnly["WriteOnly (icr)"]
    ReadWrite["ReadWrite (dr, cr, ifls, imsc)"]
end
Method["UART Method"]
RegsCall["self.regs()"]
UnsafeDeref["unsafe { self.base.as_ref() }"]
RegisterStruct["&Pl011UartRegs"]
SpecificReg["Specific Register Access"]

Method --> RegsCall
RegisterStruct --> SpecificReg
RegsCall --> UnsafeDeref
UnsafeDeref --> RegisterStruct

The regs() method provides type-safe access to memory-mapped registers through the tock-registers crate, ensuring that register access constraints are enforced at compile time.

Sources: src/pl011.rs(L57 - L59)  src/pl011.rs(L9 - L32) 

API Reference

Relevant source files

This document provides comprehensive API documentation for the arm_pl011 crate, covering all public interfaces, method signatures, and usage patterns. The API is designed for direct hardware control of PL011 UART controllers in embedded systems and bare-metal environments.

For implementation details and internal architecture, see Core Implementation. For hardware-specific register specifications, see Hardware Reference.

API Overview

The arm_pl011 crate exposes a minimal but complete interface centered around the Pl011Uart struct. The API follows a pattern of explicit initialization followed by character-level I/O operations with optional interrupt handling.

Public Interface Structure

flowchart TD
subgraph subGraph5["Safety Traits"]
    SEND["unsafe impl Send"]
    SYNC["unsafe impl Sync"]
end
subgraph subGraph4["Interrupt Methods"]
    IS_IRQ["is_receive_interrupt(&self) -> bool"]
    ACK_IRQ["ack_interrupts(&mut self)"]
end
subgraph subGraph3["I/O Methods"]
    PUTCHAR["putchar(&mut self, c: u8)"]
    GETCHAR["getchar(&mut self) -> Option"]
end
subgraph subGraph2["Initialization Methods"]
    INIT["init(&mut self)"]
end
subgraph subGraph1["Constructor Methods"]
    NEW["new(base: *mut u8)"]
end
subgraph subGraph0["Public API Surface"]
    CRATE["arm_pl011 crate"]
    UART["Pl011Uart struct"]
    REGS["Pl011UartRegs (internal)"]
end

CRATE --> UART
UART --> ACK_IRQ
UART --> GETCHAR
UART --> INIT
UART --> IS_IRQ
UART --> NEW
UART --> PUTCHAR
UART --> REGS
UART --> SEND
UART --> SYNC

Sources: src/lib.rs(L8)  src/pl011.rs(L34 - L103) 

Method Call Flow

sequenceDiagram
    participant Application as "Application"
    participant Pl011Uart as "Pl011Uart"
    participant PL011Hardware as "PL011 Hardware"

    Application ->> Pl011Uart: new(base_addr)
    Note over Pl011Uart: Construct with hardware base address
    Application ->> Pl011Uart: init()
    Pl011Uart ->> PL011Hardware: Configure registers (icr, ifls, imsc, cr)
    Note over PL011Hardware: Clear interrupts, set FIFO levels, enable RX/TX
    loop "Character Output"
        Application ->> Pl011Uart: putchar(c)
        Pl011Uart ->> PL011Hardware: Check fr register (TX FIFO full?)
        Pl011Uart ->> PL011Hardware: Write to dr register
    end
    loop "Character Input"
        Application ->> Pl011Uart: getchar()
        Pl011Uart ->> PL011Hardware: Check fr register (RX FIFO empty?)
        Pl011Uart -->> Application: Some(char) or None
    end
    opt "Interrupt Handling"
        PL011Hardware ->> Pl011Uart: Hardware interrupt
        Application ->> Pl011Uart: is_receive_interrupt()
        Pl011Uart ->> PL011Hardware: Read mis register
        Pl011Uart -->> Application: true/false
        Application ->> Pl011Uart: ack_interrupts()
        Pl011Uart ->> PL011Hardware: Write to icr register
    end

Sources: src/pl011.rs(L51 - L103) 

Core Types

Pl011Uart

The main driver struct that provides safe access to PL011 UART hardware registers.

FieldTypeDescription
baseNonNullTyped pointer to memory-mapped register base address

Safety Characteristics:

  • Implements Send and Sync for multi-threaded environments
  • Uses NonNull for memory safety guarantees
  • Requires unsafe implementations due to hardware register access

Sources: src/pl011.rs(L42 - L47) 

Pl011UartRegs

Internal register structure defining the memory layout of PL011 UART registers.

OffsetRegisterTypeDescription
0x00drReadWriteData Register
0x18frReadOnlyFlag Register
0x30crReadWriteControl Register
0x34iflsReadWriteInterrupt FIFO Level Select
0x38imscReadWriteInterrupt Mask Set Clear
0x3crisReadOnlyRaw Interrupt Status
0x40misReadOnlyMasked Interrupt Status
0x44icrWriteOnlyInterrupt Clear

Sources: src/pl011.rs(L9 - L32) 

Method Documentation

Constructor Methods

new(base: *mut u8) -> Self

Creates a new Pl011Uart instance from a hardware base address.

Parameters:

  • base: Raw pointer to the memory-mapped UART register base address

Returns:

  • Pl011Uart instance ready for initialization

Safety:

  • Marked as const fn for compile-time construction
  • Uses NonNull::new().unwrap() - will panic if base address is null
  • Caller must ensure base address points to valid PL011 hardware

Example Usage:

// Base address for QEMU virt machine PL011
const UART_BASE: *mut u8 = 0x09000000 as *mut u8;
let mut uart = Pl011Uart::new(UART_BASE);

Sources: src/pl011.rs(L50 - L55) 

Initialization Methods

init(&mut self)

Initializes the PL011 UART with default configuration for basic operation.

Configuration Applied:

  • Clears all pending interrupts (icr = 0x7ff)
  • Sets FIFO trigger levels to 1/8 for both RX and TX (ifls = 0)
  • Enables receive interrupts (imsc = 1 << 4)
  • Enables UART, transmitter, and receiver (cr = (1 << 0) | (1 << 8) | (1 << 9))

Prerequisites:

  • Hardware clock and reset must be properly configured
  • Base address must point to accessible PL011 registers

Sources: src/pl011.rs(L61 - L76) 

I/O Methods

putchar(&mut self, c: u8)

Transmits a single byte through the UART.

Parameters:

  • c: Byte value to transmit

Behavior:

  • Blocks until TX FIFO has space (polls fr register bit 5)
  • Writes character to data register (dr)
  • No return value - always succeeds once FIFO space available

Register Operations:

// Wait for TX FIFO not full
while self.regs().fr.get() & (1 << 5) != 0 {}
// Write character
self.regs().dr.set(c as u32);

Sources: src/pl011.rs(L78 - L82) 

getchar(&mut self) -> Option

Attempts to read a single byte from the UART receive buffer.

Returns:

  • Some(byte): If data is available in RX FIFO
  • None: If RX FIFO is empty

Behavior:

  • Non-blocking operation
  • Checks RX FIFO empty flag (fr register bit 4)
  • Reads from data register (dr) if data available

Register Operations:

if self.regs().fr.get() & (1 << 4) == 0 {
    Some(self.regs().dr.get() as u8)
} else {
    None
}

Sources: src/pl011.rs(L84 - L91) 

Interrupt Methods

is_receive_interrupt(&self) -> bool

Checks if a receive interrupt is currently pending.

Returns:

  • true: Receive interrupt is pending
  • false: No receive interrupt pending

Register Operations:

  • Reads masked interrupt status (mis register)
  • Checks receive interrupt bit (bit 4)

Sources: src/pl011.rs(L93 - L97) 

ack_interrupts(&mut self)

Clears all pending UART interrupts.

Behavior:

  • Writes 0x7ff to interrupt clear register (icr)
  • Clears all interrupt types simultaneously
  • Should be called from interrupt handler after processing

Sources: src/pl011.rs(L99 - L102) 

Thread Safety Implementation

The Pl011Uart struct implements both Send and Sync traits to enable safe usage across thread boundaries in multi-threaded embedded systems.

flowchart TD
subgraph subGraph1["Safety Rationale"]
    HW_ATOMIC["Hardware register access is inherently atomic"]
    NO_STATE["No shared mutable state between operations"]
    MMIO_SAFE["Memory-mapped I/O operations are thread-safe"]
end
subgraph subGraph0["Thread Safety Guarantees"]
    UART["Pl011Uart"]
    SEND["unsafe impl Send"]
    SYNC["unsafe impl Sync"]
end

SEND --> HW_ATOMIC
SYNC --> MMIO_SAFE
SYNC --> NO_STATE
UART --> SEND
UART --> SYNC

Implementation Details:

  • Send: Safe to transfer ownership between threads
  • Sync: Safe to share references between threads
  • Hardware registers provide atomic access guarantees
  • No internal synchronization primitives required

Usage Considerations:

  • Multiple threads can safely call read-only methods (is_receive_interrupt)
  • Mutable methods require external synchronization for concurrent access
  • Interrupt handlers can safely access the same instance

Sources: src/pl011.rs(L46 - L47) 

Pl011Uart Methods

Relevant source files

This page provides detailed documentation for all public methods of the Pl011Uart struct, which implements the primary interface for controlling PL011 UART hardware. These methods handle UART initialization, character transmission and reception, and interrupt management.

For information about the underlying register definitions and hardware abstraction, see Register Definitions. For thread safety and memory safety considerations when using these methods, see Thread Safety and Memory Safety.

Constructor Methods

new()

The new() method constructs a new Pl011Uart instance from a hardware base address.

MethodSignaturePurpose
newpub const fn new(base: *mut u8) -> SelfCreate UART instance from base address
// Usage example - base address typically from device tree or platform code
let uart = Pl011Uart::new(0x0900_0000 as *mut u8);

This constructor uses const fn to enable compile-time initialization and leverages NonNull::new().unwrap().cast() to safely convert the raw pointer to a typed register pointer. The method is marked as const to support static initialization patterns common in embedded systems.

Sources: src/pl011.rs(L50 - L55) 

Initialization Methods

init()

The init() method performs complete UART hardware initialization, configuring interrupts, FIFO levels, and enabling transmission/reception.

MethodSignaturePurpose
initpub fn init(&mut self)Initialize UART hardware for operation

Initialization Sequence:

flowchart TD
START["init() called"]
CLEAR["Clear all interrupts"]
FIFO["Set FIFO trigger levels"]
RX_INT["Enable RX interrupts"]
ENABLE["Enable TX/RX/UART"]
READY["UART ready for operation"]

CLEAR --> FIFO
ENABLE --> READY
FIFO --> RX_INT
RX_INT --> ENABLE
START --> CLEAR

The initialization process involves:

  1. Interrupt Clearing: Sets ICR register to 0x7ff to clear all pending interrupts
  2. FIFO Configuration: Sets IFLS register to 0 for 1/8 RX and TX FIFO trigger levels
  3. Interrupt Enablement: Sets IMSC register bit 4 to enable RX interrupts
  4. UART Enablement: Sets CR register bits 0, 8, and 9 to enable TX, RX, and overall UART operation

Sources: src/pl011.rs(L61 - L76) 

Character I/O Methods

putchar()

The putchar() method transmits a single character through the UART, implementing blocking transmission with FIFO status checking.

MethodSignaturePurpose
putcharpub fn putchar(&mut self, c: u8)Transmit single character (blocking)

Transmission Process:

flowchart TD
CALL["putchar(c)"]
CHECK["Check FR[5] TXFF flag"]
WAIT["TX FIFO full?"]
WRITE["Write char to DR register"]
DONE["Character transmitted"]

CALL --> CHECK
CHECK --> WAIT
WAIT --> CHECK
WAIT --> WRITE
WRITE --> DONE

The method blocks until the transmit FIFO has space (FR register bit 5 clear), then writes the character to the data register for transmission.

Sources: src/pl011.rs(L78 - L82) 

getchar()

The getchar() method attempts to receive a character from the UART, returning Some(u8) if data is available or None if the receive FIFO is empty.

MethodSignaturePurpose
getcharpub fn getchar(&mut self) -> OptionReceive single character (non-blocking)

Reception Logic:

flowchart TD
CALL["getchar()"]
CHECK["Check FR[4] RXFE flag"]
EMPTY["RX FIFO empty?"]
NONE["Return None"]
READ["Read from DR register"]
SOME["Return Some(char)"]

CALL --> CHECK
CHECK --> EMPTY
EMPTY --> NONE
EMPTY --> READ
READ --> SOME

The method checks the FR register bit 4 (RXFE - receive FIFO empty). If clear, data is available and the method reads from the data register; otherwise it returns None.

Sources: src/pl011.rs(L84 - L91) 

Interrupt Handling Methods

is_receive_interrupt()

The is_receive_interrupt() method checks if a receive interrupt is currently pending, enabling interrupt-driven I/O patterns.

MethodSignaturePurpose
is_receive_interruptpub fn is_receive_interrupt(&self) -> boolCheck for pending RX interrupt

This method reads the MIS (Masked Interrupt Status) register and tests bit 4 to determine if a receive interrupt is active. It only detects interrupts that are both asserted and enabled through the interrupt mask.

Sources: src/pl011.rs(L93 - L97) 

ack_interrupts()

The ack_interrupts() method clears all pending UART interrupts by writing to the interrupt clear register.

MethodSignaturePurpose
ack_interruptspub fn ack_interrupts(&mut self)Clear all pending interrupts

This method writes 0x7ff to the ICR register, clearing all possible interrupt conditions. This is typically called in interrupt service routines to acknowledge interrupt processing.

Sources: src/pl011.rs(L99 - L102) 

Method Access Patterns

The following diagram shows how each method interacts with the PL011 hardware registers:

flowchart TD
subgraph subGraph1["PL011 Registers"]
    DR["DR - Data Register"]
    FR["FR - Flag Register"]
    CR["CR - Control Register"]
    IFLS["IFLS - FIFO Level Select"]
    IMSC["IMSC - Interrupt Mask"]
    MIS["MIS - Masked Interrupt Status"]
    ICR["ICR - Interrupt Clear"]
end
subgraph subGraph0["Pl011Uart Methods"]
    NEW["new()"]
    INIT["init()"]
    PUTCHAR["putchar()"]
    GETCHAR["getchar()"]
    IS_INT["is_receive_interrupt()"]
    ACK_INT["ack_interrupts()"]
end
BASE["Base Address Setup"]

ACK_INT --> ICR
GETCHAR --> DR
GETCHAR --> FR
INIT --> CR
INIT --> ICR
INIT --> IFLS
INIT --> IMSC
IS_INT --> MIS
NEW --> BASE
PUTCHAR --> DR
PUTCHAR --> FR

Typical Usage Flow

Most applications follow this pattern when using Pl011Uart methods:

flowchart TD
CREATE["Create with new()"]
INITIALIZE["Call init()"]
READY["UART Ready"]
TX_PATH["Transmission Path"]
RX_PATH["Reception Path"]
INT_PATH["Interrupt Path"]
PUTCHAR_CALL["putchar(c)"]
GETCHAR_CALL["getchar()"]
RX_CHECK["Data available?"]
PROCESS["Process character"]
INT_CHECK["is_receive_interrupt()"]
INT_HANDLE["Interrupt pending?"]
HANDLE_INT["Handle interrupt"]
ACK_CALL["ack_interrupts()"]

ACK_CALL --> INT_PATH
CREATE --> INITIALIZE
GETCHAR_CALL --> RX_CHECK
HANDLE_INT --> ACK_CALL
INITIALIZE --> READY
INT_CHECK --> INT_HANDLE
INT_HANDLE --> HANDLE_INT
INT_HANDLE --> INT_PATH
INT_PATH --> INT_CHECK
PROCESS --> RX_PATH
PUTCHAR_CALL --> TX_PATH
READY --> INT_PATH
READY --> RX_PATH
READY --> TX_PATH
RX_CHECK --> PROCESS
RX_CHECK --> RX_PATH
RX_PATH --> GETCHAR_CALL
TX_PATH --> PUTCHAR_CALL

Private Helper Methods

regs()

The regs() method provides internal access to the register structure, converting the base pointer to a register reference.

MethodSignaturePurpose
regsconst fn regs(&self) -> &Pl011UartRegsInternal register access

This private method uses unsafe code to dereference the base pointer and return a reference to the register structure. All public methods use this helper to access hardware registers safely.

Sources: src/pl011.rs(L57 - L59) 

Thread Safety and Memory Safety

Relevant source files

This document covers the thread safety and memory safety guarantees provided by the arm_pl011 crate, focusing on the Pl011Uart implementation and its safe abstractions over hardware register access. For general API usage patterns, see 3.1. For hardware register specifications, see 5.

Purpose and Scope

The arm_pl011 crate provides memory-safe and thread-safe abstractions for PL011 UART hardware access in no_std embedded environments. This page examines the explicit Send and Sync implementations, memory safety guarantees through NonNull and tock-registers, and safe usage patterns for concurrent access scenarios.

Send and Sync Implementations

The Pl011Uart struct explicitly implements both Send and Sync traits through unsafe implementations, enabling safe transfer and sharing between threads.

Safety Guarantees

flowchart TD
Pl011Uart["Pl011Uart struct"]
Send["unsafe impl Send"]
Sync["unsafe impl Sync"]
NonNull["NonNull<Pl011UartRegs> base"]
SendSafety["Transfer between threads"]
SyncSafety["Shared references across threads"]
MemSafety["Non-null pointer guarantee"]
TockRegs["tock-registers abstraction"]
TypeSafe["Type-safe register access"]
VolatileOps["Volatile read/write operations"]
HardwareExclusive["Hardware access exclusive to instance"]
RegisterAtomic["Register operations are atomic"]

NonNull --> MemSafety
NonNull --> TockRegs
Pl011Uart --> NonNull
Pl011Uart --> Send
Pl011Uart --> Sync
Send --> SendSafety
SendSafety --> HardwareExclusive
Sync --> SyncSafety
SyncSafety --> RegisterAtomic
TockRegs --> TypeSafe
TockRegs --> VolatileOps

Send Safety Justification: Each Pl011Uart instance exclusively owns access to its memory-mapped register region. The hardware controller exists at a unique physical address, making transfer between threads safe as long as no aliasing occurs.

Sync Safety Justification: PL011 register operations are atomic at the hardware level. The tock-registers abstraction ensures volatile access patterns that are safe for concurrent readers.

Sources: src/pl011.rs(L46 - L47) 

Implementation Details

TraitSafety RequirementImplementation Rationale
SendSafe to transfer ownership between threadsHardware register access is location-bound, not thread-bound
SyncSafe to share references across threadsRegister operations are atomic; concurrent reads are safe

The implementations rely on hardware-level atomicity guarantees and the absence of internal mutability that could cause data races.

Sources: src/pl011.rs(L42 - L47) 

Memory Safety Guarantees

NonNull Pointer Management

flowchart TD
Construction["Pl011Uart::new(base: *mut u8)"]
NonNullWrap["NonNull::new(base).unwrap()"]
Cast["cast<Pl011UartRegs>()"]
Storage["NonNull<Pl011UartRegs> base"]
RegAccess["regs() -> &Pl011UartRegs"]
UnsafeDeref["unsafe { self.base.as_ref() }"]
SafeWrapper["Safe register interface"]
PanicOnNull["Panic on null pointer"]
ValidityAssumption["Assumes valid memory mapping"]

Cast --> Storage
Construction --> NonNullWrap
NonNullWrap --> Cast
NonNullWrap --> PanicOnNull
RegAccess --> UnsafeDeref
Storage --> RegAccess
UnsafeDeref --> SafeWrapper
UnsafeDeref --> ValidityAssumption

The Pl011Uart constructor uses NonNull::new().unwrap() to ensure non-null pointer storage, panicking immediately on null input rather than deferring undefined behavior.

Memory Safety Properties:

  • Non-null guarantee: NonNull<T> type prevents null pointer dereference
  • Const construction: Construction in const context validates pointer at compile time when possible
  • Type safety: Cast to Pl011UartRegs provides typed access to register layout

Sources: src/pl011.rs(L51 - L54)  src/pl011.rs(L57 - L59) 

Register Access Safety

The crate uses tock-registers to provide memory-safe register access patterns:

flowchart TD
UnsafeDeref["unsafe { self.base.as_ref() }"]
TockInterface["tock-registers interface"]
ReadOnly["ReadOnly<u32> registers"]
ReadWrite["ReadWrite<u32> registers"]
WriteOnly["WriteOnly<u32> registers"]
VolatileRead["Volatile read operations"]
VolatileReadWrite["Volatile read/write operations"]
VolatileWrite["Volatile write operations"]
CompilerBarrier["Prevents compiler reordering"]
HardwareSync["Synchronized with hardware state"]

CompilerBarrier --> HardwareSync
ReadOnly --> VolatileRead
ReadWrite --> VolatileReadWrite
TockInterface --> ReadOnly
TockInterface --> ReadWrite
TockInterface --> WriteOnly
UnsafeDeref --> TockInterface
VolatileRead --> CompilerBarrier
VolatileReadWrite --> CompilerBarrier
VolatileWrite --> CompilerBarrier
WriteOnly --> VolatileWrite

Safety Mechanisms:

  • Volatile access: All register operations use volatile semantics to prevent compiler optimizations
  • Type safety: Register types prevent incorrect access patterns (e.g., writing to read-only registers)
  • Memory barriers: Volatile operations provide necessary compiler barriers for hardware synchronization

Sources: src/pl011.rs(L9 - L32)  src/pl011.rs(L57 - L59) 

Thread Safety Considerations

Concurrent Access Patterns

flowchart TD
subgraph subGraph2["Method Safety"]
    ConstMethods["&self methods (read-only)"]
    MutMethods["&mut self methods (exclusive)"]
end
subgraph subGraph1["Unsafe Patterns"]
    AliasedMutable["Multiple mutable references"]
    UnprotectedShared["Unprotected shared mutable access"]
end
subgraph subGraph0["Safe Patterns"]
    ExclusiveOwnership["Exclusive ownership per thread"]
    ReadOnlySharing["Shared read-only access"]
    MutexWrapped["Mutex<Pl011Uart> for shared writes"]
end
SafeTransfer["Safe: move between threads"]
ReadRegisters["Read: fr, mis, ris registers"]
WriteRegisters["Write: dr, cr, imsc, icr registers"]
DataRace["Data race on register access"]
UndefinedBehavior["Undefined behavior"]

AliasedMutable --> DataRace
ConstMethods --> ReadRegisters
ExclusiveOwnership --> SafeTransfer
MutMethods --> WriteRegisters
MutexWrapped --> MutMethods
ReadOnlySharing --> ConstMethods
UnprotectedShared --> UndefinedBehavior

Thread Safety Model:

  • Shared immutable access: Multiple threads can safely call &self methods for reading status
  • Exclusive mutable access: Only one thread should have &mut access for configuration and I/O operations
  • Hardware atomicity: Individual register operations are atomic at the hardware level

Sources: src/pl011.rs(L78 - L103) 

Method Safety Classification

MethodSignatureThread SafetyHardware Impact
is_receive_interrupt()&selfSafe for concurrent accessRead-only status check
putchar()&mut selfRequires exclusive accessModifies transmit state
getchar()&mut selfRequires exclusive accessModifies receive state
init()&mut selfRequires exclusive accessModifies controller configuration
ack_interrupts()&mut selfRequires exclusive accessClears interrupt state

The distinction between &self and &mut self methods reflects the underlying hardware behavior and safety requirements.

Sources: src/pl011.rs(L64 - L103) 

Safe Usage Patterns

flowchart TD
SingleOwner["Single-threaded ownership"]
DirectAccess["Direct method calls"]
MultipleReaders["Multiple reader threads"]
SharedRef["Arc<Pl011Uart>"]
ReadOnlyOps["Only &self methods"]
SharedWriter["Shared writer access"]
MutexWrap["Arc<Mutex<Pl011Uart>>"]
ExclusiveLock["lock().unwrap()"]
FullAccess["All methods available"]
InterruptHandler["Interrupt context"]
AtomicCheck["Atomic status check"]
DeferredWork["Defer work to thread context"]

AtomicCheck --> DeferredWork
ExclusiveLock --> FullAccess
InterruptHandler --> AtomicCheck
MultipleReaders --> SharedRef
MutexWrap --> ExclusiveLock
SharedRef --> ReadOnlyOps
SharedWriter --> MutexWrap
SingleOwner --> DirectAccess

Best Practices:

  1. Single ownership: Preferred pattern for most embedded applications
  2. Arc sharing: Use for status monitoring across multiple threads
  3. Mutex protection: Required for shared mutable access
  4. Interrupt safety: Minimize work in interrupt context, use atomic operations only

Sources: src/pl011.rs(L94 - L96) 

Initialization Safety

The const fn new() constructor enables compile-time safety verification when used with known memory addresses:

// Safe: Address known at compile time
const UART0: Pl011Uart = Pl011Uart::new(0x9000_0000 as *mut u8);

// Runtime construction - requires valid address
let uart = Pl011Uart::new(base_addr);

Safety Requirements:

  • Base address must point to valid PL011 hardware registers
  • Memory region must remain mapped for lifetime of Pl011Uart instance
  • No other code should directly access the same register region

Sources: src/pl011.rs(L50 - L55) 

Development

Relevant source files

This section provides a comprehensive guide for contributors to the arm_pl011 crate, covering the development workflow, multi-target building, code quality standards, and continuous integration processes. The material focuses on the practical aspects of contributing to this embedded systems driver library.

For detailed API documentation and usage patterns, see API Reference. For hardware-specific implementation details, see Core Implementation.

Development Environment Setup

The arm_pl011 crate is designed as a no_std embedded library that supports multiple target architectures. Development requires the Rust nightly toolchain with specific components and target platforms.

Required Toolchain Components

The project uses Rust nightly with the following components as defined in the CI configuration:

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and code quality checks
rustfmtCode formatting enforcement

Supported Target Platforms

The crate maintains compatibility across four distinct target architectures:

TargetUse Case
x86_64-unknown-linux-gnuDevelopment and testing on Linux
x86_64-unknown-noneBare metal x86_64 systems
riscv64gc-unknown-none-elfRISC-V embedded systems
aarch64-unknown-none-softfloatARM64 embedded systems without FPU

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L19) 

Multi-Target Development Workflow

The development process accommodates the diverse embedded systems ecosystem by supporting multiple target architectures through a unified workflow.

Target Architecture Matrix

flowchart TD
subgraph subGraph2["Build Verification"]
    FMT["cargo fmt --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test(Linux only)"]
end
subgraph subGraph1["Target Matrix"]
    LINUX["x86_64-unknown-linux-gnuDevelopment & Testing"]
    BARE_X86["x86_64-unknown-noneBare Metal x86"]
    RISCV["riscv64gc-unknown-none-elfRISC-V Systems"]
    ARM64["aarch64-unknown-none-softfloatARM64 Embedded"]
end
subgraph subGraph0["Development Workflow"]
    SRC["Source Codesrc/lib.rssrc/pl011.rs"]
    TOOLCHAIN["Rust Nightlyrust-src, clippy, rustfmt"]
end

ARM64 --> FMT
BARE_X86 --> FMT
BUILD --> TEST
CLIPPY --> BUILD
FMT --> CLIPPY
LINUX --> FMT
RISCV --> FMT
SRC --> TOOLCHAIN
TOOLCHAIN --> ARM64
TOOLCHAIN --> BARE_X86
TOOLCHAIN --> LINUX
TOOLCHAIN --> RISCV

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L23 - L30) 

Local Development Commands

For local development, contributors should verify their changes against all supported targets:

# Format check
cargo fmt --all -- --check

# Linting for each target
cargo clippy --target <TARGET> --all-features

# Build verification
cargo build --target <TARGET> --all-features

# Unit tests (Linux only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

The --all-features flag ensures compatibility with the complete feature set defined in [Cargo.toml(L15)&emsp;](https://github.com/arceos-org/arm_pl011/blob/a5a02f1f/Cargo.toml#L15-L15)

Sources: .github/workflows/ci.yml(L23 - L30) 

Continuous Integration Pipeline

The project maintains code quality through an automated CI/CD pipeline that validates all changes across the supported target matrix.

CI/CD Architecture

flowchart TD
subgraph subGraph4["Documentation Pipeline"]
    DOC_BUILD["cargo doc --no-deps"]
    RUSTDOC_FLAGS["-D rustdoc::broken_intra_doc_links-D missing-docs"]
    GH_PAGES["Deploy to gh-pages(main branch only)"]
end
subgraph subGraph3["Quality Gates"]
    FORMAT["cargo fmt --check"]
    LINT["cargo clippy"]
    BUILD_ALL["cargo build(all targets)"]
    UNIT_TEST["cargo test(Linux only)"]
end
subgraph subGraph2["CI Matrix Strategy"]
    TOOLCHAIN["rust-toolchain: nightly"]
    TARGET_MATRIX["targets matrix:x86_64-unknown-linux-gnux86_64-unknown-noneriscv64gc-unknown-none-elfaarch64-unknown-none-softfloat"]
end
subgraph subGraph1["CI Jobs"]
    CI_JOB["ci jobubuntu-latest"]
    DOC_JOB["doc jobubuntu-latest"]
end
subgraph subGraph0["Trigger Events"]
    PUSH["git push"]
    PR["pull_request"]
end

BUILD_ALL --> UNIT_TEST
CI_JOB --> TARGET_MATRIX
CI_JOB --> TOOLCHAIN
DOC_BUILD --> RUSTDOC_FLAGS
DOC_JOB --> DOC_BUILD
FORMAT --> LINT
LINT --> BUILD_ALL
PR --> CI_JOB
PR --> DOC_JOB
PUSH --> CI_JOB
PUSH --> DOC_JOB
RUSTDOC_FLAGS --> GH_PAGES
TARGET_MATRIX --> FORMAT

Sources: .github/workflows/ci.yml(L1 - L56) 

Quality Enforcement Standards

The CI pipeline enforces strict quality standards through multiple validation stages:

Code Formatting

All code must pass rustfmt validation with the project's formatting rules. The CI fails if formatting inconsistencies are detected.

Linting Rules

The project uses clippy with custom configuration that allows the clippy::new_without_default warning while maintaining strict standards for other potential issues.

Documentation Requirements

Documentation builds enforce strict standards through RUSTDOCFLAGS:

  • -D rustdoc::broken_intra_doc_links: Fails on broken documentation links
  • -D missing-docs: Requires documentation for all public interfaces

Sources: .github/workflows/ci.yml(L23 - L25)  .github/workflows/ci.yml(L40) 

Testing Strategy

The project implements a targeted testing approach:

  • Unit Tests: Execute only on x86_64-unknown-linux-gnu for practical development iteration
  • Build Verification: All targets must compile successfully to ensure cross-platform compatibility
  • Documentation Tests: Included in the documentation build process

This strategy balances comprehensive validation with CI resource efficiency, since the core functionality is hardware-agnostic while the compilation targets verify platform compatibility.

Sources: .github/workflows/ci.yml(L28 - L30) 

Documentation Deployment

The documentation pipeline automatically generates and deploys API documentation to GitHub Pages for the main branch. The process includes:

  1. Building documentation with cargo doc --no-deps --all-features
  2. Generating an index redirect page based on the crate name from cargo tree
  3. Deploying to the gh-pages branch using single-commit strategy

This ensures that the latest documentation is always available at the project's GitHub Pages URL as specified in [Cargo.toml(L10)&emsp;](https://github.com/arceos-org/arm_pl011/blob/a5a02f1f/Cargo.toml#L10-L10)

Sources: .github/workflows/ci.yml(L44 - L55) 

Building and Testing

Relevant source files

This document provides comprehensive instructions for building the arm_pl011 crate locally, running tests, and working with the multi-target build matrix. It covers the development workflow from initial setup through quality assurance checks and documentation generation.

For information about the automated CI/CD pipeline and deployment processes, see CI/CD Pipeline.

Prerequisites and Setup

The arm_pl011 crate requires Rust nightly toolchain with specific components and target architectures. The crate uses no_std and requires unstable features for cross-platform embedded development.

Required Toolchain Components

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and code analysis
rustfmtCode formatting

Supported Target Architectures

The crate supports multiple target architectures as defined in the CI configuration:

TargetUse Case
x86_64-unknown-linux-gnuDevelopment and testing on Linux
x86_64-unknown-noneBare metal x86_64 systems
riscv64gc-unknown-none-elfRISC-V embedded systems
aarch64-unknown-none-softfloatARM64 embedded systems

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L18 - L19) 

Local Build Workflow

Development Build Process

flowchart TD
Start["Start Development"]
Setup["Setup Toolchain"]
Install["Install Components"]
AddTargets["Add Target Architectures"]
Format["cargo fmt --check"]
Lint["cargo clippy"]
Build["cargo build --target"]
Test["cargo test"]
Doc["cargo doc"]
Complete["Development Complete"]
FormatFail["Format Check Failed"]
LintFail["Clippy Warnings/Errors"]
BuildFail["Build Failed"]
TestFail["Test Failed"]
FixFormat["Fix Formatting Issues"]
FixLint["Fix Lint Issues"]
FixBuild["Fix Build Errors"]
FixTest["Fix Test Failures"]

AddTargets --> Format
Build --> BuildFail
Build --> Test
BuildFail --> FixBuild
Doc --> Complete
FixBuild --> Build
FixFormat --> Format
FixLint --> Lint
FixTest --> Test
Format --> FormatFail
Format --> Lint
FormatFail --> FixFormat
Install --> AddTargets
Lint --> Build
Lint --> LintFail
LintFail --> FixLint
Setup --> Install
Start --> Setup
Test --> Doc
Test --> TestFail
TestFail --> FixTest

Sources: .github/workflows/ci.yml(L22 - L30) 

Multi-Target Building

Target Architecture Matrix

The crate implements a comprehensive multi-target build strategy to ensure compatibility across different embedded platforms:

flowchart TD
subgraph subGraph3["Quality Checks"]
    rustfmt["cargo fmt --check"]
    clippy["cargo clippy"]
    build["cargo build"]
    test["cargo test"]
end
subgraph subGraph2["Build Outputs"]
    LinuxLib["Linux Development Library"]
    BareMetalLib["Bare Metal Library"]
    RiscvLib["RISC-V Embedded Library"]
    ArmLib["ARM64 Embedded Library"]
end
subgraph subGraph1["Build Matrix"]
    x86Linux["x86_64-unknown-linux-gnu"]
    x86Bare["x86_64-unknown-none"]
    riscv["riscv64gc-unknown-none-elf"]
    arm64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Source Code"]
    CargoToml["Cargo.toml"]
    SrcLib["src/lib.rs"]
    SrcPl011["src/pl011.rs"]
end

CargoToml --> arm64
CargoToml --> riscv
CargoToml --> x86Bare
CargoToml --> x86Linux
SrcLib --> arm64
SrcLib --> riscv
SrcLib --> x86Bare
SrcLib --> x86Linux
SrcPl011 --> arm64
SrcPl011 --> riscv
SrcPl011 --> x86Bare
SrcPl011 --> x86Linux
arm64 --> ArmLib
riscv --> RiscvLib
x86Bare --> BareMetalLib
x86Linux --> LinuxLib
x86Linux --> build
x86Linux --> clippy
x86Linux --> rustfmt
x86Linux --> test

Building for Specific Targets

To build for a specific target architecture:

# Build for Linux development
cargo build --target x86_64-unknown-linux-gnu --all-features

# Build for bare metal x86_64
cargo build --target x86_64-unknown-none --all-features

# Build for RISC-V embedded
cargo build --target riscv64gc-unknown-none-elf --all-features

# Build for ARM64 embedded
cargo build --target aarch64-unknown-none-softfloat --all-features

Sources: .github/workflows/ci.yml(L27)  Cargo.toml(L1 - L16) 

Testing Strategy

Test Execution Matrix

Testing is platform-specific due to the embedded nature of the crate:

flowchart TD
subgraph subGraph2["Test Execution"]
    CanTest["Tests Run"]
    CannotTest["Tests Skipped"]
end
subgraph subGraph1["Target Platforms"]
    LinuxTarget["x86_64-unknown-linux-gnu"]
    BareTarget["x86_64-unknown-none"]
    RiscvTarget["riscv64gc-unknown-none-elf"]
    ArmTarget["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Test Types"]
    UnitTests["Unit Tests"]
    IntegrationTests["Integration Tests"]
    DocTests["Documentation Tests"]
end
TestResults["Test Results Output"]
BuildOnly["Build Verification Only"]

ArmTarget --> CannotTest
BareTarget --> CannotTest
CanTest --> TestResults
CannotTest --> BuildOnly
DocTests --> LinuxTarget
IntegrationTests --> LinuxTarget
LinuxTarget --> CanTest
RiscvTarget --> CannotTest
UnitTests --> LinuxTarget

Running Tests Locally

Unit tests are only executed on the x86_64-unknown-linux-gnu target due to the embedded nature of other targets:

# Run unit tests with output
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

# Run specific test
cargo test --target x86_64-unknown-linux-gnu test_name -- --nocapture

# Run tests with all features
cargo test --target x86_64-unknown-linux-gnu --all-features -- --nocapture

Sources: .github/workflows/ci.yml(L28 - L30) 

Code Quality Assurance

Quality Check Pipeline

The development workflow includes mandatory quality checks that mirror the CI pipeline:

flowchart TD
subgraph subGraph3["Build Verification"]
    BuildCmd["cargo build --target TARGET"]
    AllFeatures["--all-features"]
    BuildResult["Build Success"]
end
subgraph subGraph2["Clippy Analysis"]
    ClippyCmd["cargo clippy --all-features"]
    ClippyFilter["-A clippy::new_without_default"]
    ClippyResult["Lint Compliance"]
end
subgraph subGraph1["Format Check"]
    FormatCmd["cargo fmt --all --check"]
    FormatResult["Format Compliance"]
end
subgraph subGraph0["Code Quality Checks"]
    Format["rustfmt Check"]
    Lint["clippy Analysis"]
    Build["Multi-target Build"]
    Test["Unit Testing"]
end

AllFeatures --> BuildResult
Build --> BuildCmd
BuildCmd --> AllFeatures
ClippyCmd --> ClippyFilter
ClippyFilter --> ClippyResult
Format --> FormatCmd
FormatCmd --> FormatResult
Lint --> ClippyCmd

Local Quality Checks

Run the same quality checks locally as the CI pipeline:

# Format check
cargo fmt --all -- --check

# Clippy with target-specific analysis
cargo clippy --target x86_64-unknown-linux-gnu --all-features -- -A clippy::new_without_default
cargo clippy --target aarch64-unknown-none-softfloat --all-features -- -A clippy::new_without_default

# Build all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features

Sources: .github/workflows/ci.yml(L22 - L27) 

Documentation Building

Documentation Generation Process

flowchart TD
subgraph Output["Output"]
    TargetDoc["target/doc/"]
    IndexHtml["index.html Redirect"]
    DocsResult["Generated Documentation"]
end
subgraph subGraph2["Documentation Checks"]
    BrokenLinks["-D rustdoc::broken_intra_doc_links"]
    MissingDocs["-D missing-docs"]
end
subgraph subGraph1["Doc Generation"]
    CargoDoc["cargo doc --no-deps"]
    AllFeatures["--all-features"]
    RustDocFlags["RUSTDOCFLAGS Environment"]
end
subgraph subGraph0["Documentation Sources"]
    SrcDocs["Source Code Documentation"]
    ReadmeDoc["README.md"]
    CargoMeta["Cargo.toml Metadata"]
end

AllFeatures --> RustDocFlags
BrokenLinks --> TargetDoc
CargoDoc --> AllFeatures
CargoMeta --> CargoDoc
IndexHtml --> DocsResult
MissingDocs --> TargetDoc
ReadmeDoc --> CargoDoc
RustDocFlags --> BrokenLinks
RustDocFlags --> MissingDocs
SrcDocs --> CargoDoc
TargetDoc --> IndexHtml

Building Documentation Locally

Generate documentation with the same strict requirements as CI:

# Set documentation flags for strict checking
export RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"

# Generate documentation
cargo doc --no-deps --all-features

# Create index redirect (mimics CI behavior)
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' \
  $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

# Open documentation in browser
open target/doc/index.html  # macOS
xdg-open target/doc/index.html  # Linux

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L48)  Cargo.toml(L10) 

Development Environment Setup

Complete Setup Commands

# Install nightly toolchain
rustup toolchain install nightly

# Add required components
rustup component add rust-src clippy rustfmt

# Add target architectures
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat

# Set nightly as default for this project
rustup override set nightly

# Verify setup
rustc --version --verbose
cargo --version

Sources: .github/workflows/ci.yml(L15 - L21) 

CI/CD Pipeline

Relevant source files

This document describes the automated continuous integration and deployment pipeline for the arm_pl011 crate. The pipeline handles code quality checks, multi-target compilation, testing, documentation generation, and automated deployment to GitHub Pages.

For information about local development and testing procedures, see Building and Testing. For API documentation specifics, see API Reference.

Pipeline Overview

The CI/CD pipeline is implemented using GitHub Actions and consists of two primary workflows that execute on every push and pull request to ensure code quality and maintain up-to-date documentation.

Workflow Architecture

flowchart TD
Trigger["GitHub Events"]
Event1["push"]
Event2["pull_request"]
Pipeline["CI Workflow"]
Job1["ci Job"]
Job2["doc Job"]
Matrix["Matrix Strategy"]
Target1["x86_64-unknown-linux-gnu"]
Target2["x86_64-unknown-none"]
Target3["riscv64gc-unknown-none-elf"]
Target4["aarch64-unknown-none-softfloat"]
DocBuild["cargo doc"]
Deploy["GitHub Pages Deploy"]
QualityChecks["Quality Checks"]
Format["cargo fmt"]
Lint["cargo clippy"]
Build["cargo build"]
Test["cargo test"]

Event1 --> Pipeline
Event2 --> Pipeline
Job1 --> Matrix
Job2 --> Deploy
Job2 --> DocBuild
Matrix --> Target1
Matrix --> Target2
Matrix --> Target3
Matrix --> Target4
Pipeline --> Job1
Pipeline --> Job2
QualityChecks --> Build
QualityChecks --> Format
QualityChecks --> Lint
QualityChecks --> Test
Target1 --> QualityChecks
Target2 --> QualityChecks
Target3 --> QualityChecks
Target4 --> QualityChecks
Trigger --> Event1
Trigger --> Event2

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Job Implementation

The ci job implements comprehensive quality assurance through a matrix build strategy that validates the crate across multiple target architectures.

Matrix Configuration

ComponentValue
Runnerubuntu-latest
Rust Toolchainnightly
Fail Fastfalse
Target Count4 architectures

The matrix strategy ensures the crate builds correctly across all supported embedded platforms:

flowchart TD
subgraph subGraph2["Toolchain Components"]
    RustSrc["rust-src"]
    Clippy["clippy"]
    Rustfmt["rustfmt"]
end
subgraph subGraph1["Target Matrix"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Matrix Configuration"]
    Toolchain["rust-toolchain: nightly"]
    Strategy["fail-fast: false"]
end

Strategy --> T1
Strategy --> T2
Strategy --> T3
Strategy --> T4
Toolchain --> Clippy
Toolchain --> RustSrc
Toolchain --> Rustfmt

Sources: .github/workflows/ci.yml(L8 - L19) 

Quality Check Steps

The pipeline implements a four-stage quality verification process:

flowchart TD
Setup["Checkout & Rust Setup"]
Version["rustc --version --verbose"]
Format["cargo fmt --all -- --check"]
Clippy["cargo clippy --target TARGET --all-features"]
Build["cargo build --target TARGET --all-features"]
Test["cargo test --target TARGET"]
Condition["TARGET == x86_64-unknown-linux-gnu?"]
Execute["Execute Tests"]
Skip["Skip Tests"]

Build --> Test
Clippy --> Build
Condition --> Execute
Condition --> Skip
Format --> Clippy
Setup --> Version
Test --> Condition
Version --> Format

Code Formatting

The cargo fmt --all -- --check command validates that all code adheres to standard Rust formatting conventions without making modifications.

Linting Analysis

Clippy performs static analysis with the configuration cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default, specifically allowing the new_without_default lint for the crate's design patterns.

Multi-Target Compilation

Each target architecture undergoes full compilation with cargo build --target ${{ matrix.targets }} --all-features to ensure cross-platform compatibility.

Unit Testing

Unit tests execute only on the x86_64-unknown-linux-gnu target using cargo test --target ${{ matrix.targets }} -- --nocapture for comprehensive output visibility.

Sources: .github/workflows/ci.yml(L20 - L30) 

Documentation Job

The doc job generates and deploys API documentation with strict quality enforcement.

Documentation Build Process

flowchart TD
DocJob["doc Job"]
Permissions["contents: write"]
EnvCheck["Branch Check"]
DocFlags["RUSTDOCFLAGS Setup"]
Flag1["-D rustdoc::broken_intra_doc_links"]
Flag2["-D missing-docs"]
Build["cargo doc --no-deps --all-features"]
IndexGen["Generate index.html redirect"]
BranchCheck["github.ref == default-branch?"]
Deploy["JamesIves/github-pages-deploy-action@v4"]
Skip["Skip Deployment"]
Target["gh-pages branch"]
Folder["target/doc"]

BranchCheck --> Deploy
BranchCheck --> Skip
Build --> IndexGen
Deploy --> Folder
Deploy --> Target
DocFlags --> Flag1
DocFlags --> Flag2
DocJob --> Permissions
EnvCheck --> DocFlags
Flag1 --> Build
Flag2 --> Build
IndexGen --> BranchCheck
Permissions --> EnvCheck

Documentation Configuration

The documentation build enforces strict quality standards through RUSTDOCFLAGS:

FlagPurpose
-D rustdoc::broken_intra_doc_linksFail on broken internal documentation links
-D missing-docsRequire documentation for all public items

The build process includes automatic index generation:

printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Sources: .github/workflows/ci.yml(L32 - L48) 

Deployment Strategy

GitHub Pages Integration

The deployment uses the JamesIves/github-pages-deploy-action@v4 action with specific configuration:

flowchart TD
DefaultBranch["Default Branch Push"]
DeployAction["github-pages-deploy-action@v4"]
Config1["single-commit: true"]
Config2["branch: gh-pages"]
Config3["folder: target/doc"]
Result["Documentation Site"]

Config1 --> Result
Config2 --> Result
Config3 --> Result
DefaultBranch --> DeployAction
DeployAction --> Config1
DeployAction --> Config2
DeployAction --> Config3

Conditional Deployment Logic

Documentation deployment occurs only when:

  1. The push targets the repository's default branch (github.ref == env.default-branch)
  2. The documentation build succeeds without errors

For non-default branches and pull requests, the documentation build continues with continue-on-error: true to provide feedback without blocking the pipeline.

Sources: .github/workflows/ci.yml(L38 - L55) 

Target Architecture Matrix

The pipeline validates compilation across four distinct target architectures, ensuring broad embedded systems compatibility:

Architecture Support Matrix

TargetEnvironmentUse Case
x86_64-unknown-linux-gnuLinux userspaceDevelopment and testing
x86_64-unknown-noneBare metal x86OS kernels and bootloaders
riscv64gc-unknown-none-elfRISC-V embeddedRISC-V based systems
aarch64-unknown-none-softfloatARM64 embeddedARM-based embedded systems

Build Validation Flow

flowchart TD
Source["Source Code"]
Validation["Multi-Target Validation"]
Linux["x86_64-unknown-linux-gnu"]
BareX86["x86_64-unknown-none"]
RISCV["riscv64gc-unknown-none-elf"]
ARM64["aarch64-unknown-none-softfloat"]
TestSuite["Unit Test Suite"]
CompileOnly["Compile Only"]
Success["Pipeline Success"]

ARM64 --> CompileOnly
BareX86 --> CompileOnly
CompileOnly --> Success
Linux --> TestSuite
RISCV --> CompileOnly
Source --> Validation
TestSuite --> Success
Validation --> ARM64
Validation --> BareX86
Validation --> Linux
Validation --> RISCV

Sources: .github/workflows/ci.yml(L12 - L30) 

Hardware Reference

Relevant source files

This document provides technical specifications and hardware details for the ARM PL011 UART controller. It covers register definitions, memory mapping, signal interfaces, and hardware configuration requirements necessary for embedded systems development with the PL011.

For software implementation details and API usage, see Core Implementation. For register abstraction and driver implementation, see Register Definitions.

PL011 UART Controller Overview

The ARM PL011 is a programmable serial interface controller that implements the Universal Asynchronous Receiver/Transmitter (UART) functionality. It provides full-duplex serial communication with configurable baud rates, data formats, and hardware flow control capabilities.

Hardware Block Architecture

flowchart TD
subgraph System_Interface["System_Interface"]
    CLK["UARTCLK"]
    PCLK["PCLK"]
    RESET["PRESETn"]
    IRQ["UARTINTR"]
end
subgraph External_Signals["External_Signals"]
    TXD["TXD (Transmit Data)"]
    RXD["RXD (Receive Data)"]
    RTS["nRTS (Request to Send)"]
    CTS["nCTS (Clear to Send)"]
    DSR["nDSR (Data Set Ready)"]
    DTR["nDTR (Data Terminal Ready)"]
    DCD["nDCD (Data Carrier Detect)"]
    RI["nRI (Ring Indicator)"]
end
subgraph PL011_Hardware_Block["PL011_Hardware_Block"]
    APB["APB Interface"]
    CTRL["Control Logic"]
    TXFIFO["TX FIFO(16 entries)"]
    RXFIFO["RX FIFO(16 entries)"]
    BAUD["Baud Rate Generator"]
    SER["Serializer/Deserializer"]
    INT["Interrupt Controller"]
end

APB --> CTRL
BAUD --> SER
CLK --> BAUD
CTRL --> BAUD
CTRL --> INT
CTRL --> RXFIFO
CTRL --> TXFIFO
CTS --> SER
DCD --> SER
DSR --> SER
INT --> IRQ
PCLK --> APB
RESET --> CTRL
RI --> SER
RXD --> SER
SER --> DTR
SER --> RTS
SER --> RXFIFO
SER --> TXD
TXFIFO --> SER

Sources: src/pl011.rs(L9 - L32) 

Register Map and Memory Layout

The PL011 uses a memory-mapped register interface accessed through the APB bus. The register map spans 72 bytes (0x48) with specific registers at defined offsets.

Complete Register Map

OffsetRegisterAccessDescription
0x00DRR/WData Register
0x04-0x14Reserved-Reserved space
0x18FRROFlag Register
0x1C-0x2CReserved-Reserved space
0x30CRR/WControl Register
0x34IFLSR/WInterrupt FIFO Level Select
0x38IMSCR/WInterrupt Mask Set/Clear
0x3CRISRORaw Interrupt Status
0x40MISROMasked Interrupt Status
0x44ICRWOInterrupt Clear
0x48END-End of register space
flowchart TD
subgraph Memory_Layout_0x48_bytes["Memory_Layout_0x48_bytes"]
    DR["0x00: DRData Register(R/W)"]
    RES1["0x04-0x14Reserved"]
    FR["0x18: FRFlag Register(RO)"]
    RES2["0x1C-0x2CReserved"]
    CR["0x30: CRControl Register(R/W)"]
    IFLS["0x34: IFLSInterrupt FIFO Level(R/W)"]
    IMSC["0x38: IMSCInterrupt Mask(R/W)"]
    RIS["0x3C: RISRaw Interrupt Status(RO)"]
    MIS["0x40: MISMasked Interrupt Status(RO)"]
    ICR["0x44: ICRInterrupt Clear(WO)"]
end

CR --> IFLS
DR --> RES1
FR --> RES2
IFLS --> IMSC
IMSC --> RIS
MIS --> ICR
RES1 --> FR
RES2 --> CR
RIS --> MIS

Sources: src/pl011.rs(L9 - L32) 

Register Specifications

Data Register (DR) - Offset 0x00

The Data Register provides the interface for transmitting and receiving data. It handles both read and write operations with automatic FIFO management.

BitsFieldAccessDescription
31:12Reserved-Reserved, reads as zero
11OEROOverrun Error
10BEROBreak Error
9PEROParity Error
8FEROFraming Error
7:0DATAR/WTransmit/Receive Data

Flag Register (FR) - Offset 0x18

The Flag Register provides status information about the UART's operational state and FIFO conditions.

BitsFieldAccessDescription
31:8Reserved-Reserved
7TXFEROTransmit FIFO Empty
6RXFFROReceive FIFO Full
5TXFFROTransmit FIFO Full
4RXFEROReceive FIFO Empty
3BUSYROUART Busy
2DCDROData Carrier Detect
1DSRROData Set Ready
0CTSROClear to Send

Control Register (CR) - Offset 0x30

The Control Register configures the operational parameters of the UART including enables, loopback, and flow control.

BitsFieldAccessDescription
31:16Reserved-Reserved
15CTSENR/WCTS Hardware Flow Control Enable
14RTSENR/WRTS Hardware Flow Control Enable
13:12Reserved-Reserved
11RTSR/WRequest to Send
10DTRR/WData Terminal Ready
9RXER/WReceive Enable
8TXER/WTransmit Enable
7LBER/WLoopback Enable
6:3Reserved-Reserved
2:1SIRLPR/WSIR Low Power Mode
0UARTENR/WUART Enable

Sources: src/pl011.rs(L12 - L31) 

Hardware Features

FIFO Configuration

The PL011 contains separate 16-entry FIFOs for transmit and receive operations, providing buffering to reduce interrupt overhead and improve system performance.

flowchart TD
subgraph Trigger_Levels["Trigger_Levels"]
    TX_1_8["TX: 1/8 (2 entries)"]
    TX_1_4["TX: 1/4 (4 entries)"]
    TX_1_2["TX: 1/2 (8 entries)"]
    TX_3_4["TX: 3/4 (12 entries)"]
    TX_7_8["TX: 7/8 (14 entries)"]
    RX_1_8["RX: 1/8 (2 entries)"]
    RX_1_4["RX: 1/4 (4 entries)"]
    RX_1_2["RX: 1/2 (8 entries)"]
    RX_3_4["RX: 3/4 (12 entries)"]
    RX_7_8["RX: 7/8 (14 entries)"]
end
subgraph FIFO_Architecture["FIFO_Architecture"]
    TXFIFO["TX FIFO16 entries8-bit width"]
    RXFIFO["RX FIFO16 entries8-bit width + 4-bit error"]
    TXLOGIC["TX Logic"]
    RXLOGIC["RX Logic"]
    IFLS_REG["IFLS RegisterTrigger Levels"]
end

IFLS_REG --> RXFIFO
IFLS_REG --> RX_1_8
IFLS_REG --> TXFIFO
IFLS_REG --> TX_1_8
RXLOGIC --> RXFIFO
TXFIFO --> TXLOGIC

Sources: src/pl011.rs(L68 - L69) 

Interrupt System

The PL011 provides comprehensive interrupt support with multiple interrupt sources and configurable masking.

InterruptBitDescription
OEIM10Overrun Error Interrupt
BEIM9Break Error Interrupt
PEIM8Parity Error Interrupt
FEIM7Framing Error Interrupt
RTIM6Receive Timeout Interrupt
TXIM5Transmit Interrupt
RXIM4Receive Interrupt
DSRMIM3nUARTDSR Modem Interrupt
DCDMIM2nUARTDCD Modem Interrupt
CTSMIM1nUARTCTS Modem Interrupt
RIMIM0nUARTRI Modem Interrupt

Sources: src/pl011.rs(L71 - L72)  src/pl011.rs(L94 - L96)  src/pl011.rs(L100 - L101) 

Signal Interface and Pin Configuration

Primary UART Signals

The PL011 requires the following primary signals for basic UART operation:

SignalDirectionDescription
TXDOutputTransmit Data
RXDInputReceive Data
UARTCLKInputUART Reference Clock
PCLKInputAPB Bus Clock
PRESETnInputAPB Reset (active low)

Modem Control Signals (Optional)

For full modem control functionality, additional signals are available:

SignalDirectionDescription
nRTSOutputRequest to Send (active low)
nCTSInputClear to Send (active low)
nDTROutputData Terminal Ready (active low)
nDSRInputData Set Ready (active low)
nDCDInputData Carrier Detect (active low)
nRIInputRing Indicator (active low)
flowchart TD
subgraph External_Device["External_Device"]
    EXT["External UART Deviceor Terminal"]
end
subgraph PL011_Pin_Interface["PL011_Pin_Interface"]
    UART["PL011 Controller"]
    TXD_PIN["TXD"]
    RXD_PIN["RXD"]
    RTS_PIN["nRTS"]
    CTS_PIN["nCTS"]
    DTR_PIN["nDTR"]
    DSR_PIN["nDSR"]
    DCD_PIN["nDCD"]
    RI_PIN["nRI"]
    CLK_PIN["UARTCLK"]
    PCLK_PIN["PCLK"]
    RST_PIN["PRESETn"]
end

CLK_PIN --> UART
CTS_PIN --> UART
DCD_PIN --> UART
DSR_PIN --> UART
EXT --> CTS_PIN
EXT --> RXD_PIN
PCLK_PIN --> UART
RI_PIN --> UART
RST_PIN --> UART
RTS_PIN --> EXT
RXD_PIN --> UART
TXD_PIN --> EXT
UART --> DTR_PIN
UART --> RTS_PIN
UART --> TXD_PIN

Sources: README.md references ARM documentation

Hardware Initialization Requirements

Power-On Reset Sequence

The PL011 requires a specific initialization sequence to ensure proper operation:

  1. Assert PRESETn: Hold the reset signal low during power-up
  2. Clock Stabilization: Ensure UARTCLK and PCLK are stable
  3. Release Reset: Deassert PRESETn to begin initialization
  4. Register Configuration: Configure control and interrupt registers

Essential Configuration Steps

Based on the driver implementation, the hardware requires this initialization sequence:

flowchart TD
RESET["Hardware ResetPRESETn asserted"]
CLK_STABLE["Clock StabilizationUARTCLK, PCLK stable"]
REL_RESET["Release ResetPRESETn deasserted"]
CLEAR_INT["Clear All InterruptsICR = 0x7FF"]
SET_FIFO["Set FIFO LevelsIFLS = 0x00"]
EN_RX_INT["Enable RX InterruptIMSC = 0x10"]
EN_UART["Enable UART OperationCR = 0x301"]

CLEAR_INT --> SET_FIFO
CLK_STABLE --> REL_RESET
EN_RX_INT --> EN_UART
REL_RESET --> CLEAR_INT
RESET --> CLK_STABLE
SET_FIFO --> EN_RX_INT

The specific register values used in initialization:

  • ICR: 0x7FF - Clears all interrupt sources
  • IFLS: 0x00 - Sets 1/8 trigger level for both TX and RX FIFOs
  • IMSC: 0x10 - Enables receive interrupt (RXIM bit)
  • CR: 0x301 - Enables UART, transmit, and receive (UARTEN | TXE | RXE)

Sources: src/pl011.rs(L64 - L76) 

Performance and Timing Considerations

Clock Requirements

The PL011 operates with two clock domains:

  • PCLK: APB bus clock for register access (typically system clock frequency)
  • UARTCLK: Reference clock for baud rate generation (can be independent)

Baud Rate Generation

The internal baud rate generator uses UARTCLK to create the desired communication speed. The relationship is:

Baud Rate = UARTCLK / (16 × (IBRD + FBRD/64))

Where IBRD and FBRD are integer and fractional baud rate divisors configured through additional registers not exposed in this implementation.

FIFO Timing Characteristics

  • FIFO Depth: 16 entries for both TX and RX
  • Access Time: Single PCLK cycle for register access
  • Interrupt Latency: Configurable based on FIFO trigger levels
  • Overrun Protection: Hardware prevents data loss when FIFOs are properly managed

Sources: src/pl011.rs(L68 - L69)  for FIFO configuration, ARM PL011 Technical Reference Manual for timing specifications

Introduction

Relevant source files

Purpose and Scope

This document covers the handler_table crate, a lock-free event handling system designed for the ArceOS operating system. The crate provides a thread-safe, fixed-size table that maps event identifiers to handler functions without requiring locks or dynamic memory allocation.

For detailed API usage and examples, see User Guide. For implementation details and atomic operations, see Implementation Details. For ArceOS-specific integration patterns, see ArceOS Integration.

Overview

The handler_table crate implements a HandlerTable<N> structure that stores function pointers in a compile-time sized array using atomic operations for thread safety. It serves as a central dispatch mechanism for event handling in kernel and embedded environments where lock-free operations are critical for performance and real-time guarantees.

System Architecture

The following diagram illustrates the core components and their relationships in the codebase:

HandlerTable Core Components

flowchart TD
subgraph subGraph2["Handler Storage"]
    FunctionPointer["fn() as *const ()"]
    ZeroValue["0 (empty slot)"]
end
subgraph subGraph1["Atomic Operations"]
    compare_exchange["compare_exchange()"]
    load["load()"]
    swap["swap()"]
end
subgraph subGraph0["Public API Methods"]
    new["new()"]
    register_handler["register_handler(idx, handler)"]
    handle["handle(idx)"]
    unregister_handler["unregister_handler(idx)"]
end
HandlerTable["HandlerTable<N>"]
AtomicArray["[AtomicUsize; N]"]

AtomicArray --> FunctionPointer
AtomicArray --> ZeroValue
HandlerTable --> AtomicArray
compare_exchange --> AtomicArray
handle --> load
load --> AtomicArray
new --> HandlerTable
register_handler --> compare_exchange
swap --> AtomicArray
unregister_handler --> swap

Sources: Cargo.toml(L1 - L15)  README.md(L11 - L31) 

Event Handling Flow

The following diagram shows how events flow through the system using specific code entities:

Event Processing Pipeline


Sources: README.md(L16 - L28) 

Key Characteristics

The handler_table crate provides several critical features for system-level event handling:

FeatureImplementationBenefit
Lock-free OperationAtomic operations only (AtomicUsize)No blocking, suitable for interrupt contexts
Fixed SizeCompile-time array[AtomicUsize; N]No dynamic allocation,no_stdcompatible
Thread SafetyMemory ordering guaranteesSafe concurrent access across threads
Zero DependenciesPure core library usageMinimal footprint for embedded systems

Target Environments

The crate is specifically designed for:

  • ArceOS kernel components - Central event dispatch system
  • Embedded systems - Real-time event handling without heap allocation
  • Interrupt handlers - Lock-free operation in critical sections
  • Multi-architecture support - Works across x86_64, RISC-V, and ARM platforms

Sources: Cargo.toml(L6 - L12) 

Integration Context

Within the ArceOS ecosystem, handler_table serves as a foundational component for event-driven programming patterns. The lock-free design enables high-performance event dispatch suitable for kernel-level operations where traditional locking mechanisms would introduce unacceptable latency or deadlock risks.

For specific integration patterns with ArceOS components, see ArceOS Integration. For understanding the lock-free design principles and their benefits, see Lock-free Design Benefits.

Sources: Cargo.toml(L8 - L11)  README.md(L1 - L7) 

ArceOS Integration

Relevant source files

This document explains how the handler_table crate integrates into the ArceOS operating system ecosystem and serves as a foundational component for lock-free event handling in kernel and embedded environments.

The scope covers the architectural role of handler_table within ArceOS, its no_std design constraints, and how it fits into the broader operating system infrastructure. For details about the lock-free programming concepts and performance benefits, see Lock-free Design Benefits. For implementation specifics, see Implementation Details.

Purpose and Role in ArceOS

The handler_table crate provides a core event handling mechanism designed specifically for the ArceOS operating system. ArceOS is a modular, component-based operating system written in Rust that targets both traditional and embedded computing environments.

ArceOS Integration Architecture

flowchart TD
subgraph Event_Sources["Event Sources"]
    Hardware_Interrupts["Hardware Interrupts"]
    Timer_Events["Timer Events"]
    System_Calls["System Calls"]
    IO_Completion["I/O Completion"]
end
subgraph handler_table_Crate["handler_table Crate"]
    HandlerTable_Struct["HandlerTable"]
    register_handler["register_handler()"]
    handle_method["handle()"]
    unregister_handler["unregister_handler()"]
end
subgraph ArceOS_Ecosystem["ArceOS Operating System Ecosystem"]
    ArceOS_Kernel["ArceOS Kernel Core"]
    Task_Scheduler["Task Scheduler"]
    Memory_Manager["Memory Manager"]
    Device_Drivers["Device Drivers"]
    Interrupt_Controller["Interrupt Controller"]
end

ArceOS_Kernel --> HandlerTable_Struct
Device_Drivers --> HandlerTable_Struct
HandlerTable_Struct --> handle_method
Hardware_Interrupts --> register_handler
IO_Completion --> register_handler
Interrupt_Controller --> HandlerTable_Struct
System_Calls --> register_handler
Task_Scheduler --> HandlerTable_Struct
Timer_Events --> register_handler
handle_method --> unregister_handler

The crate serves as a central dispatch mechanism that allows different ArceOS subsystems to register event handlers without requiring traditional locking mechanisms that would introduce latency and complexity in kernel code.

Sources: Cargo.toml(L1 - L15) 

No-std Environment Design

The handler_table crate is specifically designed for no_std environments, making it suitable for embedded systems and kernel-level code where the standard library is not available.

Design ConstraintImplementationArceOS Benefit
No heap allocationFixed-size arrays with compile-time boundsPredictable memory usage in kernel
No standard libraryCore atomics and primitive types onlyMinimal dependencies for embedded targets
Zero-cost abstractionsDirect atomic operations without runtime overheadHigh-performance event handling
Compile-time sizingGenericHandlerTablewith const parameterMemory layout known at build time

No-std Integration Flow

flowchart TD
subgraph ArceOS_Targets["ArceOS Target Architectures"]
    x86_64_none["x86_64-unknown-none"]
    riscv64_none["riscv64gc-unknown-none-elf"]
    aarch64_none["aarch64-unknown-none-softfloat"]
end
subgraph handler_table_Integration["handler_table Integration"]
    Core_Only["core:: imports only"]
    AtomicUsize_Array["AtomicUsize array storage"]
    Const_Generic["HandlerTable sizing"]
end
subgraph Build_Process["ArceOS Build Process"]
    Cargo_Config["Cargo Configuration"]
    Target_Spec["Target Specification"]
    no_std_Flag["#![no_std] Attribute"]
end

AtomicUsize_Array --> Const_Generic
Cargo_Config --> Core_Only
Const_Generic --> aarch64_none
Const_Generic --> riscv64_none
Const_Generic --> x86_64_none
Core_Only --> AtomicUsize_Array
Target_Spec --> Core_Only
no_std_Flag --> Core_Only

This design enables ArceOS to use handler_table across different target architectures without modification, supporting both traditional x86_64 systems and embedded RISC-V and ARM platforms.

Sources: Cargo.toml(L12) 

Integration Patterns

ArceOS components integrate with handler_table through several common patterns that leverage the crate's lock-free design.

Event Registration Pattern

sequenceDiagram
    participant ArceOSModule as "ArceOS Module"
    participant HandlerTableN as "HandlerTable<N>"
    participant AtomicUsizeidx as "AtomicUsize[idx]"

    Note over ArceOSModule,AtomicUsizeidx: Module Initialization Phase
    ArceOSModule ->> HandlerTableN: "register_handler(event_id, handler_fn)"
    HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)"
    alt Registration Success
        AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
        HandlerTableN -->> ArceOSModule: "true"
        Note over ArceOSModule: "Handler registered successfully"
    else Slot Already Occupied
        AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)"
        HandlerTableN -->> ArceOSModule: "false"
        Note over ArceOSModule: "Handle registration conflict"
    end
    Note over ArceOSModule,AtomicUsizeidx: Runtime Event Handling
    ArceOSModule ->> HandlerTableN: "handle(event_id)"
    HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "handler_ptr"
    alt Handler Exists
        HandlerTableN ->> HandlerTableN: "transmute & call handler"
        HandlerTableN -->> ArceOSModule: "true (handled)"
    else No Handler
        HandlerTableN -->> ArceOSModule: "false (not handled)"
    end

This pattern allows ArceOS modules to register event handlers during system initialization and efficiently dispatch events during runtime without locks.

Sources: Cargo.toml(L6) 

Repository and Documentation Structure

The crate is maintained as part of the broader ArceOS organization, with integration points designed to support the operating system's modular architecture.

ArceOS Organization Structure

flowchart TD
subgraph Integration_Points["Integration Points"]
    homepage_link["Homepage -> arceos repo"]
    keywords_arceos["Keywords: 'arceos'"]
    license_compat["Compatible licensing"]
end
subgraph Documentation["Documentation Infrastructure"]
    docs_rs["docs.rs documentation"]
    github_pages["GitHub Pages"]
    readme_docs["README.md examples"]
end
subgraph arceos_org["ArceOS Organization (GitHub)"]
    arceos_main["arceos (main repository)"]
    handler_table_repo["handler_table (this crate)"]
    other_crates["Other ArceOS crates"]
end

arceos_main --> handler_table_repo
handler_table_repo --> docs_rs
handler_table_repo --> github_pages
handler_table_repo --> other_crates
handler_table_repo --> readme_docs
homepage_link --> arceos_main
keywords_arceos --> arceos_main
license_compat --> arceos_main

The repository structure ensures that handler_table can be discovered and integrated by ArceOS developers while maintaining independent versioning and documentation.

Sources: Cargo.toml(L8 - L11) 

Version and Compatibility

The current version 0.1.2 indicates this is an early-stage crate that is being actively developed alongside the ArceOS ecosystem. The version strategy supports incremental improvements while maintaining API stability for core ArceOS components.

AspectCurrent StateArceOS Integration Impact
Version0.1.2Early development, compatible with ArceOS components
LicenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0Multiple license options for different ArceOS use cases
Edition2021Modern Rust features available for ArceOS development
DependenciesNoneZero-dependency design reduces ArceOS build complexity

The tri-license approach provides flexibility for different ArceOS deployment scenarios, supporting both open-source and commercial use cases.

Sources: Cargo.toml(L3 - L7)  Cargo.toml(L14 - L15) 

Lock-free Design Benefits

Relevant source files

Purpose and Scope

This document explains the lock-free design principles used in the handler_table crate and the specific benefits this approach provides for event handling in operating systems. We focus on how lock-free programming concepts enable high-performance, concurrent event dispatch without traditional synchronization primitives.

For information about how this crate integrates with ArceOS specifically, see ArceOS Integration. For detailed implementation specifics of the atomic operations, see Atomic Operations.

Lock-free Programming Fundamentals

Lock-free programming is a concurrency paradigm that eliminates the use of traditional locks (mutexes, semaphores) by relying on atomic operations to ensure thread safety. In the handler_table crate, this is achieved through the use of AtomicUsize arrays and carefully chosen memory ordering constraints.

Core Lock-free Principles in HandlerTable

flowchart TD
subgraph subGraph2["HandlerTable Implementation"]
    HANDLERS["handlers: [AtomicUsize; N]"]
    REG["register_handler()"]
    HANDLE["handle()"]
    UNREG["unregister_handler()"]
end
subgraph subGraph1["Lock-free HandlerTable Approach"]
    A2["Thread 2"]
    L2["Wait for Lock"]
    A3["Thread N"]
    L3["Wait for Lock"]
    R1["Release Lock"]
    O2["Modify Data"]
    B1["Thread 1"]
    CAS1["compare_exchange on AtomicUsize"]
    B2["Thread 2"]
    LOAD1["load from AtomicUsize"]
    B3["Thread N"]
    SWAP1["swap on AtomicUsize"]
    SUCCESS1["Immediate Success/Failure"]
    SUCCESS2["Immediate Read"]
    SUCCESS3["Immediate Exchange"]
end
subgraph subGraph0["Traditional Locking Approach"]
    A1["Thread 1"]
    L1["Acquire Lock"]
    A2["Thread 2"]
    L2["Wait for Lock"]
    A3["Thread N"]
    L3["Wait for Lock"]
    O1["Modify Data"]
    R1["Release Lock"]
    O2["Modify Data"]
    B1["Thread 1"]
    B2["Thread 2"]
end

A1 --> L1
A2 --> L2
A3 --> L3
B1 --> CAS1
B2 --> LOAD1
B3 --> SWAP1
CAS1 --> REG
CAS1 --> SUCCESS1
HANDLE --> HANDLERS
L1 --> O1
L2 --> O2
LOAD1 --> HANDLE
LOAD1 --> SUCCESS2
O1 --> R1
R1 --> L2
REG --> HANDLERS
SWAP1 --> SUCCESS3
SWAP1 --> UNREG
UNREG --> HANDLERS

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L30 - L37)  src/lib.rs(L58 - L70)  src/lib.rs(L42 - L52) 

Atomic Operations Mapping

The HandlerTable maps each operation to specific atomic primitives:

OperationAtomic MethodMemory OrderingPurpose
register_handlercompare_exchangeAcquire/RelaxedAtomically register if slot empty
handleloadAcquireRead handler pointer safely
unregister_handlerswapAcquireAtomically remove and return handler

Sources: src/lib.rs(L34 - L36)  src/lib.rs(L62)  src/lib.rs(L46) 

Benefits in Event Handling Systems

Non-blocking Event Processing

Lock-free design enables event handlers to be registered, executed, and unregistered without blocking other threads. This is critical in operating system kernels where interrupt handlers and system calls must execute with minimal latency.

sequenceDiagram
    participant InterruptContext as Interrupt Context
    participant HandlerTable as HandlerTable
    participant AtomicUsizeidx as AtomicUsize[idx]
    participant EventHandler as Event Handler

    Note over InterruptContext,EventHandler: Concurrent Event Processing
    par Registration in Kernel Thread
        InterruptContext ->> HandlerTable: "register_handler(IRQ_TIMER, timer_handler)"
        HandlerTable ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)"
        AtomicUsizeidx -->> HandlerTable: "Ok() - Registration Success"
    else Event Handling in Interrupt
        InterruptContext ->> HandlerTable: "handle(IRQ_TIMER)"
        HandlerTable ->> AtomicUsizeidx: "load(Ordering::Acquire)"
        AtomicUsizeidx -->> HandlerTable: "handler_ptr"
        HandlerTable ->> EventHandler: "Call timer_handler()"
        EventHandler -->> HandlerTable: "Handler Complete"
        HandlerTable -->> InterruptContext: "true - Event Handled"
    else Cleanup in User Thread
        InterruptContext ->> HandlerTable: "unregister_handler(IRQ_TIMER)"
        HandlerTable ->> AtomicUsizeidx: "swap(0, Ordering::Acquire)"
        AtomicUsizeidx -->> HandlerTable: "old_handler_ptr"
        HandlerTable -->> InterruptContext: "Some(timer_handler)"
    end

Sources: src/lib.rs(L30 - L37)  src/lib.rs(L58 - L70)  src/lib.rs(L42 - L52) 

Deterministic Performance Characteristics

Unlike mutex-based approaches, lock-free operations have bounded execution time regardless of concurrent access patterns:

CharacteristicLock-based ApproachLock-free HandlerTable
Worst-case latencyUnbounded (lock contention)Bounded (atomic operation time)
Priority inversionPossibleImpossible
Deadlock riskPresentAbsent
Context switchingRequired on contentionNever required

Sources: src/lib.rs(L4)  src/lib.rs(L34 - L36) 

Implementation in HandlerTable

Memory Layout and Atomic Storage

The HandlerTable<N> stores function pointers as usize values in an AtomicUsize array, leveraging the fact that function pointers have the same size as usize on supported architectures.

flowchart TD
subgraph subGraph2["Concurrent Operations"]
    CAS["compare_exchange(0, fn_ptr)"]
    LOAD["load() -> usize"]
    SWAP["swap(0) -> old_fn_ptr"]
end
subgraph subGraph1["Atomic Value Semantics"]
    ZERO["0 = Empty Slot"]
    FNPTR["non-zero = Handler Function Pointer"]
    TRANSMUTE["unsafe transmute<usize, fn()>"]
end
subgraph subGraph0["HandlerTable Memory Layout"]
    STRUCT["HandlerTable<N>"]
    ARRAY["handlers: [AtomicUsize; N]"]
    SLOT0["AtomicUsize[0]"]
    SLOT1["AtomicUsize[1]"]
    SLOTN["AtomicUsize[N-1]"]
end

ARRAY --> SLOT0
ARRAY --> SLOT1
ARRAY --> SLOTN
CAS --> SLOT0
FNPTR --> TRANSMUTE
LOAD --> SLOT1
SLOT0 --> ZERO
SLOT1 --> FNPTR
SLOTN --> FNPTR
STRUCT --> ARRAY
SWAP --> SLOTN

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L22 - L23)  src/lib.rs(L48)  src/lib.rs(L64) 

Memory Ordering Guarantees

The crate uses Ordering::Acquire for reads and exchanges, ensuring that:

  1. Handler registration becomes visible to other threads before any subsequent operations
  2. Handler execution observes all memory writes that happened before registration
  3. Handler unregistration synchronizes with any concurrent access attempts
// From register_handler - ensures visibility of registration
compare_exchange(0, handler as usize, Ordering::Acquire, Ordering::Relaxed)

// From handle - ensures handler sees all prior writes
load(Ordering::Acquire)

// From unregister_handler - ensures cleanup synchronization
swap(0, Ordering::Acquire)

Sources: src/lib.rs(L35)  src/lib.rs(L62)  src/lib.rs(L46) 

Performance and Scalability Benefits

Scalability Analysis

Lock-free design provides several scalability advantages for event-driven systems:

MetricImpactBenefit
Thread contentionEliminatedLinear scalability with core count
Cache coherencyMinimizedReduced memory bus traffic
Context switchesAvoidedLower CPU overhead
Real-time guaranteesPreservedPredictable worst-case execution time

No-std Environment Optimization

The #![no_std] design requirement makes lock-free programming essential, as traditional locking primitives are unavailable or too heavyweight for embedded/kernel environments.

flowchart TD
subgraph subGraph2["ArceOS Integration Benefits"]
    KERNEL["Kernel Space Usage"]
    INTERRUPT["Interrupt Context Safe"]
    EMBEDDED["Embedded Target Support"]
    REALTIME["Real-time Guarantees"]
end
subgraph subGraph1["no_std HandlerTable Design"]
    NOSTD["#![no_std]"]
    ATOMIC["core::sync::atomic"]
    CONST["const fn new()"]
    FIXED["Fixed-size Array"]
end
subgraph subGraph0["Traditional OS Constraints"]
    HEAP["Heap Allocation"]
    STDLIB["Standard Library"]
    THREADS["Thread Primitives"]
    MUTEX["Mutex/Condvar"]
end

ATOMIC --> KERNEL
ATOMIC --> REALTIME
CONST --> INTERRUPT
FIXED --> EMBEDDED
HEAP --> NOSTD
MUTEX --> NOSTD
NOSTD --> ATOMIC
NOSTD --> CONST
NOSTD --> FIXED
STDLIB --> NOSTD
THREADS --> NOSTD

Sources: src/lib.rs(L1)  src/lib.rs(L4)  src/lib.rs(L20 - L24) 

Memory Safety and Correctness

Safe Concurrent Access Patterns

The lock-free design maintains memory safety through several mechanisms:

  1. Atomic pointer operations: Function pointers are stored and accessed atomically
  2. ABA problem avoidance: Using compare_exchange prevents race conditions during registration
  3. Memory ordering: Acquire ordering ensures proper synchronization
  4. Bounded unsafe code: unsafe transmutation is isolated to well-defined conversion points
flowchart TD
subgraph subGraph2["Mitigation Strategies"]
    ZERO_CHECK["Zero Value = Empty Check"]
    CAS_ATOMICITY["compare_exchange Atomicity"]
    BOUNDS_CHECK["Array Bounds Validation"]
end
subgraph subGraph1["Potential Hazards"]
    TRANSMUTE["transmute<usize, fn()>"]
    NULL_DEREF["Null Function Pointer"]
    RACE_CONDITION["Registration Races"]
end
subgraph subGraph0["Safety Guarantees"]
    ATOMIC_OPS["Atomic Operations Only"]
    MEMORY_ORDER["Acquire Memory Ordering"]
    CAS_PROTECTION["CAS Prevents ABA"]
    ISOLATED_UNSAFE["Bounded unsafe Regions"]
end

ATOMIC_OPS --> CAS_ATOMICITY
CAS_PROTECTION --> CAS_ATOMICITY
ISOLATED_UNSAFE --> BOUNDS_CHECK
MEMORY_ORDER --> CAS_ATOMICITY
NULL_DEREF --> ZERO_CHECK
RACE_CONDITION --> CAS_ATOMICITY
TRANSMUTE --> ZERO_CHECK

Sources: src/lib.rs(L48)  src/lib.rs(L64)  src/lib.rs(L31 - L32)  src/lib.rs(L43 - L44)  src/lib.rs(L59 - L60) 

The lock-free design of HandlerTable provides essential performance and safety characteristics required for kernel-level event handling while maintaining the simplicity needed for embedded and real-time systems.

User Guide

Relevant source files

This guide provides practical instructions for using the handler_table crate in your applications. The guide covers the complete API usage workflow, from initialization through event handling and cleanup. For detailed API documentation, see API Reference, and for comprehensive examples, see Usage Examples. For implementation details and internals, see Implementation Details.

Overview

The HandlerTable<N> provides a lock-free, fixed-size table for registering and invoking event handlers. It is designed for no_std environments where traditional synchronization primitives are unavailable or undesirable. The table uses atomic operations to ensure thread safety without blocking.

Core Usage Pattern:

flowchart TD
Create["HandlerTable::new()"]
Register["register_handler(idx, fn)"]
Handle["handle(idx)"]
Unregister["unregister_handler(idx)"]
Handle2["handle(other_idx)"]

Create --> Register
Handle --> Unregister
Register --> Handle
Register --> Handle2

Sources: README.md(L1 - L32) 

Key Concepts

The HandlerTable<N> operates on several fundamental concepts:

ConceptDescriptionCode Entity
Fixed SizeTable size determined at compile timeHandlerTablegeneric parameter
Event IndexNumeric identifier for each event typeidxparameter in methods
Handler FunctionZero-argument closure or function pointerFnOnce()trait bound
Atomic StorageLock-free concurrent accessInternalAtomicUsizearray

Handler Table Lifecycle


Sources: README.md(L11 - L31) 

Quick Start Example

The basic usage follows a simple pattern of registration, handling, and optional cleanup:

use handler_table::HandlerTable;

// Create a table with 8 slots
static TABLE: HandlerTable<8> = HandlerTable::new();

// Register handlers for specific event indices
TABLE.register_handler(0, || {
    println!("Hello, event 0!");
});
TABLE.register_handler(1, || {
    println!("Hello, event 1!");
});

// Handle events by index
assert!(TABLE.handle(0));  // Returns true, prints message
assert!(!TABLE.handle(2)); // Returns false, no handler registered

// Unregister and retrieve handlers
let handler = TABLE.unregister_handler(1).unwrap();
handler(); // Can still call the retrieved handler

Sources: README.md(L11 - L31) 

API Workflow

The following diagram shows how the main API methods interact with the internal atomic storage:

sequenceDiagram
    participant UserCode as "User Code"
    participant HandlerTableN as "HandlerTable<N>"
    participant AtomicUsizeidx as "AtomicUsize[idx]"

    Note over UserCode,AtomicUsizeidx: "Registration Phase"
    UserCode ->> HandlerTableN: "register_handler(idx, fn)"
    HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)"
    alt "Slot Empty"
        AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
        HandlerTableN -->> UserCode: "true (success)"
    else "Slot Occupied"
        AtomicUsizeidx -->> HandlerTableN: "Err(current_ptr)"
        HandlerTableN -->> UserCode: "false (already registered)"
    end
    Note over UserCode,AtomicUsizeidx: "Execution Phase"
    UserCode ->> HandlerTableN: "handle(idx)"
    HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "handler_ptr or 0"
    alt "Handler Present"
        HandlerTableN ->> HandlerTableN: "transmute and call handler"
        HandlerTableN -->> UserCode: "true (handled)"
    else "No Handler"
        HandlerTableN -->> UserCode: "false (not handled)"
    end
    Note over UserCode,AtomicUsizeidx: "Cleanup Phase"
    UserCode ->> HandlerTableN: "unregister_handler(idx)"
    HandlerTableN ->> AtomicUsizeidx: "swap(0, Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "old_handler_ptr"
    HandlerTableN -->> UserCode: "Some(handler_fn) or None"

Sources: README.md(L16 - L30) 

Core API Methods

The HandlerTable<N> provides four essential methods for handler management:

Initialization

  • HandlerTable::new() - Creates a new empty handler table with all slots initialized to zero

Registration

  • register_handler(idx: usize, handler: impl FnOnce()) - Atomically registers a handler function at the specified index
  • Returns true if registration succeeds
  • Returns false if a handler is already registered at that index

Execution

  • handle(idx: usize) - Invokes the handler registered at the specified index
  • Returns true if a handler was found and executed
  • Returns false if no handler is registered at that index

Cleanup

  • unregister_handler(idx: usize) - Atomically removes and returns the handler at the specified index
  • Returns Some(handler) if a handler was registered
  • Returns None if no handler was registered
flowchart TD
subgraph subGraph1["Internal AtomicUsize Array"]
    slot0["slots[0]: AtomicUsize"]
    slot1["slots[1]: AtomicUsize"]
    slotN["slots[N-1]: AtomicUsize"]
end
subgraph subGraph0["HandlerTable Public API"]
    new["new() -> HandlerTable"]
    register["register_handler(idx, fn) -> bool"]
    handle["handle(idx) -> bool"]
    unregister["unregister_handler(idx) -> Option"]
end

handle --> slot0
handle --> slot1
handle --> slotN
new --> slot0
new --> slot1
new --> slotN
register --> slot0
register --> slot1
register --> slotN
unregister --> slot0
unregister --> slot1
unregister --> slotN

Sources: README.md(L16 - L30) 

Best Practices

Size Selection

Choose the table size N based on your maximum expected number of concurrent event types. The size is fixed at compile time and cannot be changed dynamically.

Index Management

  • Use consistent index values across your application
  • Consider defining constants for event indices to avoid magic numbers
  • Ensure indices are within bounds (0 to N-1)

Handler Design

  • Keep handlers lightweight and fast-executing
  • Avoid blocking operations within handlers
  • Consider handler re-entrancy if the same index might be triggered during execution

Concurrent Usage

  • Multiple threads can safely call any method simultaneously
  • Registration conflicts return false rather than blocking
  • Handler execution is atomic but the handler itself may not be re-entrant

Error Handling

  • Check return values from register_handler() to handle registration conflicts
  • Check return values from handle() to distinguish between handled and unhandled events
  • Use the return value from unregister_handler() to verify successful removal

Sources: README.md(L1 - L32) 

API Reference

Relevant source files

This page provides comprehensive documentation for all public APIs in the handler_table crate. It covers the core HandlerTable<N> data structure, its methods, and the Handler type alias.

For practical usage examples and integration patterns, see Usage Examples. For detailed information about the underlying atomic operations and memory safety guarantees, see Implementation Details.

Core Types

Handler Type Alias

The Handler type represents a function pointer to an event handler with no parameters and no return value.

pub type Handler = fn();

Key Characteristics:

  • Zero-argument function pointer
  • No return value
  • Suitable for simple event callbacks
  • Compatible with closures that capture no variables

Sources: src/lib.rs(L6 - L9) 

HandlerTable Struct

The HandlerTable<N> is a generic data structure that stores up to N event handlers in a lock-free manner using atomic operations.

pub struct HandlerTable<const N: usize> {
    handlers: [AtomicUsize; N],
}

Key Characteristics:

  • Compile-time fixed size determined by const generic N
  • Uses AtomicUsize array for lock-free operation
  • Stores function pointers as usize values via transmutation
  • Zero-initialized (empty) slots contain value 0

Sources: src/lib.rs(L11 - L16) 

API Structure Overview

flowchart TD
subgraph subGraph2["Atomic Operations"]
    compare_exchange["compare_exchange(0, handler_ptr)"]
    swap["swap(0)"]
    load["load(Ordering::Acquire)"]
end
subgraph subGraph1["Core Types"]
    Handler["Handler = fn()"]
    AtomicArray["[AtomicUsize; N]"]
end
subgraph subGraph0["HandlerTable Public API"]
    new["new() -> Self"]
    register["register_handler(idx, handler) -> bool"]
    unregister["unregister_handler(idx) -> Option"]
    handle["handle(idx) -> bool"]
    default["Default::default() -> Self"]
end

AtomicArray --> compare_exchange
AtomicArray --> load
AtomicArray --> swap
Handler --> register
default --> new
handle --> load
new --> AtomicArray
register --> compare_exchange
unregister --> swap

Sources: src/lib.rs(L18 - L77) 

Constructor Methods

new()

Creates a new HandlerTable<N> with all handler slots initialized to empty.

Signature:

pub const fn new() -> Self

Behavior:

  • Initializes all N slots to zero using AtomicUsize::new(0)
  • Can be used in const contexts for static allocation
  • All slots are initially unregistered

Example Usage:

static TABLE: HandlerTable<8> = HandlerTable::new();

Sources: src/lib.rs(L19 - L24) 

Default Implementation

The Default trait is implemented to delegate to new().

Signature:

impl<const N: usize> Default for HandlerTable<N>

Behavior:

  • Calls Self::new() internally
  • Provides standard Rust default initialization pattern

Sources: src/lib.rs(L73 - L77) 

Handler Registration

register_handler()

Registers an event handler for a specific index slot.

Signature:

#![allow(unused)]
fn main() {
pub fn register_handler(&self, idx: usize, handler: Handler) -> bool
}

Parameters:

ParameterTypeDescription
idxusizeIndex of the handler slot (must be < N)
handlerHandlerFunction pointer to register

Return Value:

  • true - Registration successful
  • false - Registration failed (index out of bounds or slot already occupied)

Behavior:

  1. Validates idx < N
  2. Attempts atomic compare-and-exchange from 0 to handler as usize
  3. Uses Ordering::Acquire for success and Ordering::Relaxed for failure
  4. Fails if slot is already occupied (non-zero value)

Atomic Operation Details:

sequenceDiagram
    participant Client as Client
    participant HandlerTable as HandlerTable
    participant AtomicSlotAtomicUsizeidx as AtomicSlot["AtomicUsize[idx]"]
    participant AtomicSlot as AtomicSlot

    Client ->> HandlerTable: "register_handler(idx, handler)"
    HandlerTable ->> HandlerTable: "Validate idx < N"
    HandlerTable ->> AtomicSlot: "compare_exchange(0, handler_ptr, Acquire, Relaxed)"
    alt "Slot is empty (0)"
        AtomicSlot -->> HandlerTable: "Ok(0)"
        HandlerTable -->> Client: "true"
    else "Slot occupied"
        AtomicSlot -->> HandlerTable: "Err(current_value)"
        HandlerTable -->> Client: "false"
    end

Sources: src/lib.rs(L26 - L37) 

Handler Deregistration

unregister_handler()

Removes and returns the handler for a specific index slot.

Signature:

#![allow(unused)]
fn main() {
pub fn unregister_handler(&self, idx: usize) -> Option<Handler>
}

Parameters:

ParameterTypeDescription
idxusizeIndex of the handler slot to unregister

Return Value:

  • Some(Handler) - Previously registered handler
  • None - No handler was registered or index out of bounds

Behavior:

  1. Validates idx < N
  2. Performs atomic swap with 0 using Ordering::Acquire
  3. If previous value was non-zero, transmutes back to Handler
  4. Returns None if slot was already empty

Safety Considerations:

  • Uses unsafe { core::mem::transmute::<usize, fn()>(handler) } for pointer conversion
  • Safe because only valid function pointers are stored via register_handler

Sources: src/lib.rs(L39 - L52) 

Event Handling

handle()

Executes the handler registered for a specific index.

Signature:

#![allow(unused)]
fn main() {
pub fn handle(&self, idx: usize) -> bool
}

Parameters:

ParameterTypeDescription
idxusizeIndex of the event to handle

Return Value:

  • true - Event was handled (handler existed and was called)
  • false - No handler registered or index out of bounds

Behavior:

  1. Validates idx < N
  2. Loads handler value atomically with Ordering::Acquire
  3. If non-zero, transmutes to Handler and calls it
  4. Handler execution is synchronous

Event Handling Flow:

flowchart TD
Start["handle(idx)"]
ValidateIdx["idx < N ?"]
ReturnFalse1["return false"]
LoadHandler["load(Ordering::Acquire)"]
CheckHandler["handler != 0 ?"]
ReturnFalse2["return false"]
Transmute["unsafe transmute to Handler"]
CallHandler["handler()"]
ReturnTrue["return true"]

CallHandler --> ReturnTrue
CheckHandler --> ReturnFalse2
CheckHandler --> Transmute
LoadHandler --> CheckHandler
Start --> ValidateIdx
Transmute --> CallHandler
ValidateIdx --> LoadHandler
ValidateIdx --> ReturnFalse1

Safety Considerations:

  • Uses unsafe { core::mem::transmute(handler) } for pointer conversion
  • Safe because only valid function pointers can be stored in slots

Sources: src/lib.rs(L54 - L70) 

Memory Ordering and Thread Safety

All atomic operations use Ordering::Acquire for loads and successful compare-exchange operations, ensuring proper synchronization between threads.

OperationMemory OrderingPurpose
compare_exchangesuccessOrdering::AcquireSynchronize handler registration
compare_exchangefailureOrdering::RelaxedNo synchronization needed on failure
swapOrdering::AcquireSynchronize handler removal
loadOrdering::AcquireSynchronize handler access

Thread Safety Guarantees:

  • Multiple threads can safely call any method concurrently
  • Registration is atomic and will never corrupt existing handlers
  • Handler execution is exclusive per slot but concurrent across slots
  • No locks or blocking operations are used

Sources: src/lib.rs(L34 - L62) 

Index Bounds Validation

All methods that accept an idx parameter perform bounds checking against the compile-time constant N.

Validation Pattern:

if idx >= N {
    return false; // or None for unregister_handler
}

Characteristics:

  • Bounds check occurs before any atomic operations
  • Out-of-bounds access returns failure indicators rather than panicking
  • Provides safe API even with invalid indices

Sources: src/lib.rs(L31 - L61) 

Usage Examples

Relevant source files

This document provides practical examples demonstrating how to use the HandlerTable API in real-world scenarios. It covers common usage patterns, integration strategies, and best practices for implementing lock-free event handling in no_std environments.

For detailed API documentation, see API Reference. For implementation details about the underlying atomic operations, see Implementation Details.

Basic Event Handler Registration

The fundamental usage pattern involves creating a static HandlerTable, registering event handlers, and dispatching events as they occur.

Static Table Creation and Handler Registration


The basic pattern demonstrated in README.md(L11 - L31)  shows:

OperationMethodPurpose
CreationHandlerTable::new()Initialize empty handler table
Registrationregister_handler(idx, fn)Associate handler function with event ID
Dispatchhandle(idx)Execute registered handler for event
Cleanupunregister_handler(idx)Remove and retrieve handler function

Sources: README.md(L11 - L31) 

Event Registration Patterns

Single Event Handler Registration

The most common pattern involves registering individual handlers for specific event types. Each handler is a closure or function pointer that gets executed when the corresponding event occurs.

sequenceDiagram
    participant EventSource as "Event Source"
    participant HandlerTableN as "HandlerTable<N>"
    participant AtomicUsizeidx as "AtomicUsize[idx]"
    participant EventHandlerFn as "Event Handler Fn"

    EventSource ->> HandlerTableN: "register_handler(0, closure)"
    HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, fn_ptr)"
    alt "Registration Success"
        AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
        HandlerTableN -->> EventSource: "true"
    else "Slot Occupied"
        AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)"
        HandlerTableN -->> EventSource: "false"
    end
    Note over EventSource,EventHandlerFn: "Later: Event Dispatch"
    EventSource ->> HandlerTableN: "handle(0)"
    HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "fn_ptr"
    HandlerTableN ->> EventHandlerFn: "transmute + call()"
    EventHandlerFn -->> HandlerTableN: "execution complete"
    HandlerTableN -->> EventSource: "true"

Sources: README.md(L16 - L21) 

Multiple Handler Registration

For systems handling multiple event types, handlers are typically registered during initialization:

flowchart TD
subgraph subGraph2["Handler Functions"]
    TimerFn["timer_interrupt_handler"]
    IOFn["io_completion_handler"]
    UserFn["user_signal_handler"]
end
subgraph subGraph1["HandlerTable Operations"]
    Reg0["register_handler(0, timer_fn)"]
    Reg1["register_handler(1, io_fn)"]
    Reg2["register_handler(2, user_fn)"]
end
subgraph subGraph0["System Initialization Phase"]
    Init["System Init"]
    Timer["Timer Handler Setup"]
    IO["I/O Handler Setup"]
    User["User Event Setup"]
end

IO --> Reg1
Init --> IO
Init --> Timer
Init --> User
Reg0 --> TimerFn
Reg1 --> IOFn
Reg2 --> UserFn
Timer --> Reg0
User --> Reg2

Sources: README.md(L16 - L21) 

Event Handling Scenarios

Successful Event Dispatch

When an event occurs and a handler is registered, the handle method executes the associated function and returns true:

flowchart TD
subgraph subGraph0["Example from README"]
    Ex1["handle(0) -> true"]
    Ex2["handle(2) -> false"]
end
Event["Event Occurs"]
Handle["handle(event_id)"]
Check["Check AtomicUsize[event_id]"]
Found["Handler Found?"]
Execute["Execute Handler Function"]
ReturnFalse["Return false"]
ReturnTrue["Return true"]

Check --> Found
Event --> Handle
Execute --> ReturnTrue
Found --> Execute
Found --> ReturnFalse
Handle --> Check

The example in README.md(L23 - L24)  demonstrates both successful dispatch (handle(0) returns true) and missing handler scenarios (handle(2) returns false).

Sources: README.md(L23 - L24) 

Handler Retrieval and Cleanup

The unregister_handler method provides atomic removal and retrieval of handler functions:

flowchart TD
subgraph subGraph2["Example Calls"]
    Unreg1["unregister_handler(2) -> None"]
    Unreg2["unregister_handler(1) -> Some(fn)"]
end
subgraph subGraph1["Return Values"]
    Some["Some(handler_fn)"]
    None["None"]
end
subgraph subGraph0["Unregistration Process"]
    Call["unregister_handler(idx)"]
    Swap["atomic_swap(0)"]
    Check["Check Previous Value"]
    Result["Option"]
end

Call --> Swap
Check --> Result
None --> Unreg1
Result --> None
Result --> Some
Some --> Unreg2
Swap --> Check

The pattern shown in README.md(L26 - L28)  demonstrates retrieving a previously registered handler function, which can then be called directly.

Sources: README.md(L26 - L30) 

Advanced Usage Patterns

Concurrent Handler Management

In multi-threaded environments, multiple threads can safely register, handle, and unregister events without synchronization primitives:

flowchart TD
subgraph subGraph2["Memory Ordering"]
    Acquire["Acquire Ordering"]
    Release["Release Ordering"]
    Consistency["Memory Consistency"]
end
subgraph subGraph1["Atomic Operations Layer"]
    CAS["compare_exchange"]
    Load["load(Acquire)"]
    Swap["swap(Acquire)"]
end
subgraph subGraph0["Thread Safety Guarantees"]
    T1["Thread 1: register_handler"]
    T2["Thread 2: handle"]
    T3["Thread 3: unregister_handler"]
end

Acquire --> Consistency
CAS --> Release
Load --> Acquire
Release --> Consistency
Swap --> Acquire
T1 --> CAS
T2 --> Load
T3 --> Swap

Sources: README.md(L14) 

Static vs Dynamic Handler Tables

The compile-time sized HandlerTable<N> enables static allocation suitable for no_std environments:

ApproachDeclarationUse Case
Static Globalstatic TABLE: HandlerTable<8>System-wide event handling
Local Instancelet table = HandlerTable::<16>::new()Component-specific events
Const GenericHandlerTable<MAX_EVENTS>Configurable event capacity

The static approach shown in README.md(L14)  is preferred for kernel-level integration where heap allocation is unavailable.

Sources: README.md(L14) 

Integration Examples

ArceOS Kernel Integration

In the ArceOS operating system context, HandlerTable serves as the central event dispatch mechanism:

flowchart TD
subgraph subGraph2["Event Dispatch Flow"]
    Dispatch["event_dispatch(id)"]
    Handle["TABLE.handle(id)"]
    Execute["handler_function()"]
end
subgraph subGraph1["HandlerTable Integration"]
    TABLE["static KERNEL_EVENTS: HandlerTable<32>"]
    IRQ["IRQ_HANDLERS[0..15]"]
    TIMER["TIMER_EVENTS[16..23]"]
    SCHED["SCHED_EVENTS[24..31]"]
end
subgraph subGraph0["ArceOS Kernel Components"]
    Interrupt["Interrupt Controller"]
    Timer["Timer Subsystem"]
    Scheduler["Task Scheduler"]
    Syscall["System Call Handler"]
end

Dispatch --> Handle
Handle --> Execute
IRQ --> TABLE
Interrupt --> IRQ
SCHED --> TABLE
Scheduler --> SCHED
Syscall --> TABLE
TABLE --> Dispatch
TIMER --> TABLE
Timer --> TIMER

Sources: README.md(L7) 

Device Driver Event Handling

Device drivers can use dedicated handler tables for managing I/O completion events:

flowchart TD
subgraph subGraph2["Event Types"]
    Complete["DMA_COMPLETE"]
    Error["DMA_ERROR"]
    Timeout["DMA_TIMEOUT"]
end
subgraph subGraph1["Event Management"]
    DriverTable["HandlerTable"]
    CompletionFn["io_completion_handler"]
    ErrorFn["io_error_handler"]
end
subgraph subGraph0["Driver Architecture"]
    Driver["Device Driver"]
    DMA["DMA Controller"]
    Buffer["I/O Buffers"]
end

Complete --> CompletionFn
CompletionFn --> Buffer
DMA --> Complete
DMA --> Error
DMA --> Timeout
Driver --> DriverTable
Error --> ErrorFn
ErrorFn --> Buffer
Timeout --> ErrorFn

This pattern allows drivers to maintain separate event handling contexts while leveraging the same lock-free infrastructure.

Sources: README.md(L7) 

Implementation Details

Relevant source files

This document provides a deep technical analysis of the HandlerTable<N> implementation, covering the internal architecture, atomic operations strategy, and lock-free design patterns. It explains how function pointers are stored atomically, the memory ordering guarantees, and the safety mechanisms that enable concurrent access without locks.

For high-level usage information, see User Guide. For atomic operations theory, see Atomic Operations.

Core Data Structure

The HandlerTable<N> struct implements a fixed-size, lock-free event handler registry using a compile-time constant generic parameter N to define the maximum number of event handlers.

HandlerTable Structure

flowchart TD
subgraph subGraph2["Value Interpretation"]
    Zero["0 = Empty Slot"]
    NonZero["Non-zero = Function Pointer"]
    Transmute["unsafe transmute"]
end
subgraph subGraph1["Handler Storage"]
    H0["handlers[0]: AtomicUsize"]
    H1["handlers[1]: AtomicUsize"]
    HN["handlers[N-1]: AtomicUsize"]
end
subgraph HandlerTable<N>["HandlerTable"]
    HT["HandlerTable"]
    Array["handlers: [AtomicUsize; N]"]
end

Array --> H0
Array --> H1
Array --> HN
H0 --> Zero
H1 --> NonZero
HN --> Zero
HT --> Array
NonZero --> Transmute

The fundamental design stores function pointers as usize values within AtomicUsize containers. An empty slot is represented by the value 0, while registered handlers store the function pointer cast to usize. This design enables atomic operations on what are essentially function pointer references.

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L21 - L23) 

Initialization Pattern

The constructor uses a const-evaluated array initialization pattern that ensures all atomic slots begin in the empty state:

flowchart TD
subgraph subGraph1["Memory Layout"]
    Slot0["AtomicUsize(0)"]
    Slot1["AtomicUsize(0)"]
    SlotN["AtomicUsize(0)"]
end
subgraph subGraph0["new() Construction"]
    New["HandlerTable::new()"]
    ConstExpr["const { AtomicUsize::new(0) }"]
    ArrayInit["[...; N]"]
end

ArrayInit --> Slot0
ArrayInit --> Slot1
ArrayInit --> SlotN
ConstExpr --> ArrayInit
New --> ConstExpr

The const { AtomicUsize::new(0) } expression leverages Rust's const evaluation to initialize each array element at compile time, ensuring zero-cost initialization and immediate readiness for concurrent access.

Sources: src/lib.rs(L19 - L24) 

Atomic Operations Strategy

The implementation employs three distinct atomic operations, each optimized for its specific use case within the lock-free protocol.

Operation Mapping

MethodAtomic OperationSuccess ConditionFailure Behavior
register_handlercompare_exchangeSlot was empty (0)Returns false
unregister_handlerswapAlways succeedsReturns None if empty
handleloadAlways succeedsReturns false if empty

Registration Protocol

sequenceDiagram
    participant Client as Client
    participant HandlerTable as HandlerTable
    participant AtomicSlothandlersidx as AtomicSlot["handlers[idx]"]
    participant AtomicSlot as AtomicSlot

    Client ->> HandlerTable: "register_handler(idx, handler)"
    HandlerTable ->> HandlerTable: "Check idx < N"
    HandlerTable ->> AtomicSlot: "compare_exchange(0, handler as usize, Acquire, Relaxed)"
    alt "Slot was empty"
        AtomicSlot -->> HandlerTable: "Ok(0)"
        HandlerTable -->> Client: "true"
    else "Slot occupied"
        AtomicSlot -->> HandlerTable: "Err(current_value)"
        HandlerTable -->> Client: "false"
    end

The compare_exchange operation provides atomic test-and-set functionality, ensuring that only one thread can successfully register a handler for any given index. The operation uses Ordering::Acquire for success to establish happens-before relationships with subsequent operations.

Sources: src/lib.rs(L30 - L37) 

Deregistration Protocol

The swap operation unconditionally exchanges the current value with 0, returning the previous value. This ensures atomic removal regardless of the current state:

flowchart TD
subgraph subGraph0["unregister_handler Flow"]
    Input["unregister_handler(idx)"]
    BoundsCheck["idx < N ?"]
    SwapOp["swap(0, Ordering::Acquire)"]
    ReturnNone["None"]
    CheckResult["handler != 0 ?"]
    Transmute["unsafe transmute"]
    ReturnSome["Some(handler)"]
end

BoundsCheck --> ReturnNone
BoundsCheck --> SwapOp
CheckResult --> ReturnNone
CheckResult --> Transmute
Input --> BoundsCheck
SwapOp --> CheckResult
Transmute --> ReturnSome

Sources: src/lib.rs(L42 - L52) 

Function Pointer Storage and Safety

The core challenge in this implementation is safely storing and retrieving function pointers through atomic operations. Since AtomicPtr<fn()> doesn't exist in stable Rust, the implementation uses AtomicUsize with unsafe transmutation.

Pointer-to-Integer Conversion

flowchart TD
subgraph subGraph1["Safety Invariants"]
    NonNull["Non-null Function Pointers"]
    ValidCode["Valid Code Addresses"]
    SameLifetime["Pointer Lifetime ≥ Table Lifetime"]
end
subgraph subGraph0["Storage Mechanism"]
    FnPtr["fn() - Function Pointer"]
    AsUsize["handler as usize"]
    AtomicStore["AtomicUsize::store/load"]
    TransmuteBack["transmute"]
    CallHandler["handler()"]
end

AsUsize --> AtomicStore
AtomicStore --> TransmuteBack
FnPtr --> AsUsize
FnPtr --> NonNull
FnPtr --> SameLifetime
FnPtr --> ValidCode
TransmuteBack --> CallHandler

The implementation relies on several critical safety assumptions:

  • Function pointers are never null (guaranteed by Rust's type system)
  • Function pointers have the same size as usize on the target platform
  • Function addresses remain valid for the lifetime of the HandlerTable
  • The transmutation between usize and fn() preserves the pointer value

Sources: src/lib.rs(L48)  src/lib.rs(L64) 

Event Handling Execution

The handle method demonstrates the complete pointer recovery and execution sequence:

flowchart TD
subgraph subGraph0["handle Method Flow"]
    LoadPtr["load(Ordering::Acquire)"]
    CheckZero["handler == 0 ?"]
    Transmute["unsafe transmute(handler)"]
    ReturnFalse["return false"]
    Execute["handler()"]
    ReturnTrue["return true"]
end

CheckZero --> ReturnFalse
CheckZero --> Transmute
Execute --> ReturnTrue
LoadPtr --> CheckZero
Transmute --> Execute

The atomic load with Ordering::Acquire ensures that any writes to memory performed by the registering thread before the compare_exchange are visible to the handling thread.

Sources: src/lib.rs(L58 - L70) 

Memory Ordering Guarantees

The implementation uses a carefully chosen set of memory orderings to balance performance with correctness guarantees.

Ordering Strategy

OperationSuccess OrderingFailure OrderingRationale
compare_exchangeAcquireRelaxedSynchronize with prior writes
swapAcquireN/AEnsure visibility of handler execution
loadAcquireN/ASee registration writes

Happens-Before Relationships

flowchart TD
subgraph subGraph1["Thread B - Execution"]
    HandleCall["handle(idx)"]
    LoadHandler["load(Ordering::Acquire)"]
    ExecuteHandler["handler()"]
end
subgraph subGraph0["Thread A - Registration"]
    SetupHandler["Setup handler environment"]
    RegisterCall["register_handler(idx, fn)"]
    CompareExchange["compare_exchange(..., Acquire, Relaxed)"]
end
subgraph subGraph2["Memory Ordering Chain"]
    HappensBefore["Happens-Before Edge"]
end

CompareExchange --> LoadHandler
LoadHandler --> ExecuteHandler
RegisterCall --> CompareExchange
SetupHandler --> RegisterCall

The Acquire ordering on successful registration creates a happens-before relationship with the Acquire load during event handling, ensuring that any memory operations completed before registration are visible during handler execution.

Sources: src/lib.rs(L35)  src/lib.rs(L46)  src/lib.rs(L62) 

Lock-free Design Patterns

The HandlerTable implementation demonstrates several fundamental lock-free programming patterns adapted for event handling scenarios.

Compare-and-Swap Pattern

The registration mechanism implements the classic compare-and-swap pattern for atomic updates:

flowchart TD
subgraph subGraph0["CAS Loop Pattern"]
    ReadCurrent["Read Current Value"]
    PrepareNew["Prepare New Value"]
    CASAttempt["Compare-and-Swap"]
    Success["Success"]
    Retry["Retry"]
end

CASAttempt --> Retry
CASAttempt --> Success
PrepareNew --> CASAttempt
ReadCurrent --> PrepareNew
Retry --> ReadCurrent

However, the handler table simplifies this pattern by only allowing transitions from empty (0) to occupied (non-zero), eliminating the need for retry loops.

ABA Problem Avoidance

The design inherently avoids the ABA problem through its state transition model:

From StateTo StateOperationABA Risk
Empty (0)Handler (ptr)register_handlerNone - single transition
Handler (ptr)Empty (0)unregister_handlerNone - swap is atomic
AnyAnyhandleNone - read-only

The absence of complex state transitions and the use of swap for deregistration eliminate scenarios where the ABA problem could manifest.

Sources: src/lib.rs(L30 - L37)  src/lib.rs(L42 - L52)  src/lib.rs(L58 - L70) 

Atomic Operations

Relevant source files

This document explains the atomic operations that form the foundation of the HandlerTable's lock-free design. It covers the specific atomic primitives used, memory ordering guarantees, and how these operations ensure thread safety without traditional locking mechanisms.

For information about the overall implementation architecture and memory safety aspects, see Memory Layout and Safety. For practical usage of the lock-free API, see API Reference.

Atomic Data Structure Foundation

The HandlerTable uses an array of AtomicUsize values as its core storage mechanism. Each slot in the array can atomically hold either a null value (0) or a function pointer cast to usize.

Core Atomic Array Structure

flowchart TD
subgraph States["Slot States"]
    Empty["Empty: 0"]
    Occupied["Occupied: handler as usize"]
end
subgraph AtomicSlots["Atomic Storage Slots"]
    Slot0["AtomicUsize[0]Value: 0 or fn_ptr"]
    Slot1["AtomicUsize[1]Value: 0 or fn_ptr"]
    SlotN["AtomicUsize[N-1]Value: 0 or fn_ptr"]
end
subgraph HandlerTable["HandlerTable<N>"]
    Array["handlers: [AtomicUsize; N]"]
end

Array --> Slot0
Array --> Slot1
Array --> SlotN
Slot0 --> Empty
Slot0 --> Occupied
Slot1 --> Empty
Slot1 --> Occupied
SlotN --> Empty
SlotN --> Occupied

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L21 - L23) 

The atomic array is initialized with zero values using const generics, ensuring compile-time allocation without heap usage. Each AtomicUsize slot represents either an empty handler position (value 0) or contains a function pointer cast to usize.

Core Atomic Operations

The HandlerTable implements three primary atomic operations that provide lock-free access to the handler storage.

Compare-Exchange Operation

The compare_exchange operation is used in register_handler to atomically install a new handler only if the slot is currently empty.

sequenceDiagram
    participant ClientThread as "Client Thread"
    participant AtomicUsizeidx as "AtomicUsize[idx]"
    participant MemorySystem as "Memory System"

    ClientThread ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr, Acquire, Relaxed)"
    AtomicUsizeidx ->> MemorySystem: "Check current value == 0"
    alt Current value is 0
        MemorySystem ->> AtomicUsizeidx: "Replace with handler_ptr"
        AtomicUsizeidx ->> ClientThread: "Ok(0)"
        Note over ClientThread: "Registration successful"
    else Current value != 0
        MemorySystem ->> AtomicUsizeidx: "Keep current value"
        AtomicUsizeidx ->> ClientThread: "Err(current_value)"
        Note over ClientThread: "Registration failed - slot occupied"
    end

Sources: src/lib.rs(L34 - L36) 

The operation uses Ordering::Acquire for success and Ordering::Relaxed for failure, ensuring proper synchronization when a handler is successfully installed.

Atomic Swap Operation

The swap operation in unregister_handler atomically replaces the current handler with zero and returns the previous value.

flowchart TD
Start["swap(0, Ordering::Acquire)"]
Read["Atomically read current value"]
Write["Atomically write 0"]
Check["Current value != 0?"]
Convert["transmute<usize, fn()>(value)"]
ReturnSome["Return Some(handler)"]
ReturnNone["Return None"]

Check --> Convert
Check --> ReturnNone
Convert --> ReturnSome
Read --> Write
Start --> Read
Write --> Check

Sources: src/lib.rs(L46)  src/lib.rs(L47 - L51) 

Atomic Load Operation

The load operation in handle reads the current handler value without modification, using acquire ordering to ensure proper synchronization.

flowchart TD
subgraph Results["Possible Results"]
    Success["Return true"]
    NoHandler["Return false"]
end
subgraph LoadOperation["Atomic Load Process"]
    Load["load(Ordering::Acquire)"]
    Check["Check value != 0"]
    Transmute["unsafe transmute<usize, Handler>"]
    Call["handler()"]
end

Call --> Success
Check --> NoHandler
Check --> Transmute
Load --> Check
Transmute --> Call

Sources: src/lib.rs(L62)  src/lib.rs(L63 - L66) 

Memory Ordering Guarantees

The atomic operations use specific memory ordering to ensure correct synchronization across threads while minimizing performance overhead.

Ordering Usage Patterns

OperationSuccess OrderingFailure OrderingPurpose
compare_exchangeAcquireRelaxedSynchronize on successful registration
swapAcquireN/ASynchronize when removing handler
loadAcquireN/ASynchronize when reading handler

Sources: src/lib.rs(L35)  src/lib.rs(L46)  src/lib.rs(L62) 

Acquire Ordering Semantics

flowchart TD
subgraph Ordering["Memory Ordering Guarantee"]
    Sync["Acquire ensures visibilityof prior writes"]
end
subgraph Thread2["Thread 2 - Handle Event"]
    T2_Load["load(Acquire)"]
    T2_Read["Read handler data"]
    T2_Call["Call handler"]
end
subgraph Thread1["Thread 1 - Register Handler"]
    T1_Write["Write handler data"]
    T1_CAS["compare_exchange(..., Acquire, ...)"]
end

T1_CAS --> Sync
T1_CAS --> T2_Load
T1_Write --> T1_CAS
T2_Load --> Sync
T2_Load --> T2_Read
T2_Read --> T2_Call

Sources: src/lib.rs(L35)  src/lib.rs(L62) 

The Acquire ordering ensures that when a thread successfully reads a non-zero handler value, it observes all memory writes that happened-before the handler was stored.

Lock-Free Properties

The atomic operations provide several lock-free guarantees essential for kernel-level event handling.

Wait-Free Registration

stateDiagram-v2
[*] --> Attempting : "register_handler(idx, fn)"
Attempting --> Success : "CAS succeeds (slot was 0)"
Attempting --> Failure : "CAS fails (slot occupied)"
Success --> [*] : "Return true"
Failure --> [*] : "Return false"
note left of Success : ['"Single atomic operation"']
note left of Failure : ['"No retry loops"']

Sources: src/lib.rs(L30 - L37) 

The registration operation is wait-free - it completes in a bounded number of steps regardless of other thread activity. Either the compare-exchange succeeds immediately, or it fails immediately if another handler is already registered.

Non-Blocking Event Handling

The handle operation never blocks and provides consistent performance characteristics:

  • Constant Time: Single atomic load operation
  • No Contention: Multiple threads can simultaneously handle different events
  • Real-Time Safe: No unbounded waiting or priority inversion

Sources: src/lib.rs(L58 - L70) 

Function Pointer Storage Mechanism

The atomic operations work on usize values that represent function pointers, requiring careful handling to maintain type safety.

Pointer-to-Integer Conversion

flowchart TD
subgraph Output["Output Types"]
    Recovered["Handler (fn())"]
end
subgraph Retrieval["Type Recovery"]
    Load["AtomicUsize::load"]
    Transmute["unsafe transmute<usize, fn()>"]
end
subgraph Storage["Atomic Storage"]
    Atomic["AtomicUsize value"]
end
subgraph Conversion["Type Conversion"]
    Cast["handler as usize"]
    Store["AtomicUsize::store"]
end
subgraph Input["Input Types"]
    Handler["Handler (fn())"]
end

Atomic --> Load
Cast --> Store
Handler --> Cast
Load --> Transmute
Store --> Atomic
Transmute --> Recovered

Sources: src/lib.rs(L35)  src/lib.rs(L48)  src/lib.rs(L64) 

The conversion relies on the platform guarantee that function pointers can be safely cast to usize and back. The unsafe transmute operations are necessary because the atomic types only work with integer values, but the conversions preserve the original function pointer values exactly.

Memory Layout and Safety

Relevant source files

This document explains the memory layout of the HandlerTable structure and the safety mechanisms that ensure correct lock-free operation. It covers how function pointers are stored atomically, memory ordering guarantees, and the safety implications of pointer transmutation operations.

For information about the specific atomic operations used, see Atomic Operations. For general API usage patterns, see API Reference.

Memory Layout Overview

The HandlerTable<N> structure has a simple but carefully designed memory layout that enables lock-free concurrent access to event handlers.

HandlerTable Structure Layout

flowchart TD
subgraph MemoryValues["Stored Values"]
    Zero["0 = Empty Slot"]
    FnPtr["fn() as usizeFunction Pointer"]
end
subgraph HandlerTableStruct["HandlerTable<N> Memory Layout"]
    ArrayField["handlers: [AtomicUsize; N]"]
    subgraph AtomicArray["AtomicUsize Array"]
        Slot0["AtomicUsize[0]8 bytes"]
        Slot1["AtomicUsize[1]8 bytes"]
        SlotDots["..."]
        SlotN["AtomicUsize[N-1]8 bytes"]
    end
end

Slot0 --> Zero
Slot1 --> FnPtr
SlotN --> Zero

HandlerTable Memory Characteristics

PropertyValueRationale
SizeN * size_of::()Fixed compile-time size
Alignmentalign_of::()Platform atomic alignment
InitializationAll slots = 0Zero represents empty slot
MutabilityAtomic operations onlyLock-free concurrency

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L20 - L24) 

Function Pointer Storage Mechanism

The core safety challenge is storing function pointers (fn()) in atomic integers (AtomicUsize) while maintaining type safety and memory safety.

Pointer Transmutation Process

sequenceDiagram
    participant ApplicationCode as "Application Code"
    participant HandlerTable as "HandlerTable"
    participant AtomicUsizeidx as "AtomicUsize[idx]"
    participant RawMemory as "Raw Memory"

    Note over ApplicationCode,RawMemory: Registration: fn() → usize
    ApplicationCode ->> HandlerTable: "register_handler(idx, handler_fn)"
    HandlerTable ->> HandlerTable: "handler as usize"
    Note over AtomicUsizeidx,HandlerTable: Transmute fn() to usize
    HandlerTable ->> AtomicUsizeidx: "compare_exchange(0, usize_value)"
    AtomicUsizeidx ->> RawMemory: "Atomic store"
    Note over ApplicationCode,RawMemory: Execution: usize → fn()
    ApplicationCode ->> HandlerTable: "handle(idx)"
    HandlerTable ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx ->> RawMemory: "Atomic load"
    RawMemory -->> HandlerTable: "usize_value"
    HandlerTable ->> HandlerTable: "transmute::<usize, fn()>(usize_value)"
    Note over AtomicUsizeidx,HandlerTable: Transmute usize to fn()
    HandlerTable ->> HandlerTable: "handler()"
    Note over AtomicUsizeidx,HandlerTable: Call function

Transmutation Safety Requirements

The unsafe transmute operations in the codebase rely on several critical safety invariants:

  1. Size Compatibility: size_of::<fn()>() == size_of::<usize>() on the target platform
  2. Representation Compatibility: Function pointers can be safely cast to/from usize
  3. Lifetime Management: Function pointers remain valid for the lifetime of storage
  4. Non-zero Constraint: Valid function pointers are never zero (allowing zero as "empty")

Sources: src/lib.rs(L35)  src/lib.rs(L48)  src/lib.rs(L64) 

Memory Ordering Guarantees

The HandlerTable uses specific memory ordering constraints to ensure correct synchronization without locks.

Ordering Operations Mapping

flowchart TD
subgraph Ordering["Memory Ordering"]
    Acquire["Ordering::Acquire"]
    Relaxed["Ordering::Relaxed"]
end
subgraph AtomicOps["Atomic Operations"]
    CAS["compare_exchange()"]
    Load["load()"]
    Swap["swap()"]
end
subgraph Operations["HandlerTable Operations"]
    Register["register_handler()"]
    Handle["handle()"]
    Unregister["unregister_handler()"]
end

CAS --> Acquire
CAS --> Relaxed
Handle --> Load
Load --> Acquire
Register --> CAS
Swap --> Acquire
Unregister --> Swap

Memory Ordering Analysis

OperationSuccess OrderingFailure OrderingPurpose
compare_exchangeAcquireRelaxedSynchronize handler installation
loadAcquireN/AEnsure handler visibility
swapAcquireN/ASynchronize handler removal

The Acquire ordering ensures that:

  • Handler installations are visible to all threads before the operation completes
  • Handler loads see the most recent installed handler
  • Handler removals synchronize with ongoing executions

Sources: src/lib.rs(L35)  src/lib.rs(L46)  src/lib.rs(L62) 

Zero Value Semantics

The choice of zero as the "empty slot" marker has important memory safety implications.

Zero Representation Safety

flowchart TD
subgraph SafetyChecks["Runtime Safety Checks"]
    CheckNonZero["if handler != 0"]
    ReturnSome["Some(handler)"]
    ReturnNone["None"]
end
subgraph ZeroSemantics["Zero Value Semantics"]
    EmptySlot["0 = Empty Slot"]
    ValidPtr["Non-zero = Valid fn()"]
    NullPtr["Null Function Pointer = 0"]
end
subgraph Initialization["Array Initialization"]
    ConstNew["const fn new()"]
    AtomicNew["AtomicUsize::new(0)"]
    ArrayInit["[const { ... }; N]"]
end

ArrayInit --> EmptySlot
AtomicNew --> ArrayInit
CheckNonZero --> ReturnNone
CheckNonZero --> ReturnSome
ConstNew --> AtomicNew
EmptySlot --> CheckNonZero
EmptySlot --> ValidPtr
ValidPtr --> NullPtr

Zero Value Safety Properties

  1. Platform Guarantee: On all supported platforms, null function pointers have value 0
  2. Initialization Safety: AtomicUsize::new(0) is const and safe
  3. Detection Safety: Zero checks reliably distinguish empty from occupied slots
  4. Transmute Safety: Zero values are never transmuted to function pointers

Sources: src/lib.rs(L22)  src/lib.rs(L47 - L51)  src/lib.rs(L63 - L68) 

Compile-time Safety Constraints

The generic constant N parameter provides compile-time memory safety guarantees.

Fixed-Size Array Benefits

flowchart TD
subgraph MemoryLayout["Memory Layout Properties"]
    Contiguous["Contiguous Memory"]
    CacheEfficient["Cache-Friendly Access"]
    NoFragmentation["No Heap Fragmentation"]
    FixedSize["Fixed Memory Footprint"]
end
subgraph RuntimeSafety["Runtime Bounds Safety"]
    BoundsCheck["if idx >= N"]
    EarlyReturn["return false/None"]
    SafeAccess["handlers[idx]"]
end
subgraph CompileTime["Compile-time Guarantees"]
    ConstN["const N: usize"]
    FixedArray["[AtomicUsize; N]"]
    StackAlloc["Stack Allocation"]
    NoHeap["No Heap Required"]
end

BoundsCheck --> EarlyReturn
BoundsCheck --> SafeAccess
ConstN --> FixedArray
Contiguous --> CacheEfficient
FixedArray --> BoundsCheck
FixedArray --> FixedSize
FixedArray --> StackAlloc
NoHeap --> NoFragmentation
StackAlloc --> Contiguous
StackAlloc --> NoHeap

Compile-time Safety Benefits

  • No Dynamic Allocation: Array size known at compile time, enabling no_std compatibility
  • Bounds Safety: Array access bounds are checked explicitly before atomic operations
  • Memory Predictability: Fixed memory footprint enables real-time system usage
  • Cache Locality: Contiguous array layout optimizes memory access patterns

Sources: src/lib.rs(L14 - L16)  src/lib.rs(L31 - L33)  src/lib.rs(L43 - L45)  src/lib.rs(L59 - L61) 

Unsafe Code Safety Analysis

The HandlerTable contains two unsafe blocks that require careful safety analysis.

Transmute Safety Invariants

flowchart TD
subgraph Verification["Safety Verification"]
    CompileTime["Compile-time size check"]
    RuntimeCheck["Runtime zero check"]
    SourceGuarantee["Only valid fn() stored"]
    StaticLifetime["Static function lifetime"]
end
subgraph SafetyConditions["Required Safety Conditions"]
    SizeMatch["sizeof(fn()) == sizeof(usize)"]
    ValidPtr["Stored value was valid fn()"]
    NonZero["Value != 0 before transmute"]
    Lifetime["Function still valid"]
end
subgraph UnsafeBlocks["Unsafe Code Locations"]
    Unregister["unregister_handler() line 48"]
    Handle["handle() line 64"]
end

Lifetime --> StaticLifetime
NonZero --> RuntimeCheck
SizeMatch --> CompileTime
ValidPtr --> SourceGuarantee

Safety Argument Summary

  1. Size Safety: Function pointers and usize have identical size on all target platforms
  2. Value Safety: Only valid function pointers (converted via as usize) are stored
  3. Zero Safety: Runtime checks prevent transmuting zero values
  4. Lifetime Safety: Event handlers typically have static lifetime in kernel contexts

The unsafe transmute operations are sound because they only reverse a previous safe cast operation under controlled conditions.

Sources: src/lib.rs(L48)  src/lib.rs(L64) 

Development Guide

Relevant source files

This document provides comprehensive information for developers who want to build, test, or contribute to the handler_table crate. It covers local development setup, testing procedures, and the automated CI/CD pipeline that ensures code quality across multiple target architectures.

For information about using the HandlerTable API, see User Guide. For implementation details and internal architecture, see Implementation Details.

Development Environment Setup

The handler_table crate requires a Rust nightly toolchain with specific components and target platforms for comprehensive development and testing. The development workflow supports both local development on x86_64-unknown-linux-gnu and cross-compilation for embedded no_std targets.

Required Toolchain Configuration

flowchart TD
Toolchain["nightly toolchain"]
Components["Required Components"]
Targets["Target Platforms"]
RustSrc["rust-src"]
Clippy["clippy"]
Rustfmt["rustfmt"]
LinuxGnu["x86_64-unknown-linux-gnu"]
BareX86["x86_64-unknown-none"]
RiscV["riscv64gc-unknown-none-elf"]
ARM64["aarch64-unknown-none-softfloat"]
TestExecution["Unit Test Execution"]
BuildOnly["Build Verification"]

ARM64 --> BuildOnly
BareX86 --> BuildOnly
Components --> Clippy
Components --> RustSrc
Components --> Rustfmt
LinuxGnu --> TestExecution
RiscV --> BuildOnly
Targets --> ARM64
Targets --> BareX86
Targets --> LinuxGnu
Targets --> RiscV
Toolchain --> Components
Toolchain --> Targets

Development Environment Commands

The following commands establish the required development environment:

  • Install nightly toolchain: rustup toolchain install nightly
  • Add required components: rustup component add rust-src clippy rustfmt
  • Add target platforms: rustup target add x86_64-unknown-none riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat

Sources: .github/workflows/ci.yml(L15 - L19) 

Build and Test Workflow

The development workflow follows a structured approach ensuring code quality through formatting checks, linting, building, and testing across all supported target architectures.

Local Development Commands

flowchart TD
FormatCheck["cargo fmt --all -- --check"]
ClippyLint["cargo clippy --target TARGET --all-features"]
Build["cargo build --target TARGET --all-features"]
Test["cargo test --target x86_64-unknown-linux-gnu -- --nocapture"]
Success["Development Ready"]
CrossCompile["Cross-compilation Verified"]

Build --> CrossCompile
Build --> Test
ClippyLint --> Build
FormatCheck --> ClippyLint
Test --> Success

Command Details

Command PurposeCommandTarget Scope
Format verificationcargo fmt --all -- --checkAll source files
Static analysiscargo clippy --target $TARGET --all-featuresPer-target analysis
Build verificationcargo build --target $TARGET --all-featuresAll target platforms
Unit test executioncargo test --target x86_64-unknown-linux-gnu -- --nocaptureLinux target only

The build process verifies compilation across all target architectures, but unit tests execute only on x86_64-unknown-linux-gnu due to the embedded nature of other targets.

Sources: .github/workflows/ci.yml(L22 - L30) 

CI/CD Pipeline Architecture

The automated pipeline implements a comprehensive verification strategy using GitHub Actions with matrix builds across multiple Rust toolchains and target architectures.

Pipeline Jobs and Dependencies

flowchart TD
subgraph subGraph1["Documentation Job"]
    DocJob["doc"]
    DocBuild["cargo doc --no-deps --all-features"]
    GHPages["GitHub Pages Deployment"]
end
subgraph subGraph0["CI Job Matrix"]
    Job1["ci-x86_64-unknown-linux-gnu"]
    Job2["ci-x86_64-unknown-none"]
    Job3["ci-riscv64gc-unknown-none-elf"]
    Job4["ci-aarch64-unknown-none-softfloat"]
end
Trigger["Push/PR Events"]
UnitTests["Unit Test Execution"]
BuildVerify["Build Verification"]

DocBuild --> GHPages
DocJob --> DocBuild
Job1 --> UnitTests
Job2 --> BuildVerify
Job3 --> BuildVerify
Job4 --> BuildVerify
Trigger --> DocJob
Trigger --> Job1
Trigger --> Job2
Trigger --> Job3
Trigger --> Job4

Matrix Build Configuration

The CI pipeline uses a fail-fast strategy set to false, ensuring all target architectures complete their builds even if one fails. Each matrix job executes the complete verification workflow for its assigned target.

Matrix DimensionValues
rust-toolchainnightly
targetsx86_64-unknown-linux-gnu,x86_64-unknown-none,riscv64gc-unknown-none-elf,aarch64-unknown-none-softfloat

Sources: .github/workflows/ci.yml(L8 - L12) 

Documentation Generation and Deployment

The documentation pipeline generates API documentation and deploys it to GitHub Pages with automatic index page generation and dependency exclusion.

flowchart TD
DocTrigger["Default Branch Push"]
DocBuild["cargo doc --no-deps --all-features"]
IndexGen["Generate index.html redirect"]
Deploy["JamesIves/github-pages-deploy-action@v4"]
GHPages["GitHub Pages Site"]
RustDocFlags["RUSTDOCFLAGS validation"]
BrokenLinks["-D rustdoc::broken_intra_doc_links"]
MissingDocs["-D missing-docs"]

Deploy --> GHPages
DocBuild --> IndexGen
DocBuild --> RustDocFlags
DocTrigger --> DocBuild
IndexGen --> Deploy
RustDocFlags --> BrokenLinks
RustDocFlags --> MissingDocs

Documentation Environment Variables

  • RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
  • default-branch: Dynamic branch reference for deployment conditions

The documentation deployment occurs only on pushes to the default branch, with automatic index page generation using the crate name extracted from cargo tree output.

Sources: .github/workflows/ci.yml(L32 - L55) 

Contributing Workflow

Code Quality Requirements

All contributions must pass the complete CI pipeline verification, including:

  1. Format Compliance: Code must conform to rustfmt standards
  2. Lint Compliance: No clippy warnings across all target architectures
  3. Build Success: Successful compilation on all supported targets
  4. Test Passage: Unit tests must pass on x86_64-unknown-linux-gnu
  5. Documentation Standards: No broken intra-doc links or missing documentation

Target Architecture Support

The crate maintains compatibility across diverse target architectures representing different use cases:

TargetPurposeTest Coverage
x86_64-unknown-linux-gnuDevelopment and testingFull unit tests
x86_64-unknown-noneBare metal x86_64Build verification
riscv64gc-unknown-none-elfRISC-V embeddedBuild verification
aarch64-unknown-none-softfloatARM64 embeddedBuild verification

This architecture matrix ensures the crate functions correctly across the diverse hardware platforms supported by ArceOS.

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L25 - L30) 

Building and Testing

Relevant source files

This page covers how to set up a local development environment for the handler_table crate, including building, testing, and code quality verification. The build process is designed to support multiple target architectures, including bare metal environments required for ArceOS integration.

For information about the automated CI/CD pipeline and deployment process, see CI/CD Pipeline.

Prerequisites

The handler_table crate requires specific toolchain components and target platforms to build successfully across all supported architectures.

Required Rust Toolchain

The crate requires the nightly Rust toolchain with specific components:

ComponentPurpose
rust-srcSource code for cross-compilation to bare metal targets
clippyLint checking for code quality
rustfmtCode formatting verification

Supported Target Architectures

The crate builds for multiple target platforms as defined in the CI matrix:

TargetEnvironmentTest Support
x86_64-unknown-linux-gnuLinux development✅ Full testing
x86_64-unknown-noneBare metal x86_64✅ Build only
riscv64gc-unknown-none-elfBare metal RISC-V✅ Build only
aarch64-unknown-none-softfloatBare metal ARM64✅ Build only

Build and Test Workflow

flowchart TD
subgraph subGraph0["Target Matrix"]
    Linux["x86_64-unknown-linux-gnu"]
    BareX86["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM64["aarch64-unknown-none-softfloat"]
end
Setup["Rust Toolchain Setup"]
Fmt["cargo fmt --check"]
Clippy["cargo clippy"]
Build["cargo build"]
Test["cargo test"]
Doc["cargo doc"]

Build --> ARM64
Build --> BareX86
Build --> Linux
Build --> RISCV
Build --> Test
Clippy --> Build
Fmt --> Clippy
Setup --> Fmt
Test --> Doc
Test --> Linux

Sources: .github/workflows/ci.yml(L11 - L19)  .github/workflows/ci.yml(L22 - L30) 

Local Development Setup

Installing Required Components

Install the nightly toolchain and required components:

# Install nightly toolchain
rustup toolchain install nightly

# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt

# Add target platforms
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat

Setting Default Toolchain

Set nightly as the default for the project:

rustup override set nightly

Verify the installation:

rustc --version --verbose

Sources: .github/workflows/ci.yml(L15 - L21) 

Building the Crate

Standard Build

Build for the default target (host platform):

cargo build --all-features

Multi-Target Building

Build for specific target architectures:

# Linux target (default)
cargo build --target x86_64-unknown-linux-gnu --all-features

# Bare metal x86_64
cargo build --target x86_64-unknown-none --all-features

# Bare metal RISC-V
cargo build --target riscv64gc-unknown-none-elf --all-features

# Bare metal ARM64
cargo build --target aarch64-unknown-none-softfloat --all-features

Multi-Target Build Process

flowchart TD
subgraph subGraph1["Build Artifacts"]
    LinuxLib["libhandler_table-*.rlib"]
    BareX86Lib["libhandler_table-*.rlib"]
    RISCVLib["libhandler_table-*.rlib"]
    ARM64Lib["libhandler_table-*.rlib"]
end
subgraph subGraph0["Build Targets"]
    Linux["x86_64-unknown-linux-gnu"]
    BareX86["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM64["aarch64-unknown-none-softfloat"]
end
Source["src/lib.rs"]
Cargo["Cargo.toml"]

ARM64 --> ARM64Lib
BareX86 --> BareX86Lib
Cargo --> ARM64
Cargo --> BareX86
Cargo --> Linux
Cargo --> RISCV
Linux --> LinuxLib
RISCV --> RISCVLib
Source --> ARM64
Source --> BareX86
Source --> Linux
Source --> RISCV

Sources: .github/workflows/ci.yml(L26 - L27) 

Running Tests

Unit Tests

Unit tests can only be executed on the x86_64-unknown-linux-gnu target due to the no_std nature of the crate and bare metal target limitations:

cargo test --target x86_64-unknown-linux-gnu -- --nocapture

The --nocapture flag allows println! statements in tests to be visible during test execution.

Test Limitations

TargetTest SupportReason
x86_64-unknown-linux-gnu✅ FullStandard library available
x86_64-unknown-none❌ NoneNo test framework in bare metal
riscv64gc-unknown-none-elf❌ NoneNo test framework in bare metal
aarch64-unknown-none-softfloat❌ NoneNo test framework in bare metal

Sources: .github/workflows/ci.yml(L28 - L30) 

Code Quality Checks

Format Checking

Verify code formatting compliance:

cargo fmt --all -- --check

This command checks that all code follows the standard Rust formatting rules without modifying files.

Lint Checking

Run Clippy for all targets:

# Check specific target
cargo clippy --target x86_64-unknown-linux-gnu --all-features
cargo clippy --target x86_64-unknown-none --all-features
cargo clippy --target riscv64gc-unknown-none-elf --all-features
cargo clippy --target aarch64-unknown-none-softfloat --all-features

Code Quality Pipeline

flowchart TD
subgraph Results["Results"]
    FmtOK["Format ✅"]
    LintOK["Lint ✅"]
    QualityOK["Quality Approved"]
end
subgraph subGraph0["Quality Checks"]
    Fmt["cargo fmt --check"]
    ClippyLinux["clippy x86_64-linux"]
    ClippyBare["clippy x86_64-none"]
    ClippyRISCV["clippy riscv64gc"]
    ClippyARM["clippy aarch64"]
end
Code["Source Code"]

ClippyARM --> LintOK
ClippyBare --> LintOK
ClippyLinux --> LintOK
ClippyRISCV --> LintOK
Code --> ClippyARM
Code --> ClippyBare
Code --> ClippyLinux
Code --> ClippyRISCV
Code --> Fmt
Fmt --> FmtOK
FmtOK --> QualityOK
LintOK --> QualityOK

Sources: .github/workflows/ci.yml(L22 - L25) 

Documentation Generation

Building Documentation

Generate crate documentation:

cargo doc --no-deps --all-features

The --no-deps flag excludes dependency documentation, focusing only on the handler_table crate.

Documentation Configuration

The documentation build process uses specific RUSTDOCFLAGS for strict documentation requirements:

export RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"
cargo doc --no-deps --all-features
FlagPurpose
-D rustdoc::broken_intra_doc_linksFail on broken internal documentation links
-D missing-docsFail on missing documentation comments

Viewing Documentation

After building, documentation is available at:

target/doc/handler_table/index.html

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L48) 

Development Workflow

Complete Development Cycle

A typical development workflow combines all verification steps:

# 1. Format check
cargo fmt --all -- --check

# 2. Lint check for primary target
cargo clippy --target x86_64-unknown-linux-gnu --all-features

# 3. Build all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features

# 4. Run tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

# 5. Generate documentation
cargo doc --no-deps --all-features

Ignored Files

The development environment excludes certain files and directories from version control:

PathPurpose
/targetBuild artifacts and compiled output
/.vscodeVisual Studio Code configuration
.DS_StoremacOS system files
Cargo.lockDependency lock file (library crate)

Sources: .gitignore(L1 - L4) 

CI/CD Pipeline

Relevant source files

This page documents the Continuous Integration and Continuous Deployment (CI/CD) pipeline for the handler_table crate. The pipeline ensures code quality, compatibility across multiple architectures, and automated documentation deployment through GitHub Actions workflows.

For information about building and testing locally during development, see Building and Testing.

Pipeline Overview

The CI/CD system consists of two primary GitHub Actions workflows that execute on push and pull request events. The pipeline validates code across multiple target architectures and automatically deploys documentation to GitHub Pages.

CI/CD Workflow Architecture

flowchart TD
subgraph subGraph3["Documentation Pipeline"]
    DocWorkflow["doc job"]
    DocCheckout["actions/checkout@v4"]
    DocBuild["cargo doc --no-deps"]
    IndexGen["generate index.html redirect"]
    Deploy["JamesIves/github-pages-deploy-action@v4"]
    Pages["GitHub Pages (gh-pages branch)"]
end
subgraph subGraph2["CI Steps"]
    Checkout["actions/checkout@v4"]
    Toolchain["dtolnay/rust-toolchain@nightly"]
    Format["cargo fmt --check"]
    Clippy["cargo clippy"]
    Build["cargo build"]
    Test["cargo test (linux only)"]
end
subgraph subGraph1["Target Architectures"]
    LinuxGNU["x86_64-unknown-linux-gnu"]
    BareX86["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["CI Job Matrix"]
    CIWorkflow["ci job"]
    Matrix["strategy.matrix"]
    RustNightly["rust-toolchain: nightly"]
    Targets["4 target architectures"]
end
Triggers["push/pull_request events"]

Build --> Test
CIWorkflow --> Matrix
Checkout --> Toolchain
Clippy --> Build
Deploy --> Pages
DocBuild --> IndexGen
DocCheckout --> DocBuild
DocWorkflow --> DocCheckout
Format --> Clippy
IndexGen --> Deploy
Matrix --> RustNightly
Matrix --> Targets
RustNightly --> Checkout
Targets --> ARM
Targets --> BareX86
Targets --> LinuxGNU
Targets --> RISCV
Toolchain --> Format
Triggers --> CIWorkflow
Triggers --> DocWorkflow

Sources: .github/workflows/ci.yml(L1 - L56) 

Multi-Architecture Testing Strategy

The CI pipeline validates compatibility across four distinct target architectures to ensure the handler_table crate functions correctly in diverse embedded and system environments.

Architecture Testing Matrix

TargetEnvironmentTest Coverage
x86_64-unknown-linux-gnuStandard LinuxFull (fmt, clippy, build, test)
x86_64-unknown-noneBare metal x86_64Build and lint only
riscv64gc-unknown-none-elfRISC-V bare metalBuild and lint only
aarch64-unknown-none-softfloatARM64 bare metalBuild and lint only

The pipeline uses a fail-fast strategy set to false, ensuring all target combinations are tested even if one fails.

Target-Specific Execution Flow

flowchart TD
subgraph subGraph2["Toolchain Components"]
    RustSrc["rust-src"]
    ClippyComp["clippy"]
    RustfmtComp["rustfmt"]
    TargetSpec["target-specific toolchain"]
end
subgraph subGraph1["Per-Target Steps"]
    Setup["Setup: checkout + toolchain + components"]
    VersionCheck["rustc --version --verbose"]
    FormatCheck["cargo fmt --all -- --check"]
    ClippyLint["cargo clippy --target TARGET --all-features"]
    BuildStep["cargo build --target TARGET --all-features"]
    TestStep["cargo test (if x86_64-unknown-linux-gnu)"]
end
subgraph subGraph0["Matrix Strategy"]
    FailFast["fail-fast: false"]
    NightlyToolchain["rust-toolchain: [nightly]"]
    FourTargets["targets: [4 architectures]"]
end

BuildStep --> TestStep
ClippyLint --> BuildStep
FailFast --> Setup
FormatCheck --> ClippyLint
FourTargets --> Setup
NightlyToolchain --> Setup
Setup --> ClippyComp
Setup --> RustSrc
Setup --> RustfmtComp
Setup --> TargetSpec
Setup --> VersionCheck
VersionCheck --> FormatCheck

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L25 - L30) 

Documentation Deployment

The documentation pipeline builds API documentation using cargo doc and deploys it to GitHub Pages. The deployment only occurs on pushes to the default branch, while documentation building is attempted on all branches and pull requests.

Documentation Build Process

The documentation job includes specific configurations for documentation quality:

  • Environment Variables: RUSTDOCFLAGS set to -D rustdoc::broken_intra_doc_links -D missing-docs to enforce documentation standards
  • Build Command: cargo doc --no-deps --all-features to generate comprehensive API documentation
  • Index Generation: Automatic creation of a redirect index.html pointing to the crate's documentation root

Documentation Deployment Flow

flowchart TD
subgraph subGraph2["Error Handling"]
    ContinueOnError["continue-on-error for non-default branches"]
end
subgraph subGraph1["Conditional Deployment"]
    BranchCheck["github.ref == default-branch?"]
    DeployAction["JamesIves/github-pages-deploy-action@v4"]
    SkipDeploy["Skip deployment"]
    SingleCommit["single-commit: true"]
    GHPages["branch: gh-pages"]
    TargetDoc["folder: target/doc"]
    PagesDeployment["GitHub Pages Site"]
end
subgraph subGraph0["Documentation Build"]
    DocJob["doc job"]
    DocSetup["checkout + rust nightly"]
    DocFlags["RUSTDOCFLAGS environment"]
    CargoDocs["cargo doc --no-deps --all-features"]
    IndexHTML["generate target/doc/index.html redirect"]
end
DocTrigger["Push/PR to any branch"]

BranchCheck --> DeployAction
BranchCheck --> SkipDeploy
CargoDocs --> ContinueOnError
CargoDocs --> IndexHTML
DeployAction --> GHPages
DeployAction --> SingleCommit
DeployAction --> TargetDoc
DocFlags --> CargoDocs
DocJob --> DocSetup
DocSetup --> DocFlags
DocTrigger --> DocJob
GHPages --> PagesDeployment
IndexHTML --> BranchCheck
SingleCommit --> PagesDeployment
TargetDoc --> PagesDeployment

Sources: .github/workflows/ci.yml(L32 - L56)  .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47 - L48) 

Workflow Job Structure

The CI/CD pipeline is organized into two distinct GitHub Actions jobs with different responsibilities and execution contexts.

Job Configuration Details

CI Job (ci):

  • Runner: ubuntu-latest
  • Strategy: Matrix execution across rust toolchain and target combinations
  • Purpose: Code quality validation and multi-architecture compatibility testing

Documentation Job (doc):

  • Runner: ubuntu-latest
  • Strategy: Single execution context
  • Permissions: contents: write for GitHub Pages deployment
  • Purpose: API documentation generation and deployment

Complete Workflow Structure

flowchart TD
subgraph subGraph4["Doc Job Steps"]
    DocStep1["actions/checkout@v4"]
    DocStep2["dtolnay/rust-toolchain@nightly"]
    DocStep3["cargo doc --no-deps --all-features"]
    DocStep4["generate index.html redirect"]
    DocStep5["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph3["Doc Job Configuration"]
    DocRunner["runs-on: ubuntu-latest"]
    DocPerms["permissions: contents: write"]
    DocEnv["RUSTDOCFLAGS environment"]
end
subgraph subGraph2["CI Job Steps"]
    CIStep1["actions/checkout@v4"]
    CIStep2["dtolnay/rust-toolchain@nightly"]
    CIStep3["rustc --version --verbose"]
    CIStep4["cargo fmt --all -- --check"]
    CIStep5["cargo clippy --target TARGET"]
    CIStep6["cargo build --target TARGET"]
    CIStep7["cargo test (conditional)"]
end
subgraph subGraph1["CI Job Configuration"]
    CIRunner["runs-on: ubuntu-latest"]
    CIStrategy["strategy.fail-fast: false"]
    CIMatrix["matrix: nightly × 4 targets"]
end
subgraph subGraph0["Workflow Triggers"]
    PushEvent["on: push"]
    PREvent["on: pull_request"]
end

CIMatrix --> CIStep1
CIRunner --> CIStrategy
CIStep1 --> CIStep2
CIStep2 --> CIStep3
CIStep3 --> CIStep4
CIStep4 --> CIStep5
CIStep5 --> CIStep6
CIStep6 --> CIStep7
CIStrategy --> CIMatrix
DocEnv --> DocStep1
DocPerms --> DocEnv
DocRunner --> DocPerms
DocStep1 --> DocStep2
DocStep2 --> DocStep3
DocStep3 --> DocStep4
DocStep4 --> DocStep5
PREvent --> CIRunner
PREvent --> DocRunner
PushEvent --> CIRunner
PushEvent --> DocRunner

Sources: .github/workflows/ci.yml(L1 - L31)  .github/workflows/ci.yml(L32 - L56)  .github/workflows/ci.yml(L36 - L37) 

Overview

Relevant source files

The memory_set crate provides building blocks for memory mapping management in Rust, offering data structures and operations similar to Unix system calls like mmap, munmap, and mprotect. This document covers the core architecture, components, and design patterns of the memory management system.

Note: This repository has been archived and the code has been moved to the axmm_crates repository as indicated in the README.

For detailed implementation specifics of individual components, see Implementation Details. For practical usage examples and patterns, see Usage and Examples.

Core Components and Structure

The memory_set crate is built around three primary components that work together to provide flexible memory mapping management:

Core Components Overview

flowchart TD
subgraph Storage["Storage"]
    BT["BTreeMap<VirtAddr, MemoryArea>"]
end
subgraph subGraph1["Supporting Types"]
    MR["MappingResult<T>"]
    ME["MappingError"]
    VA["VirtAddr"]
    VAR["VirtAddrRange"]
end
subgraph subGraph0["Primary Types"]
    MS["MemorySet<F,PT,B>"]
    MA["MemoryArea<F,B>"]
    MB["MappingBackend<F,PT> trait"]
end

MA --> MB
MA --> VAR
MR --> ME
MS --> BT
MS --> MA
MS --> MR
VAR --> VA

Sources: src/lib.rs(L12 - L13)  src/lib.rs(L15 - L27)  README.md(L18 - L41) 

The system uses a generic type parameter approach where:

  • F represents memory flags (must implement Copy)
  • PT represents the page table type
  • B represents the mapping backend implementation

System Architecture and Data Flow

The memory management system follows a layered architecture where high-level operations are decomposed into area management and low-level page table manipulation:

Memory Management Operation Flow

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend

    Note over Client,MappingBackend: map() operation
    Client ->> MemorySet: "map(area, page_table, unmap_overlap)"
    MemorySet ->> BTreeMap: "Check for overlaps"
    BTreeMap -->> MemorySet: "Overlapping areas found"
    alt unmap_overlap = true
        MemorySet ->> MemorySet: "Split/remove overlapping areas"
        MemorySet ->> MappingBackend: "unmap existing regions"
    else unmap_overlap = false
        MemorySet -->> Client: "MappingError::AlreadyExists"
    end
    MemorySet ->> MemoryArea: "Get backend reference"
    MemoryArea ->> MappingBackend: "map(start, size, flags, page_table)"
    MappingBackend -->> MemorySet: "Success/failure"
    MemorySet ->> BTreeMap: "Insert new area"
    MemorySet -->> Client: "MappingResult"
    Note over Client,MappingBackend: unmap() operation
    Client ->> MemorySet: "unmap(start, size, page_table)"
    MemorySet ->> BTreeMap: "Find affected areas"
    loop For each affected area
    alt Fully contained
        MemorySet ->> BTreeMap: "Remove area"
    else Partially overlapping
        MemorySet ->> MemoryArea: "split(boundary_addr)"
        MemoryArea -->> MemorySet: "New area created"
        MemorySet ->> BTreeMap: "Update area collection"
    end
    MemorySet ->> MappingBackend: "unmap(start, size, page_table)"
    end
    MemorySet -->> Client: "MappingResult"

Sources: README.md(L36 - L48)  src/lib.rs(L15 - L27) 

Generic Type System Design

The crate achieves flexibility through careful use of Rust generics, allowing different implementations for flags, page tables, and backends while maintaining type safety:

Generic Type Relationships

flowchart TD
subgraph subGraph2["Mock Implementation Example"]
    MF["MockFlags = u8"]
    MPT["MockPageTable = [u8; MAX_ADDR]"]
    MOCK["MockBackend"]
    MS_CONCRETE["MemorySet<MockFlags, MockPageTable, MockBackend>"]
end
subgraph subGraph1["Core Generic Types"]
    MS["MemorySet<F,PT,B>"]
    MA["MemoryArea<F,B>"]
    MBT["MappingBackend<F,PT>"]
end
subgraph subGraph0["Generic Parameters"]
    F["F: Copy"]
    PT["PT"]
    B["B: MappingBackend<F,PT>"]
end

B --> MA
B --> MBT
B --> MS
F --> MA
F --> MBT
F --> MS
MF --> MS_CONCRETE
MOCK --> MBT
MOCK --> MS_CONCRETE
MPT --> MS_CONCRETE
PT --> MBT
PT --> MS

Sources: README.md(L24 - L34)  README.md(L51 - L87)  src/lib.rs(L12 - L13) 

Error Handling Strategy

The crate uses a dedicated error type system to handle various failure scenarios in memory operations:

Error TypeDescriptionUsage Context
MappingError::InvalidParamInvalid parameters likeaddr,size,flagsInput validation
MappingError::AlreadyExistsRange overlaps with existing mappingOverlap detection
MappingError::BadStateBackend page table in bad stateBackend operation failures

The MappingResult<T> type alias provides a convenient Result type for all memory operations, defaulting to MappingResult<()> for operations that return no meaningful data on success.

Sources: src/lib.rs(L15 - L27) 

Key Design Principles

  1. Generic Flexibility: The system uses three generic type parameters (F, PT, B) to allow customization of flags, page tables, and backend implementations
  2. Area-Based Management: Memory is managed in discrete areas that can be split, merged, and manipulated independently
  3. Backend Abstraction: The MappingBackend trait abstracts actual page table manipulation, enabling different implementation strategies
  4. Overlap Handling: Sophisticated overlap detection and resolution with options for automatic unmapping of conflicting regions
  5. Type Safety: Rust's type system ensures memory safety and prevents common memory management errors

This overview establishes the foundation for understanding the detailed implementation covered in Implementation Details and the practical usage patterns demonstrated in Usage and Examples.

Sources: README.md(L9 - L14)  src/lib.rs(L1 - L13) 

Core Concepts

Relevant source files

This document explains the fundamental building blocks of the memory_set crate: MemorySet, MemoryArea, and MappingBackend. These three types work together to provide a flexible abstraction for memory mapping operations similar to mmap, munmap, and mprotect system calls.

For detailed implementation specifics, see Implementation Details. For practical usage examples, see Basic Usage Patterns.

The Three Core Types

The memory_set crate is built around three primary abstractions that work together to manage memory mappings:

flowchart TD
subgraph subGraph0["Generic Parameters"]
    F["F: Memory Flags"]
    PT["PT: Page Table Type"]
    B["B: Backend Implementation"]
end
MemorySet["MemorySet"]
MemoryArea["MemoryArea"]
MappingBackend["MappingBackend"]
BTreeMap["BTreeMap"]
VirtAddrRange["VirtAddrRange"]
PageTable["Page Table (PT)"]

B --> MemorySet
F --> MemorySet
MappingBackend --> PageTable
MemoryArea --> MappingBackend
MemoryArea --> VirtAddrRange
MemorySet --> BTreeMap
MemorySet --> MappingBackend
MemorySet --> MemoryArea
PT --> MemorySet

Sources: README.md(L18 - L41) 

MemorySet: Collection Management

MemorySet<F, PT, B> serves as the primary interface for managing collections of memory areas. It maintains a sorted collection of non-overlapping memory regions and provides high-level operations for mapping, unmapping, and protecting memory.

Key Responsibilities

OperationDescriptionOverlap Handling
map()Add new memory areaCan split/remove existing areas
unmap()Remove memory regionsAutomatically splits affected areas
protect()Change permissionsUpdates flags for matching areas
iter()Enumerate areasProvides ordered traversal

The MemorySet uses a BTreeMap<VirtAddr, MemoryArea> internally to maintain areas sorted by their starting virtual address, enabling efficient overlap detection and range queries.

flowchart TD
subgraph Operations["Operations"]
    VR1["VirtAddrRange: 0x1000-0x2000"]
    VR2["VirtAddrRange: 0x3000-0x4000"]
    VR3["VirtAddrRange: 0x5000-0x6000"]
end
subgraph subGraph1["Area Organization"]
    A1["va!(0x1000) -> MemoryArea"]
    A2["va!(0x3000) -> MemoryArea"]
    A3["va!(0x5000) -> MemoryArea"]
end
subgraph subGraph0["MemorySet Internal Structure"]
    MS["MemorySet"]
    BT["BTreeMap"]
end

A1 --> VR1
A2 --> VR2
A3 --> VR3
BT --> A1
BT --> A2
BT --> A3
MS --> BT

Sources: README.md(L34 - L48) 

MemoryArea: Individual Memory Regions

MemoryArea<F, B> represents a contiguous region of virtual memory with specific properties including address range, permissions flags, and an associated backend for page table operations.

Core Properties

Each MemoryArea encapsulates:

  • Virtual Address Range: Start address and size defining the memory region
  • Flags: Memory permissions (read, write, execute, etc.)
  • Backend: Implementation for actual page table manipulation

Area Lifecycle Operations


Sources: README.md(L37 - L38) 

MappingBackend: Page Table Interface

The MappingBackend<F, PT> trait defines the interface between memory areas and the underlying page table implementation. This abstraction allows the memory_set crate to work with different page table formats and memory management systems.

Required Operations

All backends must implement three core operations:

MethodParametersPurpose
map()start: VirtAddr, size: usize, flags: F, pt: &mut PTEstablish new memory mappings
unmap()start: VirtAddr, size: usize, pt: &mut PTRemove existing mappings
protect()start: VirtAddr, size: usize, new_flags: F, pt: &mut PTChange mapping permissions

Example Implementation Pattern

The mock backend demonstrates the interface pattern:

flowchart TD
subgraph subGraph1["MappingBackend Trait"]
    map_method["map()"]
    unmap_method["unmap()"]
    protect_method["protect()"]
end
subgraph subGraph0["MockBackend Implementation"]
    MockBackend["MockBackend struct"]
    MockFlags["MockFlags = u8"]
    MockPageTable["MockPageTable = [u8; MAX_ADDR]"]
end

MockBackend --> map_method
MockBackend --> protect_method
MockBackend --> unmap_method
MockFlags --> MockPageTable
map_method --> MockPageTable
protect_method --> MockPageTable
unmap_method --> MockPageTable

Sources: README.md(L51 - L87) 

Generic Type System

The memory_set crate uses a sophisticated generic type system to provide flexibility while maintaining type safety:

Type Parameters

  • F: Memory flags type (must implement Copy)
  • PT: Page table type (can be any structure)
  • B: Backend implementation (must implement MappingBackend<F, PT>)

This design allows the crate to work with different:

  • Flag representations (bitfields, enums, integers)
  • Page table formats (arrays, trees, hardware tables)
  • Backend strategies (direct manipulation, system calls, simulation)
flowchart TD
subgraph Usage["Usage"]
    MemorySet_Concrete["MemorySet"]
end
subgraph subGraph1["Concrete Example"]
    MockFlags_u8["MockFlags = u8"]
    MockPT_Array["MockPageTable = [u8; MAX_ADDR]"]
    MockBackend_Impl["MockBackend: MappingBackend"]
end
subgraph subGraph0["Generic Constraints"]
    F_Copy["F: Copy"]
    B_Backend["B: MappingBackend"]
    PT_Any["PT: Any type"]
end

B_Backend --> MockBackend_Impl
F_Copy --> MockFlags_u8
MockBackend_Impl --> MemorySet_Concrete
MockFlags_u8 --> MemorySet_Concrete
MockPT_Array --> MemorySet_Concrete
PT_Any --> MockPT_Array

Sources: README.md(L24 - L31) 

Coordinated Operation Flow

The three core types work together in a coordinated fashion to handle memory management operations:

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend
    participant PageTable as PageTable

    Note over Client,PageTable: Memory Mapping Operation
    Client ->> MemorySet: "map(area, page_table, unmap_overlap)"
    MemorySet ->> MemorySet: "check for overlapping areas"
    alt overlaps exist and unmap_overlap=true
        MemorySet ->> MemoryArea: "split/shrink existing areas"
        MemorySet ->> MappingBackend: "unmap() overlapping regions"
        MappingBackend ->> PageTable: "clear page table entries"
    end
    MemorySet ->> MemoryArea: "get backend reference"
    MemoryArea ->> MappingBackend: "map(start, size, flags, pt)"
    MappingBackend ->> PageTable: "set page table entries"
    MappingBackend -->> MemorySet: "return success/failure"
    MemorySet ->> MemorySet: "insert area into BTreeMap"
    MemorySet -->> Client: "return MappingResult"
    Note over Client,PageTable: Memory Unmapping Operation
    Client ->> MemorySet: "unmap(start, size, page_table)"
    MemorySet ->> MemorySet: "find affected areas"
    loop for each affected area
        MemorySet ->> MemoryArea: "determine overlap relationship"
    alt area fully contained
        MemorySet ->> MemorySet: "remove area from BTreeMap"
    else area partially overlapping
        MemorySet ->> MemoryArea: "split area at boundaries"
        MemorySet ->> MemorySet: "update BTreeMap with new areas"
    end
    MemorySet ->> MappingBackend: "unmap(start, size, pt)"
    MappingBackend ->> PageTable: "clear page table entries"
    end
    MemorySet -->> Client: "return MappingResult"

Sources: README.md(L42 - L43) 

System Architecture

Relevant source files

This document details the architectural design of the memory_set crate, focusing on how the core components interact, the generic type system design, and the data flow through memory management operations. For an introduction to the fundamental concepts of MemorySet, MemoryArea, and MappingBackend, see Core Concepts. For detailed implementation specifics of individual components, see Implementation Details.

Component Architecture and Interactions

The memory_set crate follows a layered architecture with three primary abstraction levels: the collection layer (MemorySet), the area management layer (MemoryArea), and the backend interface layer (MappingBackend).

Component Interaction Overview

flowchart TD
subgraph subGraph3["External Dependencies"]
    PageTable["Page Table (P)"]
    MemoryAddr["memory_addr crate"]
end
subgraph subGraph2["Backend Interface Layer"]
    MappingBackend["MappingBackend<F,P> trait"]
    ConcreteBackend["Concrete Backend Implementation"]
end
subgraph subGraph1["Area Management Layer"]
    MemoryArea1["MemoryArea<F,P,B>"]
    MemoryArea2["MemoryArea<F,P,B>"]
    VirtAddrRange["VirtAddrRange"]
end
subgraph subGraph0["Collection Layer"]
    MemorySet["MemorySet<F,P,B>"]
    BTreeStorage["BTreeMap<VirtAddr, MemoryArea>"]
end

BTreeStorage --> MemoryArea1
BTreeStorage --> MemoryArea2
ConcreteBackend --> MappingBackend
MappingBackend --> PageTable
MemoryArea1 --> MappingBackend
MemoryArea1 --> VirtAddrRange
MemoryArea2 --> MappingBackend
MemorySet --> BTreeStorage
MemorySet --> PageTable
VirtAddrRange --> MemoryAddr

The MemorySet acts as the orchestrator, managing collections of MemoryArea objects through a BTreeMap indexed by virtual addresses. Each MemoryArea delegates actual page table manipulation to its associated MappingBackend implementation.

Sources: src/set.rs(L9 - L11)  README.md(L18 - L41) 

Generic Type System Design

The crate achieves flexibility through a carefully designed generic type system that allows different flag types, page table implementations, and backend strategies while maintaining type safety.

Generic Type Parameter Relationships

flowchart TD
subgraph subGraph2["Concrete Mock Example"]
    MockFlags["MockFlags = u8"]
    MockPageTable["MockPageTable = [u8; MAX_ADDR]"]
    MockBackend["MockBackend"]
    ConcreteMemorySet["MemorySet<u8, [u8; MAX_ADDR], MockBackend>"]
end
subgraph subGraph1["Core Generic Types"]
    MemorySet["MemorySet<F,P,B>"]
    MemoryArea["MemoryArea<F,P,B>"]
    MappingBackendTrait["MappingBackend<F,P>"]
end
subgraph subGraph0["Generic Parameters"]
    F["F: Copy (Flags Type)"]
    P["P (Page Table Type)"]
    B["B: MappingBackend<F,P> (Backend Type)"]
end

B --> MappingBackendTrait
B --> MemoryArea
B --> MemorySet
F --> MappingBackendTrait
F --> MemoryArea
F --> MemorySet
MockBackend --> ConcreteMemorySet
MockBackend --> MappingBackendTrait
MockFlags --> ConcreteMemorySet
MockPageTable --> ConcreteMemorySet
P --> MappingBackendTrait
P --> MemoryArea
P --> MemorySet

The type parameter F represents memory flags and must implement Copy. The page table type P is completely generic, allowing integration with different page table implementations. The backend type B must implement MappingBackend<F,P>, creating a three-way constraint that ensures type compatibility across the entire system.

Sources: src/set.rs(L9)  README.md(L24 - L31) 

Memory Management Data Flow

Memory management operations follow predictable patterns that involve coordination between all architectural layers. The most complex operations, such as unmapping that splits existing areas, demonstrate the sophisticated interaction patterns.

Map Operation Data Flow

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend
    participant PageTable as PageTable

    Client ->> MemorySet: "map(area, page_table, unmap_overlap)"
    MemorySet ->> MemorySet: "overlaps() check"
    alt overlaps && unmap_overlap
        MemorySet ->> MemorySet: "unmap() existing regions"
        MemorySet ->> MappingBackend: "unmap() calls"
        MappingBackend ->> PageTable: "clear entries"
    else overlaps && !unmap_overlap
        MemorySet -->> Client: "MappingError::AlreadyExists"
    end
    MemorySet ->> MemoryArea: "map_area(page_table)"
    MemoryArea ->> MappingBackend: "map() call"
    MappingBackend ->> PageTable: "set entries"
    MappingBackend -->> MemoryArea: "success/failure"
    MemoryArea -->> MemorySet: "MappingResult"
    MemorySet ->> BTreeMap: "insert(area.start(), area)"
    MemorySet -->> Client: "MappingResult"

Unmap Operation with Area Splitting

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend

    Client ->> MemorySet: "unmap(start, size, page_table)"
    MemorySet ->> BTreeMap: "find affected areas"
    loop for each affected area
    alt area fully contained
        MemorySet ->> BTreeMap: "remove area"
        MemorySet ->> MemoryArea: "unmap_area()"
    else area intersects left boundary
        MemorySet ->> MemoryArea: "shrink_right()"
    else area intersects right boundary
        MemorySet ->> MemoryArea: "shrink_left()"
    else unmap range in middle
        MemorySet ->> MemoryArea: "split(end_addr)"
        MemoryArea -->> MemorySet: "right_part created"
        MemorySet ->> MemoryArea: "shrink_right() on left part"
        MemorySet ->> BTreeMap: "insert(end, right_part)"
    end
    MemorySet ->> MappingBackend: "unmap() call"
    end
    MemorySet -->> Client: "MappingResult"

The unmap operation demonstrates the most complex data flow, involving area splitting, shrinking, and careful coordination with the page table backend to maintain consistency.

Sources: src/set.rs(L93 - L114)  src/set.rs(L122 - L169) 

Storage Organization and Efficiency

The MemorySet uses a BTreeMap<VirtAddr, MemoryArea> as its core storage mechanism, providing efficient operations for the common memory management use cases.

BTreeMap Storage Structure

flowchart TD
subgraph subGraph3["Key Operations"]
    OverlapDetection["overlaps() - O(log n)"]
    FindOperation["find() - O(log n)"]
    FreeAreaSearch["find_free_area() - O(n)"]
end
subgraph subGraph2["Efficiency Operations"]
    SortedOrder["Sorted by start address"]
    LogarithmicLookup["O(log n) lookups"]
    RangeQueries["Efficient range queries"]
end
subgraph subGraph1["BTreeMap Key-Value Organization"]
    Entry1["va!(0x1000) → MemoryArea[0x1000..0x2000]"]
    Entry2["va!(0x3000) → MemoryArea[0x3000..0x4000]"]
    Entry3["va!(0x5000) → MemoryArea[0x5000..0x6000]"]
end
subgraph subGraph0["MemorySet Internal Storage"]
    MemorySet["MemorySet"]
    Areas["areas: BTreeMap<VirtAddr, MemoryArea>"]
end

Areas --> Entry1
Areas --> Entry2
Areas --> Entry3
Entry1 --> SortedOrder
Entry2 --> LogarithmicLookup
Entry3 --> RangeQueries
LogarithmicLookup --> FindOperation
MemorySet --> Areas
RangeQueries --> FreeAreaSearch
SortedOrder --> OverlapDetection

The BTreeMap provides several efficiency advantages:

OperationComplexityImplementation
overlaps()O(log n)Range queries before/after target range
find()O(log n)Range query up to target address
find_free_area()O(n)Linear scan between existing areas
map()O(log n)Insert operation after overlap check
unmap()O(log n + k)Where k is the number of affected areas

Key Algorithm: Overlap Detection

The overlap detection algorithm uses BTreeMap's range query capabilities to efficiently check for conflicts:

flowchart TD
subgraph subGraph0["Overlap Detection Strategy"]
    RangeBefore["range(..range.start).last()"]
    RangeAfter["range(range.start..).next()"]
    CheckBefore["Check if before.va_range().overlaps(range)"]
    CheckAfter["Check if after.va_range().overlaps(range)"]
end
Result["Boolean result"]

CheckAfter --> Result
CheckBefore --> Result
RangeAfter --> CheckAfter
RangeBefore --> CheckBefore

This approach requires at most two BTreeMap lookups regardless of the total number of areas, making it highly efficient even for large memory sets.

Sources: src/set.rs(L10)  src/set.rs(L37 - L49)  src/set.rs(L52 - L55)  src/set.rs(L64 - L83) 

Implementation Details

Relevant source files

This document provides detailed technical information about the internal implementation of the memory_set crate's core components. It covers the internal data structures, algorithms, and patterns used to implement memory mapping management functionality. For high-level concepts and architecture overview, see System Architecture. For usage examples and public API documentation, see Public API and Error Handling.

Core Data Structures and Types

The memory_set crate is built around three primary data structures that work together through a carefully designed generic type system.

MemoryArea Internal Structure

The MemoryArea struct serves as the fundamental building block representing a contiguous virtual memory region with uniform properties.

flowchart TD
subgraph subGraph3["Key Methods"]
    MAPAREA["map_area()"]
    UNMAPAREA["unmap_area()"]
    PROTECTAREA["protect_area()"]
    SPLIT["split()"]
    SHRINKLEFT["shrink_left()"]
    SHRINKRIGHT["shrink_right()"]
end
subgraph subGraph2["Type Constraints"]
    FCOPY["F: Copy"]
    BMAPPING["B: MappingBackend"]
    PGENERIC["P: Page Table Type"]
end
subgraph subGraph1["VirtAddrRange Components"]
    START["start: VirtAddr"]
    END["end: VirtAddr"]
end
subgraph subGraph0["MemoryArea Fields"]
    MA["MemoryArea"]
    VAR["va_range: VirtAddrRange"]
    FLAGS["flags: F"]
    BACKEND["backend: B"]
    PHANTOM["_phantom: PhantomData<(F,P)>"]
end

BACKEND --> BMAPPING
FLAGS --> FCOPY
MA --> BACKEND
MA --> FLAGS
MA --> MAPAREA
MA --> PHANTOM
MA --> PROTECTAREA
MA --> SHRINKLEFT
MA --> SHRINKRIGHT
MA --> SPLIT
MA --> UNMAPAREA
MA --> VAR
PHANTOM --> PGENERIC
VAR --> END
VAR --> START

The MemoryArea struct maintains both metadata about the virtual address range and a reference to the backend that handles the actual page table manipulation. The PhantomData field ensures proper generic type relationships without runtime overhead.

Sources: src/area.rs(L29 - L34)  src/area.rs(L36 - L76) 

MappingBackend Trait Implementation

The MappingBackend trait defines the interface for different memory mapping strategies, allowing the system to support various page table implementations and mapping policies.

flowchart TD
subgraph subGraph3["Implementation Requirements"]
    CLONE["Clone trait bound"]
    BOOLRESULT["Boolean success indicator"]
end
subgraph subGraph2["Generic Parameters"]
    F["F: Copy (Flags Type)"]
    P["P: Page Table Type"]
end
subgraph subGraph1["Backend Responsibilities"]
    MAPENTRY["Set page table entries"]
    CLEARENTRY["Clear page table entries"]
    UPDATEENTRY["Update entry permissions"]
end
subgraph subGraph0["MappingBackend Trait"]
    MB["MappingBackend"]
    MAP["map(start, size, flags, page_table) -> bool"]
    UNMAP["unmap(start, size, page_table) -> bool"]
    PROTECT["protect(start, size, new_flags, page_table) -> bool"]
end

F --> MB
MAP --> BOOLRESULT
MAP --> MAPENTRY
MB --> CLONE
MB --> MAP
MB --> PROTECT
MB --> UNMAP
P --> MB
PROTECT --> BOOLRESULT
PROTECT --> UPDATEENTRY
UNMAP --> BOOLRESULT
UNMAP --> CLEARENTRY

The trait's boolean return values allow backends to signal success or failure, which the higher-level operations convert into proper MappingResult types for error handling.

Sources: src/area.rs(L15 - L22)  src/area.rs(L90 - L110) 

MemorySet Collection Structure

The MemorySet uses a BTreeMap to maintain an ordered collection of memory areas, enabling efficient range queries and overlap detection.

flowchart TD
subgraph subGraph3["Core Methods"]
    MAPMETHOD["map()"]
    UNMAPMETHOD["unmap()"]
    PROTECTMETHOD["protect()"]
    OVERLAPS["overlaps()"]
    FIND["find()"]
    FINDFREE["find_free_area()"]
end
subgraph subGraph2["Efficiency Operations"]
    RANGEQUERIES["O(log n) range queries"]
    OVERLAP["Efficient overlap detection"]
    SEARCH["Binary search for areas"]
end
subgraph subGraph1["BTreeMap Key-Value Organization"]
    KEY1["Key: area.start()"]
    VAL1["Value: MemoryArea"]
    ORDERING["Sorted by start address"]
end
subgraph subGraph0["MemorySet Structure"]
    MS["MemorySet"]
    BTREE["areas: BTreeMap>"]
end

BTREE --> KEY1
BTREE --> VAL1
KEY1 --> ORDERING
MS --> BTREE
MS --> FIND
MS --> FINDFREE
MS --> MAPMETHOD
MS --> OVERLAPS
MS --> PROTECTMETHOD
MS --> UNMAPMETHOD
ORDERING --> OVERLAP
ORDERING --> RANGEQUERIES
ORDERING --> SEARCH

The choice of VirtAddr as the key ensures that areas are naturally sorted by their start addresses, which is crucial for the overlap detection and range manipulation algorithms.

Sources: src/set.rs(L9 - L11)  src/set.rs(L36 - L49) 

Memory Area Lifecycle Operations

Memory areas support sophisticated lifecycle operations that enable complex memory management patterns while maintaining consistency with the underlying page table.

Area Splitting Algorithm

The split() method implements a critical operation for handling partial unmapping and protection changes. It divides a single memory area into two independent areas at a specified boundary.

sequenceDiagram
    participant Client as Client
    participant MemoryArea as "MemoryArea"
    participant MappingBackend as "MappingBackend"

    Note over Client,MappingBackend: split(pos: VirtAddr) Operation
    Client ->> MemoryArea: split(pos)
    MemoryArea ->> MemoryArea: Validate pos in range (start < pos < end)
    alt Valid position
        MemoryArea ->> MemoryArea: Create new_area from pos to end
        Note over MemoryArea: new_area = MemoryArea::new(pos, end-pos, flags, backend.clone())
        MemoryArea ->> MemoryArea: Shrink original to start..pos
        Note over MemoryArea: self.va_range.end = pos
        MemoryArea ->> Client: Return Some(new_area)
    else Invalid position
        MemoryArea ->> Client: Return None
    end
    Note over MemoryArea,MappingBackend: No page table operations during split
    Note over MemoryArea: Backend cloned, both areas share same mapping behavior

The splitting operation is purely metadata manipulation - it doesn't modify the page table entries. The actual page table changes happen when subsequent operations like unmap() or protect() are called on the split areas.

Sources: src/area.rs(L148 - L163) 

Area Shrinking Operations

Memory areas support shrinking from either end, which is essential for handling partial unmapping operations efficiently.

flowchart TD
subgraph subGraph2["Error Handling"]
    CHECKRESULT["Check backend success"]
    SUCCESS["Update metadata"]
    FAILURE["Return MappingError::BadState"]
end
subgraph subGraph1["shrink_right() Process"]
    SR["shrink_right(new_size)"]
    CALCUNMAP2["unmap_size = old_size - new_size"]
    UNMAPRIGHT["backend.unmap(start + new_size, unmap_size)"]
    UPDATEEND["va_range.end -= unmap_size"]
end
subgraph subGraph0["shrink_left() Process"]
    SL["shrink_left(new_size)"]
    CALCUNMAP["unmap_size = old_size - new_size"]
    UNMAPLEFT["backend.unmap(start, unmap_size)"]
    UPDATESTART["va_range.start += unmap_size"]
end

CALCUNMAP --> UNMAPLEFT
CALCUNMAP2 --> UNMAPRIGHT
CHECKRESULT --> FAILURE
CHECKRESULT --> SUCCESS
SL --> CALCUNMAP
SR --> CALCUNMAP2
UNMAPLEFT --> CHECKRESULT
UNMAPLEFT --> UPDATESTART
UNMAPRIGHT --> CHECKRESULT
UNMAPRIGHT --> UPDATEEND

Both shrinking operations immediately update the page table through the backend before modifying the area's metadata, ensuring consistency between the virtual memory layout and page table state.

Sources: src/area.rs(L116 - L139) 

Collection Management Algorithms

The MemorySet implements sophisticated algorithms for managing collections of memory areas, particularly for handling overlapping operations and maintaining area integrity.

Overlap Detection Strategy

The overlap detection algorithm leverages the BTreeMap's ordered structure to efficiently check for conflicts with minimal tree traversal.

flowchart TD
subgraph subGraph0["BTreeMap Range Queries"]
    BEFORECHECK["Find last area before range.start"]
    AFTERCHECK["Find first area >= range.start"]
    RANGEBEFORE["areas.range(..range.start).last()"]
    RANGEAFTER["areas.range(range.start..).next()"]
end
START["overlaps(range: VirtAddrRange)"]
BEFOREOVERLAP["Does before area overlap with range?"]
RETURNTRUE["return true"]
AFTEROVERLAP["Does after area overlap with range?"]
RETURNFALSE["return false"]

AFTERCHECK --> AFTEROVERLAP
AFTERCHECK --> RANGEAFTER
AFTEROVERLAP --> RETURNFALSE
AFTEROVERLAP --> RETURNTRUE
BEFORECHECK --> BEFOREOVERLAP
BEFORECHECK --> RANGEBEFORE
BEFOREOVERLAP --> AFTERCHECK
BEFOREOVERLAP --> RETURNTRUE
START --> BEFORECHECK

This algorithm achieves O(log n) complexity by examining at most two areas, regardless of the total number of areas in the set.

Sources: src/set.rs(L36 - L49) 

Complex Unmapping Algorithm

The unmap() operation handles the most complex scenario in memory management: removing an arbitrary address range that may affect multiple existing areas in different ways.

flowchart TD
subgraph subGraph3["Phase 3 Details"]
    RIGHTRANGE["areas.range_mut(start..).next()"]
    RIGHTINTERSECT["Check if after.start < end"]
    RIGHTCASE["Shrink left of area"]
end
subgraph subGraph2["Phase 2 Details"]
    LEFTRANGE["areas.range_mut(..start).last()"]
    LEFTINTERSECT["Check if before.end > start"]
    LEFTCASE1["Case 1: Unmap at end of area"]
    LEFTCASE2["Case 2: Unmap in middle (split required)"]
end
subgraph subGraph1["Phase 1 Details"]
    RETAIN["areas.retain() with condition"]
    CONTAINED["area.va_range().contained_in(range)"]
    REMOVE["Remove and unmap area"]
end
subgraph subGraph0["unmap() Three-Phase Algorithm"]
    PHASE1["Phase 1: Remove Fully Contained Areas"]
    PHASE2["Phase 2: Handle Left Boundary Intersection"]
    PHASE3["Phase 3: Handle Right Boundary Intersection"]
end

CONTAINED --> REMOVE
LEFTINTERSECT --> LEFTCASE1
LEFTINTERSECT --> LEFTCASE2
LEFTRANGE --> LEFTINTERSECT
PHASE1 --> RETAIN
PHASE2 --> LEFTRANGE
PHASE3 --> RIGHTRANGE
RETAIN --> CONTAINED
RIGHTINTERSECT --> RIGHTCASE
RIGHTRANGE --> RIGHTINTERSECT

This three-phase approach ensures that all possible area-range relationships are handled correctly, from simple removal to complex splitting scenarios.

Sources: src/set.rs(L122 - L168) 

Protection Changes with Area Management

The protect() operation demonstrates the most sophisticated area manipulation, potentially creating new areas while modifying existing ones.

sequenceDiagram
    participant MemorySet as "MemorySet"
    participant BTreeMap as "BTreeMap"
    participant MemoryArea as "MemoryArea"
    participant MappingBackend as "MappingBackend"
    participant to_insertVec as "to_insert: Vec"

    Note over MemorySet,to_insertVec: protect(start, size, update_flags, page_table)
    MemorySet ->> BTreeMap: Iterate through all areas
    loop For each area in range
        BTreeMap ->> MemorySet: area reference
        MemorySet ->> MemorySet: Call update_flags(area.flags())
    alt Flags should be updated
        Note over MemorySet,to_insertVec: Determine area-range relationship
    alt Area fully contained in range
        MemorySet ->> MemoryArea: protect_area(new_flags)
        MemoryArea ->> MappingBackend: protect(start, size, new_flags)
        MemorySet ->> MemoryArea: set_flags(new_flags)
    else Range fully contained in area (split into 3)
        MemorySet ->> MemoryArea: split(end) -> right_part
        MemorySet ->> MemoryArea: set_end(start) (becomes left_part)
        MemorySet ->> MemorySet: Create middle_part with new_flags
        MemorySet ->> to_insertVec: Queue right_part and middle_part for insertion
    else Partial overlaps
        MemorySet ->> MemoryArea: split() and protect as needed
        MemorySet ->> to_insertVec: Queue new parts for insertion
    end
    end
    end
    MemorySet ->> BTreeMap: areas.extend(to_insert)

The algorithm defers insertions to avoid iterator invalidation, collecting new areas in a vector and inserting them after the main iteration completes.

Sources: src/set.rs(L189 - L247) 

Generic Type System Implementation

The crate's generic type system enables flexible memory management while maintaining type safety and zero-cost abstractions.

Type Parameter Relationships

flowchart TD
subgraph subGraph1["Type Flow Through System"]
    MAFLAG["MemoryArea.flags: F"]
    MBFLAG["MappingBackend.map(flags: F)"]
    MBPT["MappingBackend methods(page_table: &mut P)"]
    MABACKEND["MemoryArea.backend: B"]
    CLONE["Cloned for area.split()"]
end
subgraph subGraph0["Generic Type Constraints"]
    F["F: Copy + Debug"]
    P["P: Any"]
    B["B: MappingBackend + Clone"]
end
subgraph subGraph3["PhantomData Usage"]
    PHANTOM["PhantomData<(F,P)>"]
    TYPEREL["Maintains F,P relationship in MemoryArea"]
    ZEROSIZE["Zero runtime cost"]
    FCOPY["F: Copy enables efficient flag passing"]
    BCLONE["B: Clone enables area splitting"]
end
subgraph subGraph2["Trait Bounds Enforcement"]
    PHANTOM["PhantomData<(F,P)>"]
    FCOPY["F: Copy enables efficient flag passing"]
    BCLONE["B: Clone enables area splitting"]
    BMAPPING["B: MappingBackend ensures consistent interface"]
end

B --> CLONE
B --> MABACKEND
F --> MAFLAG
F --> MBFLAG
P --> MBPT
PHANTOM --> TYPEREL
PHANTOM --> ZEROSIZE

The PhantomData<(F,P)> field in MemoryArea ensures that the compiler tracks the relationship between flag type F and page table type P even though P is not directly stored in the struct.

Sources: src/area.rs(L29 - L34)  src/area.rs(L15 - L22) 

Error Handling Strategy

The crate implements a consistent error handling strategy that propagates failures through the operation chain while maintaining transactional semantics.

Error Propagation Pattern

flowchart TD
subgraph subGraph3["Error Handling Locations"]
    AREAOPS["MemoryArea operations"]
    SETOPS["MemorySet operations"]
    PUBLICAPI["Public API boundary"]
end
subgraph subGraph2["Error Propagation"]
    PROPAGATE["? operator propagation"]
end
subgraph subGraph1["Error Sources"]
    BACKEND["Backend operation failure"]
    VALIDATION["Parameter validation"]
    OVERLAP["Overlap detection"]
end
subgraph subGraph0["Error Type Hierarchy"]
    MR["MappingResult = Result"]
    ME["MappingError"]
    BE["BadState"]
    IE["InvalidParam"]
    AE["AlreadyExists"]
end

AE --> PROPAGATE
AREAOPS --> SETOPS
BACKEND --> BE
BE --> PROPAGATE
IE --> PROPAGATE
ME --> AE
ME --> BE
ME --> IE
OVERLAP --> AE
PROPAGATE --> AREAOPS
SETOPS --> PUBLICAPI
VALIDATION --> IE

The error handling design ensures that failures at any level (backend, area, or set) are properly propagated to the caller with meaningful error information.

Sources: src/area.rs(L6)  src/area.rs(L90 - L110)  src/set.rs(L98 - L114) 

Backend Error Translation

The system translates boolean failure indicators from backends into structured error types:

sequenceDiagram
    participant MemoryArea as "MemoryArea"
    participant MappingBackend as "MappingBackend"
    participant MappingResult as "MappingResult"

    Note over MemoryArea,MappingResult: Backend Error Translation Pattern
    MemoryArea ->> MappingBackend: map(start, size, flags, page_table)
    MappingBackend ->> MemoryArea: return bool (success/failure)
    alt Backend returns true
    MemoryArea ->> MappingResult: Ok(())
    else Backend returns false
    MemoryArea ->> MappingResult: Err(MappingError::BadState)
    end
    Note over MemoryArea,MappingResult: Pattern used in map_area(), unmap_area(), protect_area()

This translation happens in the MemoryArea methods that interface with the backend, converting the simple boolean results into the richer MappingResult type for higher-level error handling.

Sources: src/area.rs(L90 - L103)  src/area.rs(L116 - L139) 

MemoryArea and MappingBackend

Relevant source files

This page provides detailed technical documentation of the MemoryArea struct and MappingBackend trait, which form the core building blocks for individual memory region management within the memory_set crate. The MappingBackend trait defines the interface for different memory mapping strategies, while MemoryArea represents a contiguous virtual memory region with associated flags and backend implementation.

For information about how multiple memory areas are organized and managed as collections, see 2.2. For the public API that users interact with, see 2.3.

MappingBackend Trait

The MappingBackend trait defines the fundamental operations required for memory mapping implementations. It serves as an abstraction layer that allows different mapping strategies to be plugged into the memory management system.

Trait Definition and Contract

classDiagram
note for MappingBackend "Clone trait requiredfor area splitting operations"
class MappingBackend {
    <<trait>>
    
    +map(start: VirtAddr, size: usize, flags: F, page_table: &mut P) bool
    +unmap(start: VirtAddr, size: usize, page_table: &mut P) bool
    +protect(start: VirtAddr, size: usize, new_flags: F, page_table: &mut P) bool
}

class TypeParameters {
    F: Copy "Memory flags type"
    P "Page table type"
    
}

MappingBackend  -->  TypeParameters : "Generic over"

The trait requires implementors to handle three core operations:

MethodPurposeReturn Value
mapEstablish virtual-to-physical mappingstrueon success,falseon failure
unmapRemove existing mappingstrueon success,falseon failure
protectModify access permissionstrueon success,falseon failure

Sources: src/area.rs(L8 - L22) 

Implementation Requirements

The trait imposes several constraints on implementors:

  • Clone Requirement: Backends must implement Clone to support memory area splitting operations
  • Generic Flexibility: The F type parameter allows different flag representations (e.g., bitfields, enums)
  • Page Table Agnostic: The P type parameter enables integration with various page table implementations
  • Boolean Return Convention: All methods return boolean success indicators

Sources: src/area.rs(L15) 

MemoryArea Struct

The MemoryArea struct represents a contiguous virtual memory region with uniform properties. It combines address range information, access flags, and a backend implementation to provide a complete memory mapping abstraction.

Structure and Fields

classDiagram
note for MemoryArea "Generic over F: Copy, P, B: MappingBackend"
class MemoryArea {
    -va_range: VirtAddrRange
    -flags: F
    -backend: B
    -_phantom: PhantomData~F,P~
    +new(start: VirtAddr, size: usize, flags: F, backend: B) Self
    +va_range() VirtAddrRange
    +flags() F
    +start() VirtAddr
    +end() VirtAddr
    +size() usize
    +backend() &B
}

class VirtAddrRange {
    +start: VirtAddr
    +end: VirtAddr
    +size() usize
}

class MappingBackend {
    <<trait>>
    
    
}

MemoryArea  -->  VirtAddrRange : "contains"
MemoryArea  -->  MappingBackend : "implements B"

Core Fields:

  • va_range: Defines the virtual address boundaries using VirtAddrRange
  • flags: Stores memory access permissions of type F
  • backend: Contains the mapping implementation of type B
  • _phantom: Enables generic type parameters without runtime overhead

Sources: src/area.rs(L24 - L34) 

Public Accessor Methods

The struct provides const accessor methods for retrieving area properties:

MethodReturn TypeDescription
va_range()VirtAddrRangeComplete address range information
flags()FCurrent access flags
start()VirtAddrStarting virtual address
end()VirtAddrEnding virtual address (exclusive)
size()usizeSize in bytes
backend()&BReference to mapping backend

Sources: src/area.rs(L47 - L75) 

Core Operations and Lifecycle

Memory areas support a complete lifecycle from creation through mapping, modification, and unmapping operations.

Area Lifecycle State Machine

stateDiagram-v2
state Mapped {
    [*] --> FullMapped
    FullMapped --> LeftShrunk : "shrink_left()"
    FullMapped --> RightShrunk : "shrink_right()"
    LeftShrunk --> FullMapped : "conceptual expansion"
    RightShrunk --> FullMapped : "conceptual expansion"
}
state SplitState {
    Original --> LeftPart : "split()"
    Original --> RightPart : "split()"
}
[*] --> Created : "new()"
Protected --> Unmapped : "unmap_area()"
Created --> Unmapped : "never mapped"
Unmapped --> [*]
Created --> Mapped : "map_area()"
Mapped --> Protected : "protect_area()"
Protected --> Mapped : "protect_area() with different flags"
Mapped --> SplitState : "split() called"
Protected --> SplitState : "split() called"
SplitState --> Mapped : "Both parts continue"
Mapped --> Unmapped : "unmap_area()"

Sources: src/area.rs(L89 - L163) 

Mapping Operations

The area provides three fundamental mapping operations that delegate to the backend:

map_area

  • Purpose: Establishes page table entries for the entire area
  • Implementation: Calls backend.map() with area's start, size, and flags
  • Error Handling: Returns MappingError::BadState on backend failure

unmap_area

  • Purpose: Removes all page table entries for the area
  • Implementation: Calls backend.unmap() with area's start and size
  • Error Handling: Returns MappingError::BadState on backend failure

protect_area

  • Purpose: Modifies access permissions without unmapping
  • Implementation: Calls backend.protect() and updates internal flags
  • Error Handling: Currently assumes success (returns Ok(()))

Sources: src/area.rs(L89 - L110) 

Area Manipulation Methods

Memory areas support sophisticated manipulation operations that enable complex memory management scenarios.

Shrinking Operations

flowchart TD
subgraph subGraph2["Implementation Details"]
    C1["Calculate unmap_size = size - new_size"]
    C2["Call backend.unmap()"]
    C3["Update va_range boundaries"]
    C4["Return MappingResult"]
    B1["Original Area: [start, end)"]
    A1["Original Area: [start, end)"]
end

A1 --> A2
A2 --> A3
B1 --> B2
B2 --> B3

shrink_left Implementation:

  1. Calculates unmap size as current_size - new_size
  2. Calls backend.unmap(start, unmap_size, page_table)
  3. Updates va_range.start by adding unmap_size
  4. Returns error if backend operation fails

shrink_right Implementation:

  1. Calculates unmap size as current_size - new_size
  2. Calls backend.unmap(start + new_size, unmap_size, page_table)
  3. Updates va_range.end by subtracting unmap_size
  4. Returns error if backend operation fails

Sources: src/area.rs(L112 - L139) 

Area Splitting

The split method enables dividing a single memory area into two independent areas:

flowchart TD
subgraph subGraph1["After Split at pos"]
    B1["Left Area [start, pos)"]
    B2["Right Area [pos, end)"]
end
subgraph subGraph0["Before Split"]
    A1["MemoryArea [start, end)"]
end
subgraph Requirements["Requirements"]
    C1["start < pos < end"]
    C2["Both parts non-empty"]
    C3["Backend implements Clone"]
end

A1 --> B1
A1 --> B2

Split Implementation Details:

  • Input Validation: Ensures start < pos < end to prevent empty areas
  • Left Area: Original area with end updated to pos
  • Right Area: New area created with backend.clone()
  • Return Value: Some(new_area) on success, None on invalid position

Sources: src/area.rs(L141 - L163) 

Type Relationships and Generic Design

The memory area system uses a sophisticated generic design to provide flexibility while maintaining type safety.

Generic Type Parameter Flow

flowchart TD
subgraph subGraph2["External Dependencies"]
    VA["VirtAddr (memory_addr crate)"]
    PD["PhantomData<(F,P)>"]
end
subgraph subGraph1["Core Types"]
    MA["MemoryArea"]
    MB["MappingBackend"]
    VR["VirtAddrRange"]
end
subgraph subGraph0["Type Parameters"]
    F["F: Copy (Flags)"]
    P["P (Page Table)"]
    B["B: MappingBackend"]
end
note1["F must implement Copy for flag operations"]
note2["B must implement Clone for area splitting"]
note3["PhantomData enables P parameter without runtime cost"]

B --> MA
B --> MB
F --> MA
F --> MB
P --> MA
P --> MB
PD --> MA
VA --> VR
VR --> MA

Type Parameter Constraints:

  • F: Copy: Enables efficient flag copying during operations
  • P: Unconstrained page table type for maximum flexibility
  • B: MappingBackend<F,P>: Ensures backend compatibility with flag and page table types

Key Design Benefits:

  • Zero-cost abstraction: PhantomData eliminates runtime overhead
  • Type safety: Generic constraints prevent mismatched components
  • Flexibility: Different flag encodings and page table implementations supported

Sources: src/area.rs(L29 - L34)  src/area.rs(L15) 

MemorySet Collection Management

Relevant source files

This document covers the internal collection management mechanisms of MemorySet, focusing on how it organizes, tracks, and manipulates collections of memory areas using a BTreeMap data structure. It examines overlap detection algorithms, area lifecycle operations, and complex manipulation procedures like splitting and shrinking.

For information about individual MemoryArea objects and the MappingBackend trait, see MemoryArea and MappingBackend. For the public API interface, see Public API and Error Handling.

Core Data Structure Organization

The MemorySet struct uses a BTreeMap<VirtAddr, MemoryArea<F, P, B>> as its primary storage mechanism, where the key is the starting virtual address of each memory area. This design provides efficient logarithmic-time operations for address-based queries and maintains areas in sorted order by their start addresses.

MemorySet BTreeMap Organization

flowchart TD
subgraph subGraph2["Key Properties"]
    Sorted["Keys sorted by address"]
    Unique["Each key is area.start()"]
    NoOverlap["No overlapping ranges"]
end
subgraph subGraph1["BTreeMap Structure"]
    Key1["va!(0x1000)"]
    Key2["va!(0x3000)"]
    Key3["va!(0x5000)"]
    Area1["MemoryArea[0x1000-0x2000]"]
    Area2["MemoryArea[0x3000-0x4000]"]
    Area3["MemoryArea[0x5000-0x6000]"]
end
subgraph MemorySet["MemorySet<F,P,B>"]
    BTree["BTreeMap<VirtAddr, MemoryArea>"]
end

BTree --> Key1
BTree --> Key2
BTree --> Key3
BTree --> NoOverlap
BTree --> Sorted
BTree --> Unique
Key1 --> Area1
Key2 --> Area2
Key3 --> Area3

Sources: src/set.rs(L9 - L11) 

Basic Collection Operations

The MemorySet provides fundamental collection operations that leverage the BTreeMap's sorted structure:

OperationMethodTime ComplexityPurpose
Count areaslen()O(1)Returns number of areas
Check emptinessis_empty()O(1)Tests if collection is empty
Iterate areasiter()O(n)Provides iterator over all areas
Find by addressfind(addr)O(log n)Locates area containing address

The find() method demonstrates efficient address lookup using range queries on the BTreeMap:

Address Lookup Algorithm

flowchart TD
Start["find(addr)"]
RangeQuery["areas.range(..=addr).last()"]
GetCandidate["Get last area with start ≤ addr"]
CheckContains["Check if area.va_range().contains(addr)"]
ReturnArea["Return Some(area)"]
ReturnNone["Return None"]

CheckContains --> ReturnArea
CheckContains --> ReturnNone
GetCandidate --> CheckContains
RangeQuery --> GetCandidate
Start --> RangeQuery

Sources: src/set.rs(L21 - L55) 

Overlap Detection and Management

The overlaps() method implements efficient overlap detection by checking at most two adjacent areas in the sorted collection, rather than scanning all areas:

Overlap Detection Strategy

flowchart TD
CheckOverlap["overlaps(range)"]
CheckBefore["Check area before range.start"]
CheckAfter["Check area at/after range.start"]
BeforeRange["areas.range(..range.start).last()"]
AfterRange["areas.range(range.start..).next()"]
TestBeforeOverlap["before.va_range().overlaps(range)"]
TestAfterOverlap["after.va_range().overlaps(range)"]
ReturnTrue1["Return true"]
ReturnTrue2["Return true"]
ContinueAfter["Continue to after check"]
ReturnFalse["Return false"]

AfterRange --> TestAfterOverlap
BeforeRange --> TestBeforeOverlap
CheckAfter --> AfterRange
CheckBefore --> BeforeRange
CheckOverlap --> CheckAfter
CheckOverlap --> CheckBefore
TestAfterOverlap --> ReturnFalse
TestAfterOverlap --> ReturnTrue2
TestBeforeOverlap --> ContinueAfter
TestBeforeOverlap --> ReturnTrue1

This algorithm achieves O(log n) complexity by exploiting the sorted nature of the BTreeMap and the non-overlapping invariant of stored areas.

Sources: src/set.rs(L36 - L49) 

Memory Area Addition and Conflict Resolution

The map() operation handles the complex process of adding new areas while managing potential conflicts:

Map Operation Flow

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea

    Client ->> MemorySet: "map(area, page_table, unmap_overlap)"
    MemorySet ->> MemorySet: "Validate area.va_range().is_empty()"
    MemorySet ->> MemorySet: "overlaps(area.va_range())"
    alt "Overlap detected"
    alt "unmap_overlap = true"
        MemorySet ->> MemorySet: "unmap(area.start(), area.size(), page_table)"
        Note over MemorySet: "Remove conflicting areas"
    else "unmap_overlap = false"
        MemorySet -->> Client: "MappingError::AlreadyExists"
    end
    end
    MemorySet ->> MemoryArea: "area.map_area(page_table)"
    MemoryArea -->> MemorySet: "MappingResult"
    MemorySet ->> BTreeMap: "insert(area.start(), area)"
    MemorySet -->> Client: "Success"

Sources: src/set.rs(L85 - L114) 

Complex Area Manipulation Operations

Unmap Operation with Area Splitting

The unmap() operation implements sophisticated area manipulation, including splitting areas when the unmapped region falls in the middle of an existing area:

Unmap Cases and Transformations

flowchart TD
subgraph subGraph3["Case 4: Middle Split"]
    subgraph subGraph1["Case 2: Right Boundary Intersection"]
        C4Before["[████████████████][unmap]"]
        C4After1["[██]      [██]split into two"]
        C3Before["[████████████][unmap]"]
        C3After1["[████]shrink_left"]
        C2Before["[██████████████][unmap]"]
        C2After1["[████]shrink_right"]
        C1Before["[████████████]Existing Area"]
        C1After1["(removed)"]
        subgraph subGraph2["Case 3: Left Boundary Intersection"]
            subgraph subGraph0["Case 1: Full Containment"]
                C4Before["[████████████████][unmap]"]
                C4After1["[██]      [██]split into two"]
                C3Before["[████████████][unmap]"]
                C3After1["[████]shrink_left"]
                C2Before["[██████████████][unmap]"]
                C2After1["[████]shrink_right"]
                C1Before["[████████████]Existing Area"]
                C1After1["(removed)"]
            end
        end
    end
end

C1Before --> C1After1
C2Before --> C2After1
C3Before --> C3After1
C4Before --> C4After1

The implementation handles these cases through a three-phase process:

  1. Full Removal: Remove areas completely contained in the unmap range using retain()
  2. Left Boundary Processing: Shrink or split areas that intersect the left boundary
  3. Right Boundary Processing: Shrink areas that intersect the right boundary

Sources: src/set.rs(L116 - L169) 

Protect Operation with Multi-way Splitting

The protect() operation changes memory flags within a range and may require splitting existing areas into multiple parts:

Protect Operation Area Handling


Sources: src/set.rs(L180 - L247) 

Free Space Management

The find_free_area() method locates unallocated address ranges by examining gaps between consecutive areas:

Free Area Search Algorithm

flowchart TD
FindFree["find_free_area(hint, size, limit)"]
InitLastEnd["last_end = max(hint, limit.start)"]
IterateAreas["For each (addr, area) in areas"]
CheckGap["if last_end + size <= addr"]
ReturnGap["Return Some(last_end)"]
UpdateEnd["last_end = area.end()"]
NextArea["Continue to next area"]
NoMoreAreas["End of areas reached"]
CheckFinalGap["if last_end + size <= limit.end"]
ReturnFinal["Return Some(last_end)"]
ReturnNone["Return None"]

CheckFinalGap --> ReturnFinal
CheckFinalGap --> ReturnNone
CheckGap --> ReturnGap
CheckGap --> UpdateEnd
FindFree --> InitLastEnd
InitLastEnd --> IterateAreas
IterateAreas --> CheckGap
IterateAreas --> NoMoreAreas
NextArea --> IterateAreas
NoMoreAreas --> CheckFinalGap
UpdateEnd --> NextArea

Sources: src/set.rs(L57 - L83) 

Collection Cleanup Operations

The clear() method provides atomic cleanup of all memory areas and their underlying mappings:

// Unmaps all areas from the page table, then clears the collection
pub fn clear(&mut self, page_table: &mut P) -> MappingResult

This operation iterates through all areas, calls unmap_area() on each, and then clears the BTreeMap if all unmapping operations succeed.

Sources: src/set.rs(L171 - L178) 

Public API and Error Handling

Relevant source files

This document covers the public interface exposed by the memory_set crate and its error handling mechanisms. It focuses on what the library makes available to users and how errors are represented and propagated throughout the system.

For implementation details of the individual components, see MemoryArea and MappingBackend and MemorySet Collection Management. For practical usage examples, see Basic Usage Patterns.

Public API Structure

The memory_set crate exposes a clean public interface consisting of three main components and a structured error handling system. The public API is defined through selective re-exports in the library root.

Public Components

The library exposes exactly three primary types to users:

ComponentTypePurpose
MemorySet<F,PT,B>Generic structTop-level memory management container
MemoryArea<F,B>Generic structIndividual memory region representation
MappingBackend<F,PT>TraitBackend interface for page table operations

Public API Export Structure

flowchart TD
subgraph subGraph3["Internal Modules"]
    SET_MOD["set module"]
    AREA_MOD["area module"]
    MS_IMPL["MemorySet implementation"]
    MA_IMPL["MemoryArea implementation"]
    MB_TRAIT["MappingBackend trait"]
end
subgraph subGraph2["src/lib.rs - Public Interface"]
    LIB["lib.rs"]
    subgraph subGraph1["Error Types"]
        ME["pub enum MappingError"]
        MR["pub type MappingResult"]
    end
    subgraph Re-exports["Re-exports"]
        MS_EXPORT["pub use self::set::MemorySet"]
        MA_EXPORT["pub use self::area::MemoryArea"]
        MB_EXPORT["pub use self::area::MappingBackend"]
    end
end

AREA_MOD --> MA_IMPL
AREA_MOD --> MB_TRAIT
LIB --> MA_EXPORT
LIB --> MB_EXPORT
LIB --> ME
LIB --> MR
LIB --> MS_EXPORT
MA_EXPORT --> AREA_MOD
MB_EXPORT --> AREA_MOD
MS_EXPORT --> SET_MOD
SET_MOD --> MS_IMPL

Sources: src/lib.rs(L12 - L13) 

Generic Type Parameters

All public components use consistent generic type parameters that provide flexibility while maintaining type safety:

  • F: Copy - Memory flags type (e.g., read/write/execute permissions)
  • PT - Page table implementation type
  • B: MappingBackend<F,PT> - Concrete backend implementation

This design allows users to plug in their own flag representations, page table structures, and backend implementations while ensuring type compatibility across the system.

Sources: src/lib.rs(L1 - L28) 

Error Handling System

The library implements a structured error handling approach using a custom error type and result wrapper that provides clear feedback about operation failures.

MappingError Variants

Error Type Definition

flowchart TD
subgraph subGraph2["Usage in Results"]
    MR["MappingResult<T>"]
    RES_OK["Ok(T)"]
    RES_ERR["Err(MappingError)"]
end
subgraph subGraph1["Error Contexts"]
    IP_DESC["Invalid addr, size, flags, etc."]
    AE_DESC["Range overlaps existing mapping"]
    BS_DESC["Backend page table corrupted"]
end
subgraph subGraph0["MappingError Enum"]
    ME["MappingError"]
    IP["InvalidParam"]
    AE["AlreadyExists"]
    BS["BadState"]
end

AE --> AE_DESC
BS --> BS_DESC
IP --> IP_DESC
ME --> AE
ME --> BS
ME --> IP
MR --> RES_ERR
MR --> RES_OK
RES_ERR --> ME

The error system defines three specific failure modes:

Error VariantDescriptionCommon Causes
InvalidParamInvalid operation parametersMisaligned addresses, zero sizes, invalid flags
AlreadyExistsRange conflicts with existing mappingOverlapping memory regions without unmap permission
BadStateBackend page table corruptionHardware errors, concurrent modification

Sources: src/lib.rs(L15 - L24) 

Result Type Pattern

The library uses a type alias MappingResult<T> that standardizes error handling across all operations:

pub type MappingResult<T = ()> = Result<T, MappingError>;

This pattern allows operations to return either successful results with typed values or structured error information. The default unit type () accommodates operations that succeed without returning data.

Sources: src/lib.rs(L26 - L27) 

API Design Patterns

Error Propagation Flow

Error Handling Throughout the System

sequenceDiagram
    participant UserCode as User Code
    participant MemorySet as MemorySet
    participant MappingBackend as MappingBackend
    participant PageTable as Page Table
    participant MA as MA

    UserCode ->> MemorySet: "map(area, page_table, flags)"
    alt "Parameter Validation"
        MemorySet ->> MemorySet: "Validate inputs"
        MemorySet -->> UserCode: "MappingResult::Err(InvalidParam)"
    end
    alt "Overlap Detection"
        MemorySet ->> MemorySet: "Check for overlaps"
        MemorySet -->> UserCode: "MappingResult::Err(AlreadyExists)"
    end
    MemorySet ->> MA: "Get backend reference"
    MA ->> MappingBackend: "map(start, size, flags, page_table)"
    MappingBackend ->> PageTable: "Set page table entries"
    alt "Backend Failure"
    PageTable -->> MappingBackend: "Hardware error"
    MappingBackend -->> MA: "MappingResult::Err(BadState)"
    MA -->> MemorySet: "Propagate error"
    MemorySet -->> UserCode: "MappingResult::Err(BadState)"
    else "Success Path"
        PageTable -->> MappingBackend: "Success"
        MappingBackend -->> MA: "MappingResult::Ok(())"
        MA -->> MemorySet: "Success"
        MemorySet -->> UserCode: "MappingResult::Ok(())"
    end

Sources: src/lib.rs(L15 - L27) 

Trait-Based Extension

The MappingBackend trait allows users to implement custom page table management while maintaining compatibility with the error handling system. All backend implementations must return MappingResult<T> values, ensuring consistent error propagation.

Backend Implementation Requirements

flowchart TD
subgraph subGraph2["Error Contract"]
    ALL_METHODS["All methods return MappingResult<T>"]
    ERROR_TYPES["Can return any MappingError variant"]
    CONSISTENT["Consistent error handling across implementations"]
end
subgraph subGraph1["User Implementation"]
    CUSTOM_BACKEND["CustomBackend"]
    IMPL_BLOCK["impl MappingBackend<F,PT> for CustomBackend"]
end
subgraph subGraph0["MappingBackend Trait"]
    MB_TRAIT["MappingBackend<F,PT>"]
    MAP_METHOD["fn map(...) -> MappingResult<()>"]
    UNMAP_METHOD["fn unmap(...) -> MappingResult<()>"]
    PROTECT_METHOD["fn protect(...) -> MappingResult<()>"]
end

ALL_METHODS --> ERROR_TYPES
CUSTOM_BACKEND --> IMPL_BLOCK
ERROR_TYPES --> CONSISTENT
IMPL_BLOCK --> MB_TRAIT
MAP_METHOD --> ALL_METHODS
MB_TRAIT --> MAP_METHOD
MB_TRAIT --> PROTECT_METHOD
MB_TRAIT --> UNMAP_METHOD

Sources: src/lib.rs(L12 - L13) 

Usage Interface Characteristics

Type Safety Guarantees

The public API enforces several compile-time guarantees:

  • Backend Compatibility: B: MappingBackend<F,PT> ensures backends match flag and page table types
  • Flag Consistency: F: Copy requirement enables efficient flag operations
  • Generic Flexibility: Users can substitute their own types while maintaining safety

Memory Management Operations

The public interface supports standard memory management operations through the MemorySet type:

  • Mapping: Create new memory regions with specified permissions
  • Unmapping: Remove existing memory regions
  • Protection: Modify permissions on existing regions
  • Querying: Inspect current memory layout and permissions

All operations return MappingResult<T> values that must be handled by user code, preventing silent failures and ensuring error visibility.

Sources: src/lib.rs(L1 - L28) 

Usage and Examples

Relevant source files

This document provides practical guidance for using the memory_set crate, including common usage patterns, complete working examples, and best practices. The examples demonstrate how to create memory mappings, manage overlapping areas, and implement custom backends for different page table systems.

For detailed implementation specifics of the core components, see Implementation Details. For information about project setup and dependencies, see Development and Project Setup.

Quick Start Example

The most basic usage involves creating a MemorySet with a custom backend implementation and using it to manage memory mappings. The following example from the README demonstrates the fundamental operations:

flowchart TD
subgraph subGraph1["Required Components"]
    Backend["MockBackend: MappingBackend"]
    PT["MockPageTable: [u8; MAX_ADDR]"]
    Flags["MockFlags: u8"]
    Area["MemoryArea::new(addr, size, flags, backend)"]
end
subgraph subGraph0["Basic Usage Flow"]
    Create["MemorySet::new()"]
    Map["map(MemoryArea, PageTable, unmap_overlap)"]
    Unmap["unmap(start, size, PageTable)"]
    Query["find(addr) / iter()"]
end

Area --> Map
Backend --> Map
Create --> Map
Flags --> Area
Map --> Unmap
PT --> Map
Unmap --> Query

Basic Usage Pattern Flow

Sources: README.md(L18 - L88) 

The core workflow requires implementing the MappingBackend trait for your specific page table system, then using MemorySet to manage collections of MemoryArea objects. Here's the essential pattern from README.md(L33 - L48) :

OperationPurposeKey Parameters
MemorySet::new()Initialize empty memory setNone
map()Add new memory mappingMemoryArea, page table, overlap handling
unmap()Remove memory mappingStart address, size, page table
find()Locate area containing addressVirtual address
iter()Enumerate all areasNone

Backend Implementation Pattern

Every usage of memory_set requires implementing the MappingBackend trait. The implementation defines how memory operations interact with your specific page table system:

flowchart TD
subgraph subGraph2["Return Values"]
    Success["true: Operation succeeded"]
    Failure["false: Operation failed"]
end
subgraph subGraph1["Page Table Operations"]
    SetEntries["Set page table entries"]
    ClearEntries["Clear page table entries"]
    UpdateEntries["Update entry flags"]
end
subgraph subGraph0["MappingBackend Implementation"]
    Map["map(start, size, flags, pt)"]
    Unmap["unmap(start, size, pt)"]
    Protect["protect(start, size, new_flags, pt)"]
end

ClearEntries --> Failure
ClearEntries --> Success
Map --> SetEntries
Protect --> UpdateEntries
SetEntries --> Failure
SetEntries --> Success
Unmap --> ClearEntries
UpdateEntries --> Failure
UpdateEntries --> Success

Backend Implementation Requirements

Sources: README.md(L51 - L87)  src/tests.rs(L15 - L51) 

The backend implementation handles the actual page table manipulation. Each method returns bool indicating success or failure:

  • map(): Sets page table entries for the specified range if all entries are currently unmapped
  • unmap(): Clears page table entries for the specified range if all entries are currently mapped
  • protect(): Updates flags for page table entries in the specified range if all entries are currently mapped

Memory Area Management Operations

The MemorySet provides sophisticated area management that handles overlaps, splits areas when needed, and maintains sorted order for efficient operations:

sequenceDiagram
    participant Client as Client
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant Backend as Backend

    Note over Client,Backend: Mapping with Overlap Resolution
    Client ->> MemorySet: "map(area, pt, unmap_overlap=true)"
    MemorySet ->> BTreeMap: "Check for overlapping areas"
    BTreeMap -->> MemorySet: "Found overlaps"
    MemorySet ->> MemorySet: "Remove/split overlapping areas"
    MemorySet ->> Backend: "unmap() existing regions"
    MemorySet ->> Backend: "map() new region"
    Backend -->> MemorySet: "Success"
    MemorySet ->> BTreeMap: "Insert new area"
    Note over Client,Backend: Unmapping that Splits Areas
    Client ->> MemorySet: "unmap(start, size, pt)"
    MemorySet ->> BTreeMap: "Find affected areas"
    loop "For each affected area"
        MemorySet ->> MemoryArea: "Check overlap relationship"
    alt "Partially overlapping"
        MemorySet ->> MemoryArea: "split() at boundaries"
        MemoryArea -->> MemorySet: "New area created"
    end
    MemorySet ->> Backend: "unmap() region"
    end
    MemorySet ->> BTreeMap: "Update area collection"

Complex Memory Operations Sequence

Sources: src/tests.rs(L152 - L226)  README.md(L42 - L48) 

Common Usage Patterns

Based on the test examples, several common patterns emerge for different memory management scenarios:

Pattern 1: Basic Mapping and Unmapping

src/tests.rs(L84 - L104)  demonstrates mapping non-overlapping areas in a regular pattern, followed by querying operations.

Pattern 2: Overlap Resolution

src/tests.rs(L114 - L127)  shows how to handle mapping conflicts using the unmap_overlap parameter to automatically resolve overlaps.

Pattern 3: Area Splitting

src/tests.rs(L166 - L192)  illustrates how unmapping operations can split existing areas into multiple smaller areas when the unmapped region falls within an existing area.

Pattern 4: Protection Changes

src/tests.rs(L252 - L285)  demonstrates using protect() operations to change memory permissions, which can also result in area splitting when applied to partial ranges.

Error Handling Patterns

The crate uses MappingResult<T> for error handling, with MappingError variants indicating different failure modes:

Error TypeCauseCommon Resolution
AlreadyExistsMapping overlaps existing areaUseunmap_overlap=trueor unmap manually
Backend failuresPage table operation failedCheck page table state and permissions

Sources: src/tests.rs(L114 - L121)  src/tests.rs(L53 - L66) 

Type System Usage

The generic type system allows customization for different environments:

flowchart TD
subgraph subGraph2["Instantiated Types"]
    MS["MemorySet"]
    MA["MemoryArea"]
end
subgraph subGraph1["Concrete Example Types"]
    MockFlags["MockFlags = u8"]
    MockPT["MockPageTable = [u8; MAX_ADDR]"]
    MockBackend["MockBackend struct"]
end
subgraph subGraph0["Generic Parameters"]
    F["F: Copy (Flags Type)"]
    PT["PT (Page Table Type)"]
    B["B: MappingBackend"]
end

B --> MockBackend
F --> MockFlags
MockBackend --> MA
MockBackend --> MS
MockFlags --> MA
MockFlags --> MS
MockPT --> MS
PT --> MockPT

Type System Instantiation Pattern

Sources: README.md(L22 - L34)  src/tests.rs(L5 - L13) 

The examples use simple types (u8 for flags, byte array for page table) but the system supports any types that meet the trait bounds. This flexibility allows integration with different operating system kernels, hypervisors, or memory management systems that have varying page table structures and flag representations.

Basic Usage Patterns

Relevant source files

This document demonstrates the fundamental usage patterns for the memory_set crate, showing how to create and configure MemorySet, MemoryArea, and implement MappingBackend for typical memory management scenarios. The examples here focus on the core API usage and basic operations like mapping, unmapping, and memory protection.

For detailed implementation internals, see Implementation Details. For advanced usage scenarios and testing patterns, see Advanced Examples and Testing.

Setting Up Types and Backend

The memory_set crate uses a generic type system that requires three type parameters to be specified. The typical pattern involves creating type aliases and implementing a backend that handles the actual memory operations.

Type Configuration Pattern

ComponentPurposeExample Type
F(Flags)Memory protection flagsu8, custom bitflags
PT(Page Table)Page table representationArray, actual page table struct
B(Backend)Memory mapping implementationCustom struct implementingMappingBackend

The basic setup pattern follows this structure:

type MockFlags = u8;
type MockPageTable = [MockFlags; MAX_ADDR];
struct MockBackend;

Core Type Relationships

flowchart TD
subgraph subGraph2["Instantiated Types"]
    MSI["MemorySet"]
    MAI["MemoryArea"]
end
subgraph subGraph1["Generic Types"]
    MS["MemorySet"]
    MA["MemoryArea"]
    MB["MappingBackend trait"]
end
subgraph subGraph0["User Configuration"]
    UF["MockFlags = u8"]
    UPT["MockPageTable = [u8; MAX_ADDR]"]
    UB["MockBackend struct"]
end

MA --> MAI
MS --> MSI
MSI --> MAI
UB --> MA
UB --> MB
UB --> MS
UF --> MA
UF --> MS
UPT --> MS

Sources: README.md(L22 - L31) 

Creating and Initializing MemorySet

The basic pattern for creating a MemorySet involves calling the new() constructor with appropriate type parameters:

let mut memory_set = MemorySet::<MockFlags, MockPageTable, MockBackend>::new();

The MemorySet maintains an internal BTreeMap that organizes memory areas by their starting virtual addresses, enabling efficient overlap detection and range queries.

MemorySet Initialization Flow

flowchart TD
subgraph subGraph0["Required Context"]
    PT["&mut MockPageTable"]
    AREAS["MemoryArea instances"]
end
NEW["MemorySet::new()"]
BT["BTreeMap"]
EMPTY["Empty memory set ready for mapping"]

BT --> EMPTY
EMPTY --> AREAS
EMPTY --> PT
NEW --> BT

Sources: README.md(L34) 

Mapping Memory Areas

The fundamental mapping operation involves creating a MemoryArea and adding it to the MemorySet. The pattern requires specifying the virtual address range, flags, and backend implementation.

Basic Mapping Pattern

memory_set.map(
    MemoryArea::new(va!(0x1000), 0x4000, flags, MockBackend),
    &mut page_table,
    unmap_overlap_flag,
)

MemoryArea Construction Parameters

ParameterTypePurpose
startVirtAddrStarting virtual address
sizeusizeSize in bytes
flagsFMemory protection flags
backendBBackend implementation

Memory Mapping Operation Flow

sequenceDiagram
    participant User as User
    participant MemorySet as MemorySet
    participant BTreeMap as BTreeMap
    participant MemoryArea as MemoryArea
    participant MappingBackend as MappingBackend

    User ->> MemorySet: "map(area, page_table, false)"
    MemorySet ->> BTreeMap: "Check for overlaps"
    alt No overlaps
        MemorySet ->> MemoryArea: "Get backend reference"
        MemoryArea ->> MappingBackend: "map(start, size, flags, pt)"
        MappingBackend -->> MemoryArea: "Success/failure"
        MemorySet ->> BTreeMap: "Insert area"
        MemorySet -->> User: "Ok(())"
    else Overlaps found and
        MemorySet -->> User: "Err(AlreadyExists)"
    end

Sources: README.md(L36 - L41) 

Unmapping and Area Management

Unmapping operations can result in area splitting when the unmapped region falls within an existing area's boundaries. This demonstrates the sophisticated area management capabilities.

Unmapping Pattern

memory_set.unmap(start_addr, size, &mut page_table)

The unmap operation handles three scenarios:

  • Complete removal: Area fully contained within unmap range
  • Area splitting: Unmap range falls within area boundaries
  • Partial removal: Unmap range overlaps area boundaries

Area Splitting During Unmap

flowchart TD
subgraph subGraph3["BTreeMap State"]
    MAP1["Key: 0x1000 → MemoryArea(0x1000-0x2000)"]
    MAP2["Key: 0x4000 → MemoryArea(0x4000-0x5000)"]
end
subgraph subGraph2["After Unmap"]
    LEFT["MemoryArea: 0x1000-0x2000"]
    RIGHT["MemoryArea: 0x4000-0x5000"]
end
subgraph subGraph1["Unmap Operation"]
    UNMAP["unmap(0x2000, 0x2000)"]
end
subgraph subGraph0["Before Unmap"]
    ORIG["MemoryArea: 0x1000-0x5000"]
end

LEFT --> MAP1
ORIG --> UNMAP
RIGHT --> MAP2
UNMAP --> LEFT
UNMAP --> RIGHT

Sources: README.md(L42 - L48) 

Implementing MappingBackend

The MappingBackend trait defines the interface for actual memory operations. Implementations must provide three core methods that manipulate the underlying page table or memory management structure.

Required Methods

MethodPurposeReturn Type
mapEstablish new mappingsbool(success/failure)
unmapRemove existing mappingsbool(success/failure)
protectChange mapping permissionsbool(success/failure)

Implementation Pattern

The typical pattern involves checking existing state and modifying page table entries:

#![allow(unused)]
fn main() {
impl MappingBackend<MockFlags, MockPageTable> for MockBackend {
    fn map(&self, start: VirtAddr, size: usize, flags: MockFlags, pt: &mut MockPageTable) -> bool {
        // Check for conflicts, then set entries
    }
    
    fn unmap(&self, start: VirtAddr, size: usize, pt: &mut MockPageTable) -> bool {
        // Verify mappings exist, then clear entries
    }
    
    fn protect(&self, start: VirtAddr, size: usize, new_flags: MockFlags, pt: &mut MockPageTable) -> bool {
        // Verify mappings exist, then update flags
    }
}
}

MappingBackend Method Responsibilities

flowchart TD
subgraph subGraph2["Implementation Details"]
    ITER["pt.iter_mut().skip(start).take(size)"]
    SET["*entry = flags"]
    CLEAR["*entry = 0"]
end
subgraph subGraph1["Page Table Operations"]
    CHECK["Validate existing state"]
    MODIFY["Modify page table entries"]
    VERIFY["Return success/failure"]
end
subgraph subGraph0["MappingBackend Interface"]
    MAP["map(start, size, flags, pt)"]
    UNMAP["unmap(start, size, pt)"]
    PROTECT["protect(start, size, flags, pt)"]
end

CHECK --> MODIFY
ITER --> CLEAR
ITER --> SET
MAP --> CHECK
MODIFY --> ITER
MODIFY --> VERIFY
PROTECT --> CHECK
UNMAP --> CHECK

Sources: README.md(L51 - L87) 

Complete Usage Flow

The following demonstrates a complete usage pattern that combines all the basic operations:

Initialization and Setup

  1. Define type aliases for flags, page table, and backend
  2. Create page table instance
  3. Initialize empty MemorySet

Memory Operations

  1. Create MemoryArea with desired parameters
  2. Map area using MemorySet::map()
  3. Perform unmapping operations that may split areas
  4. Iterate over resulting areas to verify state

Error Handling

All operations return MappingResult<T> which wraps either success values or MappingError variants for proper error handling.

Complete Operation Sequence

flowchart TD
subgraph subGraph0["Backend Operations"]
    BACKEND_MAP["backend.map()"]
    BACKEND_UNMAP["backend.unmap()"]
    PT_UPDATE["Page table updates"]
end
INIT["Initialize types and MemorySet"]
CREATE["Create MemoryArea"]
MAP["memory_set.map()"]
CHECK["Check result"]
UNMAP["memory_set.unmap()"]
HANDLE["Handle MappingError"]
VERIFY["Verify area splitting"]
ITER["memory_set.iter()"]
END["Operation complete"]

BACKEND_MAP --> PT_UPDATE
BACKEND_UNMAP --> PT_UPDATE
CHECK --> HANDLE
CHECK --> UNMAP
CREATE --> MAP
HANDLE --> END
INIT --> CREATE
ITER --> END
MAP --> BACKEND_MAP
MAP --> CHECK
UNMAP --> BACKEND_UNMAP
UNMAP --> VERIFY
VERIFY --> ITER

Sources: README.md(L18 - L88) 

Advanced Examples and Testing

Relevant source files

This page covers advanced usage patterns, comprehensive testing strategies, and the MockBackend implementation that demonstrates the full capabilities of the memory_set crate. It focuses on complex memory management scenarios, sophisticated testing patterns, and how to implement custom backends for specialized use cases.

For basic usage patterns and simple examples, see Basic Usage Patterns. For implementation details of the core types, see Implementation Details.

MockBackend Implementation Pattern

The memory_set crate includes a comprehensive mock implementation that serves both as a testing framework and as an example of how to implement the MappingBackend trait for custom use cases.

Mock Type Definitions

The testing framework defines simplified types that demonstrate the generic nature of the memory management system:

flowchart TD
subgraph subGraph2["Array-Based Page Table"]
    array_entry["pt[addr] = flags"]
    zero_check["pt[addr] == 0 (unmapped)"]
    flag_update["pt[addr] = new_flags"]
end
subgraph subGraph1["MappingBackend Implementation"]
    map_method["map(start, size, flags, pt)"]
    unmap_method["unmap(start, size, pt)"]
    protect_method["protect(start, size, new_flags, pt)"]
end
subgraph subGraph0["Mock Type System"]
    MF["MockFlags = u8"]
    MPT["MockPageTable = [MockFlags; MAX_ADDR]"]
    MB["MockBackend (struct)"]
    MMS["MockMemorySet = MemorySet<MockFlags, MockPageTable, MockBackend>"]
end

MB --> MMS
MB --> map_method
MB --> protect_method
MB --> unmap_method
MF --> MB
MPT --> MB
MPT --> array_entry
MPT --> flag_update
MPT --> zero_check
map_method --> array_entry
protect_method --> flag_update
unmap_method --> zero_check

MockBackend Implementation Strategy

The MockBackend uses a simple array where each index represents a virtual address and the value represents the mapping flags. This approach enables:

  • Direct address-to-flag mapping for O(1) lookups
  • Simple validation of mapping state
  • Easy verification of operations in tests

Sources: src/tests.rs(L5 - L13)  src/tests.rs(L15 - L51) 

Backend Operation Implementation

The mock implementation demonstrates the three core operations required by the MappingBackend trait:

sequenceDiagram
    participant Test as Test
    participant MockBackend as MockBackend
    participant MockPageTable as MockPageTable

    Note over Test,MockPageTable: Mapping Operation
    Test ->> MockBackend: map(start, size, flags, pt)
    MockBackend ->> MockPageTable: Check pt[start..start+size] == 0
    alt All entries unmapped
        MockBackend ->> MockPageTable: Set pt[addr] = flags for range
        MockBackend -->> Test: true (success)
    else Some entries already mapped
        MockBackend -->> Test: false (failure)
    end
    Note over Test,MockPageTable: Unmapping Operation
    Test ->> MockBackend: unmap(start, size, pt)
    MockBackend ->> MockPageTable: Check pt[start..start+size] != 0
    alt All entries mapped
        MockBackend ->> MockPageTable: Set pt[addr] = 0 for range
        MockBackend -->> Test: true (success)
    else Some entries already unmapped
        MockBackend -->> Test: false (failure)
    end
    Note over Test,MockPageTable: Protection Operation
    Test ->> MockBackend: protect(start, size, new_flags, pt)
    MockBackend ->> MockPageTable: Check pt[start..start+size] != 0
    alt All entries mapped
        MockBackend ->> MockPageTable: Set pt[addr] = new_flags for range
        MockBackend -->> Test: true (success)
    else Some entries unmapped
        MockBackend -->> Test: false (failure)
    end

Key Implementation Details:

  • Validation First: Each operation validates the current state before making changes
  • Atomic Failure: Operations fail completely if any part of the range is in an invalid state
  • Simple State Model: Uses 0 for unmapped, non-zero for mapped with specific flags

Sources: src/tests.rs(L16 - L24)  src/tests.rs(L26 - L34)  src/tests.rs(L36 - L50) 

Testing Utilities and Assertion Framework

The test suite includes specialized utilities for testing memory management operations:

Custom Assertion Macros

MacroPurposeUsage Pattern
assert_ok!Verify operation successassert_ok!(set.map(area, &mut pt, false))
assert_err!Verify operation failureassert_err!(operation, ExpectedError)

Debug and Inspection Tools

flowchart TD
subgraph subGraph1["Verification Methods"]
    len_check["set.len() validation"]
    pt_check["Page table state verification"]
    area_check["Individual area validation"]
    range_check["Address range verification"]
end
subgraph subGraph0["Testing Infrastructure"]
    dump["dump_memory_set()"]
    lock["DUMP_LOCK (Mutex)"]
    iter["set.iter()"]
    find["set.find(addr)"]
end
verification["Test Assertions"]

area_check --> verification
dump --> iter
dump --> lock
find --> area_check
iter --> area_check
len_check --> verification
pt_check --> verification
range_check --> verification

Debug Output Pattern: The dump_memory_set function provides synchronized debug output showing the current state of all memory areas, which is essential for understanding complex test scenarios involving area splitting and merging.

Sources: src/tests.rs(L53 - L66)  src/tests.rs(L68 - L77) 

Complex Memory Management Testing

Overlapping Area Management

The test suite demonstrates sophisticated overlap handling scenarios:

flowchart TD
subgraph subGraph2["Resolution Strategy"]
    check_flag["unmap_overlap = false"]
    error_result["Returns AlreadyExists"]
    force_flag["unmap_overlap = true"]
    success_result["Removes overlapping areas, maps new area"]
end
subgraph subGraph1["Overlap Scenario"]
    O["New Area[0x4000, 0x8000) flags=3"]
    overlap_next["Overlaps next area"]
end
subgraph subGraph0["Initial Mapping Pattern"]
    A["Area[0, 0x1000) flags=1"]
    B["Area[0x2000, 0x3000) flags=2"]
    C["Area[0x4000, 0x5000) flags=1"]
    D["Area[0x6000, 0x7000) flags=2"]
end

O --> C
O --> check_flag
O --> force_flag
O --> overlap_next
check_flag --> error_result
force_flag --> success_result

Test Coverage Includes:

  • Overlap Detection: Testing how the system identifies conflicting memory areas
  • Forced Unmapping: Using unmap_overlap=true to resolve conflicts automatically
  • Area Consolidation: Verifying that overlapping areas are properly removed and replaced

Sources: src/tests.rs(L113 - L138) 

Advanced Unmapping and Area Splitting

The most complex testing scenarios involve partial unmapping operations that split existing areas:

flowchart TD
subgraph subGraph3["Split Operation Types"]
    shrink_left["Shrink from right"]
    shrink_right["Shrink from left"]
    split_middle["Split in middle"]
end
subgraph subGraph2["Resulting Areas"]
    left["Area[0x0, 0xc00) flags=1"]
    gap["Unmapped[0xc00, 0x2400)"]
    right["Area[0x2400, 0x3000) flags=1"]
end
subgraph subGraph1["Unmap Operation"]
    unmap_op["unmap(0xc00, 0x1800)"]
end
subgraph subGraph0["Original Area State"]
    orig["Area[0x0, 0x1000) flags=1"]
end

orig --> unmap_op
unmap_op --> gap
unmap_op --> left
unmap_op --> right
unmap_op --> shrink_left
unmap_op --> shrink_right
unmap_op --> split_middle

Complex Splitting Scenarios:

  • Boundary Shrinking: Areas shrink when unmapping occurs at boundaries
  • Middle Splitting: Areas split into two when unmapping occurs in the middle
  • Cross-Boundary Operations: Unmapping spans multiple areas with different behaviors

Sources: src/tests.rs(L152 - L226) 

Protection and Flag Management Testing

Dynamic Protection Changes

The protection testing demonstrates how memory areas adapt to changing access permissions:

stateDiagram-v2
[*] --> init : Map areas with flags=0x7
init : Initial Areas
init : Areas with flags=0x7
init --> protect1 : protect(addr, size, update_flags(0x1))
init : Initial Areas
init : Areas with flags=0x7
protect1 : After First Protect
protect1 : Split areas with flags=0x7,0x1
protect1 --> protect2 : protect(addr, size, update_flags(0x13))
protect1 : After First Protect
protect1 : Split areas with flags=0x7,0x1
protect2 : After Second Protect
protect2 : Further split with flags=0x7,0x1,0x3
protect2 --> [*] : unmap(0, MAX_ADDR)
protect2 : After Second Protect
protect2 : Further split with flags=0x7,0x1,0x3
note left of protect1 : ['Areas split at protection boundaries<br>Original: 8 areas<br>Result: 23 areas']
protect1 : After First Protect
protect1 : Split areas with flags=0x7,0x1
note left of protect2 : ['Further splitting on nested protection<br>Result: 39 areas']
protect2 : After Second Protect
protect2 : Further split with flags=0x7,0x1,0x3

Protection Update Function Pattern: The tests use a closure-based approach for flag updates, allowing complex flag transformation logic while preserving other flag bits:

let update_flags = |new_flags: MockFlags| {
    move |old_flags: MockFlags| -> Option<MockFlags> {
        // Complex flag transformation logic
    }
};

Sources: src/tests.rs(L228 - L324) 

Protection Optimization Testing

flowchart TD
subgraph subGraph1["Protection Application"]
    subgraph subGraph0["Protection Optimization"]
        diff_flags["Different flags"]
        apply_op["Apply protection"]
        area_split["Area splitting occurs"]
        same_flags["Same flags check"]
        skip_op["Skip operation"]
        no_split["No area splitting"]
    end
end

apply_op --> area_split
diff_flags --> apply_op
same_flags --> skip_op
skip_op --> no_split

Optimization Strategy: The protection system includes optimization to avoid unnecessary operations when the new flags would be identical to existing flags, preventing unnecessary area splitting.

Sources: src/tests.rs(L312 - L316) 

Test Scenario Architecture

Comprehensive Test Coverage Matrix

Test CategoryOperations TestedArea BehaviorValidation Focus
test_map_unmapMap, Find, Forced Map, UnmapOverlap handling, Area replacementPage table consistency, Area count
test_unmap_splitPartial Unmap, Boundary operationsArea shrinking, Middle splittingSplit correctness, Gap verification
test_protectProtection changes, Flag updatesProtection-based splittingFlag transformation, Optimization

Testing Pattern Integration

flowchart TD
subgraph subGraph1["Validation Strategies"]
    area_count["Area count verification"]
    pt_state["Page table state check"]
    area_props["Individual area properties"]
    consistency["Cross-validation consistency"]
end
subgraph subGraph0["Test Execution Flow"]
    setup["Setup MockMemorySet + MockPageTable"]
    pattern["Execute test pattern"]
    validate["Validate results"]
    debug["Debug output (if needed)"]
    cleanup["Verify cleanup"]
end

debug --> cleanup
pattern --> validate
setup --> pattern
validate --> area_count
validate --> area_props
validate --> consistency
validate --> debug
validate --> pt_state

Testing Integration Points:

  • State Verification: Each test verifies both the MemorySet state and the MockPageTable state
  • Debug Integration: Debug output is synchronized and can be enabled for complex test debugging
  • Cleanup Validation: Tests ensure complete cleanup to verify the unmapping functionality

Sources: src/tests.rs(L79 - L149)  src/tests.rs(L151 - L226)  src/tests.rs(L228 - L324) 

Development and Project Setup

Relevant source files

This document provides essential information for developers working on or with the memory_set crate, covering project dependencies, build configuration, supported targets, and development workflows. For implementation details and API usage, see Implementation Details and Usage and Examples.

Project Overview and Structure

The memory_set crate is designed as a foundational component within the ArceOS ecosystem, providing no_std compatible memory mapping management capabilities. The project follows standard Rust conventions with additional considerations for embedded and OS development environments.

Project Structure and Dependencies

flowchart TD
subgraph Licensing["Licensing"]
    LIC["GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
end
subgraph Categorization["Categorization"]
    KW["keywords: [arceos, virtual-memory, memory-area, mmap]"]
    CAT["categories: [os, memory-management, no-std]"]
end
subgraph subGraph2["External Dependencies"]
    MA["memory_addr = 0.2"]
end
subgraph subGraph1["Package Metadata"]
    PN["name: memory_set"]
    PV["version: 0.1.0"]
    PE["edition: 2021"]
    PA["author: Yuekai Jia"]
end
subgraph subGraph0["Project Configuration"]
    CT["Cargo.toml"]
    CI[".github/workflows/ci.yml"]
end

CT --> CAT
CT --> KW
CT --> LIC
CT --> MA
CT --> PA
CT --> PE
CT --> PN
CT --> PV

Sources: Cargo.toml(L1 - L16) 

The crate maintains minimal external dependencies, requiring only the memory_addr crate for address type definitions. This design choice supports the no_std environment requirement and reduces compilation complexity.

ConfigurationValuePurpose
Edition2021Modern Rust language features
Categoriesos,memory-management,no-stdEcosystem positioning
LicenseTriple-licensedCompatibility with various projects
Repositorygithub.com/arceos-org/memory_setSource code location
Documentationdocs.rs/memory_setAPI documentation

Sources: Cargo.toml(L1 - L16) 

Build Configuration and Target Support

The project supports multiple target architectures, reflecting its intended use in diverse embedded and OS development scenarios. The build configuration accommodates both hosted and bare-metal environments.

Supported Target Architectures

flowchart TD
subgraph subGraph2["Bare Metal Targets"]
    X86N["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Hosted Targets"]
    X86L["x86_64-unknown-linux-gnu"]
end
subgraph subGraph0["CI Target Matrix"]
    RT["nightly toolchain"]
end

RT --> ARM64
RT --> RISCV
RT --> X86L
RT --> X86N

Sources: .github/workflows/ci.yml(L11 - L12) 

The build process includes several validation steps:

StepCommandPurpose
Format Checkcargo fmt --all -- --checkCode style enforcement
Lintingcargo clippy --target TARGET --all-featuresStatic analysis
Buildcargo build --target TARGET --all-featuresCompilation verification
Testingcargo test --target TARGETUnit test execution

Sources: .github/workflows/ci.yml(L23 - L30) 

Toolchain Requirements

The project requires the nightly Rust toolchain with specific components:

  • rust-src: Required for no_std target compilation
  • clippy: Static analysis and linting
  • rustfmt: Code formatting enforcement

Toolchain Setup Process

sequenceDiagram
    participant Developer as Developer
    participant CISystem as CI System
    participant RustToolchain as Rust Toolchain
    participant TargetBuild as Target Build

    Developer ->> CISystem: "Push/PR trigger"
    CISystem ->> RustToolchain: "Install nightly toolchain"
    RustToolchain ->> CISystem: "rust-src, clippy, rustfmt components"
    CISystem ->> RustToolchain: "rustc --version --verbose"
    loop "For each target"
        CISystem ->> TargetBuild: "cargo fmt --check"
        CISystem ->> TargetBuild: "cargo clippy --target TARGET"
        CISystem ->> TargetBuild: "cargo build --target TARGET"
    alt "x86_64-unknown-linux-gnu only"
        CISystem ->> TargetBuild: "cargo test --target TARGET"
    end
    end

Sources: .github/workflows/ci.yml(L14 - L30) 

Development Workflow and CI Pipeline

The continuous integration system enforces code quality standards and validates functionality across all supported targets. The workflow is designed to catch issues early and maintain consistency.

CI Job Configuration

The CI pipeline consists of two primary jobs:

  1. Main CI Job (ci): Validates code across all target architectures
  2. Documentation Job (doc): Builds and deploys API documentation

CI Pipeline Architecture

flowchart TD
subgraph subGraph5["Documentation Job"]
    DOCBUILD["cargo doc --no-deps"]
    DEPLOY["Deploy to gh-pages"]
end
subgraph subGraph4["Validation Steps"]
    FMT["cargo fmt --check"]
    CLIP["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test (linux only)"]
end
subgraph subGraph3["CI Job Matrix"]
    subgraph Targets["Targets"]
        T1["x86_64-unknown-linux-gnu"]
        T2["x86_64-unknown-none"]
        T3["riscv64gc-unknown-none-elf"]
        T4["aarch64-unknown-none-softfloat"]
    end
    subgraph Toolchain["Toolchain"]
        NIGHTLY["nightly"]
    end
end
subgraph subGraph0["Trigger Events"]
    PUSH["push events"]
    PR["pull_request events"]
end
CI["CI"]

BUILD --> TEST
CI --> DOCBUILD
CLIP --> BUILD
DOCBUILD --> DEPLOY
FMT --> CLIP
NIGHTLY --> T1
NIGHTLY --> T2
NIGHTLY --> T3
NIGHTLY --> T4
PR --> CI
PUSH --> CI
T1 --> FMT
T2 --> FMT
T3 --> FMT
T4 --> FMT

Sources: .github/workflows/ci.yml(L1 - L56) 

Documentation Generation

The documentation workflow includes specialized configuration for maintaining high-quality API documentation:

  • RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
  • Deployment: Automatic GitHub Pages deployment on main branch
  • Index Generation: Automatic redirect to crate documentation

Sources: .github/workflows/ci.yml(L40 - L48) 

Testing Strategy

Testing is performed only on the x86_64-unknown-linux-gnu target due to the hosted environment requirements for test execution. The test command includes --nocapture to display output from test functions.

Test Execution Conditions

flowchart TD
START["CI Job Start"]
CHECK_TARGET["Target == x86_64-unknown-linux-gnu?"]
RUN_TESTS["cargo test --target TARGET -- --nocapture"]
SKIP_TESTS["Skip unit tests"]
END["Job Complete"]

CHECK_TARGET --> RUN_TESTS
CHECK_TARGET --> SKIP_TESTS
RUN_TESTS --> END
SKIP_TESTS --> END
START --> CHECK_TARGET

Sources: .github/workflows/ci.yml(L28 - L30) 

ArceOS Ecosystem Integration

The memory_set crate is positioned as a foundational component within the broader ArceOS operating system project. This integration influences several design decisions:

  • Homepage: Points to the main ArceOS project (github.com/arceos-org/arceos)
  • Keywords: Include arceos for discoverability within the ecosystem
  • License: Triple-licensing supports integration with various ArceOS components
  • No-std Compatibility: Essential for kernel-level memory management

Sources: Cargo.toml(L8 - L12) 

The minimal dependency footprint and broad target support make this crate suitable for integration across different ArceOS configurations and deployment scenarios.

Dependencies and Configuration

Relevant source files

This document covers the project dependencies, build configuration, and integration aspects of the memory_set crate within the broader ArceOS ecosystem. It provides essential information for developers who need to understand how to build, configure, and integrate this memory management library.

For information about the actual usage patterns and API details, see Basic Usage Patterns. For development workflow and testing setup, see Development Workflow.

Package Configuration

The memory_set crate is configured as a foundational memory management library designed specifically for operating system development. The package configuration reflects its role as a core component in the ArceOS ecosystem.

Crate Metadata

ConfigurationValue
Package Namememory_set
Version0.1.0
Rust Edition2021
LicenseTriple licensed: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
Categoriesos,memory-management,no-std

The crate is explicitly designed for no-std environments, making it suitable for kernel-level and embedded development where the standard library is not available.

flowchart TD
subgraph subGraph2["ArceOS Integration"]
    HOMEPAGE["homepage = 'github.com/arceos-org/arceos'"]
    KEYWORDS["keywords = ['arceos', 'virtual-memory', 'memory-area', 'mmap']"]
    REPO["repository = 'github.com/arceos-org/memory_set'"]
end
subgraph subGraph1["Target Environment"]
    NOSTD["no-std compatible"]
    KERNEL["Kernel development"]
    EMBED["Embedded systems"]
    OS["Operating systems"]
end
subgraph subGraph0["Package Metadata"]
    PKG["memory_set v0.1.0"]
    ED["edition = '2021'"]
    LIC["Triple License"]
    CAT["Categories: os, memory-management, no-std"]
end
GPL["GPL-3.0-or-later"]
APACHE["Apache-2.0"]
MULAN["MulanPSL-2.0"]

HOMEPAGE --> PKG
KEYWORDS --> PKG
LIC --> APACHE
LIC --> GPL
LIC --> MULAN
PKG --> EMBED
PKG --> KERNEL
PKG --> NOSTD
PKG --> OS
REPO --> PKG

Crate Package Configuration Structure

Sources: Cargo.toml(L1 - L13) 

Dependencies

The memory_set crate maintains a minimal dependency footprint with only one external dependency, reflecting its design philosophy of being a lightweight, foundational library.

External Dependencies

The crate depends on a single external library:

  • memory_addr = "0.2" - Provides core address types and utilities for virtual memory management

This dependency provides the fundamental VirtAddr and VirtAddrRange types that are used throughout the memory management system.

flowchart TD
subgraph subGraph2["Standard Library"]
    CORE["core (no-std)"]
    ALLOC["alloc (BTreeMap)"]
end
subgraph subGraph1["External Dependencies"]
    MEMADDR["memory_addr v0.2"]
    VIRTADDR["VirtAddr"]
    VIRTADDRRANGE["VirtAddrRange"]
end
subgraph subGraph0["memory_set Crate"]
    MS["MemorySet"]
    MA["MemoryArea"]
    MB["MappingBackend"]
    VAR["VirtAddrRange usage"]
    VA["VirtAddr usage"]
end

MA --> CORE
MA --> VA
MA --> VAR
MB --> CORE
MS --> ALLOC
MS --> VA
MS --> VAR
VA --> VIRTADDR
VAR --> VIRTADDRRANGE
VIRTADDR --> MEMADDR
VIRTADDRRANGE --> MEMADDR

Dependency Graph and Type Flow

Internal Standard Library Usage

While the crate is no-std compatible, it uses specific components from the Rust standard library:

  • core - Fundamental types and traits (available in no-std)
  • alloc - For BTreeMap collection used in MemorySet (requires allocator)

The use of BTreeMap indicates that while the crate is no-std, it requires an allocator to be available in the target environment.

Sources: Cargo.toml(L14 - L15) 

Build Configuration

Rust Edition and Compiler Features

The crate uses Rust Edition 2021, which provides access to modern Rust language features while maintaining compatibility with no-std environments.

Key build characteristics:

  • No standard library dependency - Suitable for kernel and embedded development
  • Allocator requirement - Uses BTreeMap which requires heap allocation
  • Generic-heavy design - Relies on Rust's zero-cost abstractions

Target Environment Compatibility

flowchart TD
subgraph subGraph2["Required Components"]
    CORE["core library"]
    ALLOCLIB["alloc library"]
    BTREE["BTreeMap support"]
end
subgraph subGraph1["Supported Targets"]
    KERNEL["Kernel modules"]
    RTOS["Real-time OS"]
    EMBED["Embedded systems"]
    BAREMETAL["Bare metal"]
end
subgraph subGraph0["Build Requirements"]
    RUST2021["Rust Edition 2021"]
    NOSTD["no-std environment"]
    ALLOC["Allocator available"]
end

ALLOC --> ALLOCLIB
ALLOCLIB --> BTREE
BTREE --> CORE
NOSTD --> ALLOC
NOSTD --> BAREMETAL
NOSTD --> EMBED
NOSTD --> KERNEL
NOSTD --> RTOS
RUST2021 --> NOSTD

Build Environment and Target Compatibility

Sources: Cargo.toml(L4)  Cargo.toml(L12) 

ArceOS Ecosystem Integration

The memory_set crate is designed as a foundational component within the ArceOS operating system ecosystem. Its configuration reflects this tight integration while maintaining modularity.

Ecosystem Position

The crate serves as a core memory management building block within ArceOS:

  • Homepage: Points to the main ArceOS project (https://github.com/arceos-org/arceos)
  • Keywords: Explicitly includes "arceos" along with memory management terms
  • Repository: Maintains separate repository for modular development
  • License: Uses triple licensing compatible with various OS licensing requirements

Integration Points

AspectConfigurationPurpose
Keywords"arceos", "virtual-memory", "memory-area", "mmap"Discoverability and categorization
Categories"os", "memory-management", "no-std"Crate ecosystem positioning
Documentationdocs.rs/memory_setPublic API documentation
HomepageArceOS main projectEcosystem context

The triple licensing scheme (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0) provides flexibility for integration into various operating system projects with different licensing requirements.

Sources: Cargo.toml(L7 - L12) 

Development Environment Setup

Prerequisites

To work with the memory_set crate, developers need:

  1. Rust toolchain with Edition 2021 support
  2. Target environment that supports no-std and alloc
  3. Allocator implementation for the target platform

Build Commands

Standard Rust cargo commands apply:

  • cargo build - Build the library
  • cargo test - Run tests (requires test environment with allocator)
  • cargo doc - Generate documentation

The crate's minimal dependency structure ensures fast build times and reduced complexity in integration scenarios.

Sources: Cargo.toml(L1 - L16) 

Development Workflow

Relevant source files

This document covers the continuous integration setup, testing strategies, and development environment configuration for contributors to the memory_set crate. It explains how code quality is maintained, how testing is automated, and what tools developers need to work effectively with the codebase.

For information about project dependencies and build configuration, see Dependencies and Configuration. For usage examples and testing patterns, see Advanced Examples and Testing.

CI Pipeline Overview

The memory_set crate uses GitHub Actions for continuous integration, with a comprehensive pipeline that ensures code quality across multiple Rust targets and maintains documentation.

CI Workflow Structure

flowchart TD
PR["Push / Pull Request"]
CIJob["ci job"]
DocJob["doc job"]
Matrix["Matrix Strategy"]
T1["x86_64-unknown-linux-gnu"]
T2["x86_64-unknown-none"]
T3["riscv64gc-unknown-none-elf"]
T4["aarch64-unknown-none-softfloat"]
Steps1["Format → Clippy → Build → Test"]
Steps2["Format → Clippy → Build"]
Steps3["Format → Clippy → Build"]
Steps4["Format → Clippy → Build"]
DocBuild["cargo doc --no-deps --all-features"]
GHPages["Deploy to gh-pages"]
Success["CI Success"]

CIJob --> Matrix
DocBuild --> GHPages
DocJob --> DocBuild
GHPages --> Success
Matrix --> T1
Matrix --> T2
Matrix --> T3
Matrix --> T4
PR --> CIJob
PR --> DocJob
Steps1 --> Success
Steps2 --> Success
Steps3 --> Success
Steps4 --> Success
T1 --> Steps1
T2 --> Steps2
T3 --> Steps3
T4 --> Steps4

Sources: .github/workflows/ci.yml(L1 - L56) 

Target Platform Matrix

The CI pipeline tests across multiple Rust targets to ensure compatibility with different architectures and environments:

TargetPurposeTesting Level
x86_64-unknown-linux-gnuStandard Linux developmentFull (includes unit tests)
x86_64-unknown-noneBare metal x86_64Build and lint only
riscv64gc-unknown-none-elfRISC-V bare metalBuild and lint only
aarch64-unknown-none-softfloatARM64 bare metalBuild and lint only

Sources: .github/workflows/ci.yml(L11 - L12) 

Code Quality Pipeline

Quality Check Sequence

sequenceDiagram
    participant Developer as "Developer"
    participant GitHubActions as "GitHub Actions"
    participant RustCompiler as "Rust Compiler"
    participant ClippyLinter as "Clippy Linter"
    participant rustfmt as "rustfmt"

    Developer ->> GitHubActions: "git push / PR"
    GitHubActions ->> rustfmt: "cargo fmt --all -- --check"
    rustfmt -->> GitHubActions: "Format check result"
    GitHubActions ->> ClippyLinter: "cargo clippy --target TARGET --all-features"
    ClippyLinter ->> RustCompiler: "Run analysis"
    RustCompiler -->> ClippyLinter: "Lint results"
    ClippyLinter -->> GitHubActions: "Clippy results (-A clippy::new_without_default)"
    GitHubActions ->> RustCompiler: "cargo build --target TARGET --all-features"
    RustCompiler -->> GitHubActions: "Build result"
    alt Target is x86_64-unknown-linux-gnu
        GitHubActions ->> RustCompiler: "cargo test --target TARGET -- --nocapture"
        RustCompiler -->> GitHubActions: "Test results"
    end
    GitHubActions -->> Developer: "CI Status"

Sources: .github/workflows/ci.yml(L22 - L30) 

Code Quality Tools Configuration

The CI pipeline enforces several code quality standards:

  • Format Checking: Uses cargo fmt --all -- --check to ensure consistent code formatting
  • Linting: Runs cargo clippy with custom configuration allowing clippy::new_without_default
  • Compilation: Verifies code builds successfully across all target platforms
  • Testing: Executes unit tests on the primary Linux target with --nocapture flag

Sources: .github/workflows/ci.yml(L22 - L30) 

Testing Strategy

Test Execution Environment

flowchart TD
subgraph subGraph1["Test Execution"]
    OnlyLinux["Tests run only on x86_64-unknown-linux-gnu"]
    NoCapture["--nocapture flag enabled"]
    UnitTests["Unit tests from lib.rs and modules"]
end
subgraph subGraph0["Test Environment"]
    Ubuntu["ubuntu-latest runner"]
    Nightly["Rust nightly toolchain"]
    Components["rust-src + clippy + rustfmt"]
end
subgraph subGraph2["Cross-Platform Validation"]
    BuildOnly["Build-only testing on bare metal targets"]
    LintOnly["Clippy linting on all targets"]
    FormatAll["Format checking across all code"]
end

BuildOnly --> LintOnly
Components --> OnlyLinux
LintOnly --> FormatAll
Nightly --> OnlyLinux
OnlyLinux --> NoCapture
OnlyLinux --> UnitTests
Ubuntu --> OnlyLinux

Sources: .github/workflows/ci.yml(L28 - L30)  .github/workflows/ci.yml(L11 - L19) 

Testing Limitations and Strategy

The testing strategy reflects the embedded/systems programming nature of the memory_set crate:

  • Full Testing: Only performed on x86_64-unknown-linux-gnu where standard library features are available
  • Build Verification: All bare metal targets are compiled to ensure cross-platform compatibility
  • Mock Testing: The crate includes MockBackend implementation for testing without real page tables

Sources: .github/workflows/ci.yml(L29 - L30) 

Documentation Workflow

Documentation Build and Deployment

flowchart TD
subgraph Deployment["Deployment"]
    DefaultBranch["Is default branch?"]
    GHPages["Deploy to gh-pages"]
    SkipDeploy["Skip deployment"]
end
subgraph subGraph1["Quality Checks"]
    BrokenLinks["-D rustdoc::broken_intra_doc_links"]
    MissingDocs["-D missing-docs"]
end
subgraph subGraph0["Doc Generation"]
    DocBuild["cargo doc --no-deps --all-features"]
    DocFlags["RUSTDOCFLAGS checks"]
    IndexGen["Generate redirect index.html"]
end

BrokenLinks --> IndexGen
DefaultBranch --> GHPages
DefaultBranch --> SkipDeploy
DocBuild --> DocFlags
DocFlags --> BrokenLinks
DocFlags --> MissingDocs
IndexGen --> DefaultBranch
MissingDocs --> IndexGen

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Quality Standards

The documentation workflow enforces strict quality standards:

  • Broken Link Detection: -D rustdoc::broken_intra_doc_links fails build on broken documentation links
  • Missing Documentation: -D missing-docs requires all public APIs to have documentation
  • Automatic Deployment: Documentation is automatically deployed to GitHub Pages on default branch updates

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L55) 

Development Environment Setup

Required Toolchain Components

For local development, contributors need:

# Install nightly Rust toolchain
rustup toolchain install nightly

# Add required components
rustup component add rust-src clippy rustfmt

# Add target platforms for cross-compilation testing
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf  
rustup target add aarch64-unknown-none-softfloat

Sources: .github/workflows/ci.yml(L15 - L19) 

Local Development Commands

To replicate CI checks locally:

# Format check
cargo fmt --all -- --check

# Linting  
cargo clippy --all-features -- -A clippy::new_without_default

# Build for different targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features

# Run tests
cargo test -- --nocapture

# Generate documentation
cargo doc --no-deps --all-features

Sources: .github/workflows/ci.yml(L22 - L30)  .github/workflows/ci.yml(L47) 

Git Configuration

The project maintains a standard .gitignore configuration:

  • /target - Rust build artifacts
  • /.vscode - IDE configuration files
  • .DS_Store - macOS system files
  • Cargo.lock - Dependency lock file (excluded for libraries)

Sources: .gitignore(L1 - L5) 

Overview

Relevant source files

This document provides an introduction to the int_ratio crate, an efficient integer-based rational arithmetic library designed for embedded systems and performance-critical applications within the ArceOS ecosystem. The crate implements rational number operations using optimized integer arithmetic that avoids expensive division operations.

For detailed implementation specifics of the core Ratio type, see Ratio Type Implementation. For practical usage examples and patterns, see Usage Guide.

Purpose and Scope

The int_ratio crate serves as a foundational mathematical library that provides precise rational number arithmetic without relying on floating-point operations or expensive integer division. It is specifically designed for use in embedded systems, bare-metal environments, and other performance-critical scenarios where computational efficiency and deterministic behavior are essential.

Sources: Cargo.toml(L1 - L15)  README.md(L1 - L22) 

Core Functionality

The crate centers around the Ratio struct, which represents a rational number as the quotient of two integers. Rather than storing the numerator and denominator directly, the implementation uses an optimized internal representation that enables fast multiplication operations through bit shifting.

Ratio Operations

flowchart TD
subgraph Output["Output"]
    I["u64 results"]
    J["Ratio instances"]
end
subgraph subGraph1["Internal Representation"]
    F["mult: u32"]
    G["shift: u32"]
    H["ratio = mult / (1 << shift)"]
end
subgraph subGraph0["Public API"]
    A["Ratio::new(num, den)"]
    B["Ratio::zero()"]
    C["ratio.mul_trunc(value)"]
    D["ratio.mul_round(value)"]
    E["ratio.inverse()"]
end

A --> F
A --> G
B --> J
C --> I
D --> I
E --> J
F --> H
G --> H

Ratio API and Internal Architecture

The Ratio::new() constructor transforms input numerator and denominator values into an optimized mult/shift representation, while mul_trunc() and mul_round() provide different precision behaviors for arithmetic operations.

Sources: README.md(L14 - L21) 

ArceOS Ecosystem Integration

The int_ratio crate operates as a core mathematical utility within the broader ArceOS operating system ecosystem, providing rational arithmetic capabilities that support system-level operations requiring precise fractional calculations.

flowchart TD
subgraph subGraph2["Application Domains"]
    H["Timing Systems"]
    I["Resource Management"]
    J["Scheduling Algorithms"]
    K["Performance Monitoring"]
end
subgraph subGraph1["Target Platforms"]
    D["x86_64 Linux"]
    E["x86_64 Bare Metal"]
    F["RISC-V Embedded"]
    G["ARM64 Embedded"]
end
subgraph subGraph0["ArceOS Ecosystem"]
    A["ArceOS Operating System"]
    B["int_ratio crate"]
    C["System Components"]
end

A --> B
B --> C
B --> D
B --> E
B --> F
B --> G
C --> H
C --> I
C --> J
C --> K

ArceOS Integration and Platform Support

The crate provides cross-platform rational arithmetic services that enable ArceOS system components to perform precise fractional calculations across multiple hardware architectures.

Sources: Cargo.toml(L8 - L9) 

Key Design Principles

Performance Optimization

The core innovation of int_ratio lies in its avoidance of expensive division operations during runtime calculations. Instead of performing value * numerator / denominator, the implementation pre-computes an equivalent mult and shift representation that enables the same calculation using fast bit-shift operations: (value * mult) >> shift.

Precision Control

The crate provides explicit control over rounding behavior through distinct methods:

MethodBehaviorUse Case
mul_trunc()Truncates fractional resultsWhen lower bounds are needed
mul_round()Rounds to nearest integerWhen best approximation is needed

Embedded System Compatibility

The library operates in no_std environments and avoids dependencies that would be unsuitable for embedded or bare-metal development. This makes it particularly valuable for resource-constrained systems where standard library features may not be available.

Sources: Cargo.toml(L11 - L12)  README.md(L7 - L10) 

Mathematical Foundation

The transformation from traditional rational representation numerator/denominator to the optimized mult/(1<<shift) form maintains mathematical precision while enabling performance improvements. This approach ensures that:

  • No u128 division operations are required during multiplication
  • Bit-shift operations replace expensive division
  • Precision is preserved through careful selection of mult and shift values
  • Results remain deterministic and reproducible across platforms

The example from the README demonstrates this optimization: Ratio(1/3 ~= 1431655765/4294967296) shows how the fraction 1/3 is represented internally using the optimized format where 4294967296 = 1 << 32.

Sources: README.md(L18 - L21) 

For implementation details of the core algorithms and internal architecture, see Ratio Type Implementation. For practical examples and common usage patterns, see Usage Guide. Development setup and contribution guidelines are covered in Development Guide.

Ratio Type Implementation

Relevant source files

This document provides a comprehensive overview of the core Ratio struct implementation, focusing on its innovative multiplication-shift optimization strategy that eliminates expensive division operations. The Ratio type transforms traditional numerator/denominator arithmetic into an efficient mult/(1<<shift) representation optimized for embedded and performance-critical applications.

For detailed internal algorithms and mathematical proofs, see Internal Architecture. For complete method documentation and usage examples, see API Reference. For mathematical foundations and precision analysis, see Mathematical Foundation.

Core Transformation Strategy

The fundamental innovation of the Ratio type lies in its preprocessing approach that converts rational arithmetic into optimized integer operations.

Ratio Transformation Process

flowchart TD
subgraph subGraph0["Performance Benefits"]
    J["No division in arithmetic"]
    K["Bit-shift operations only"]
    L["Maintained precision"]
end
A["Input: numerator/denominator"]
B["new() constructor"]
C["Calculate optimal shift value"]
D["Calculate mult factor"]
E["mult = (numerator << shift) / denominator"]
F["Optimize: reduce common factors of 2"]
G["Store: mult/(1<"]
H["mul_trunc(): (value * mult) >> shift"]
I["mul_round(): ((value * mult) + round) >> shift"]

A --> B
B --> C
B --> D
C --> E
D --> E
E --> F
F --> G
G --> H
G --> I
G --> L
H --> J
I --> K

Sources: src/lib.rs(L51 - L84) 

Struct Layout and Field Responsibilities

The Ratio struct maintains both the original rational representation and the optimized computational form:

FieldTypePurposeUsage
numeratoru32Original numerator valueStored forinverse()andDebug
denominatoru32Original denominator valueStored forinverse()andDebug
multu32Optimized multiplierUsed inmul_trunc()andmul_round()
shiftu32Bit-shift amountRepresents power of 2 denominator

The struct maintains both representations to support mathematical operations like inverse() while providing optimized arithmetic through the mult/shift fields.

Sources: src/lib.rs(L13 - L19) 

Constructor Methods and Special Cases

Constructor Method Mapping

flowchart TD
subgraph Outcomes["Outcomes"]
    F["panic!"]
    G["mult=0, shift=0"]
    H["Optimization algorithm"]
end
subgraph subGraph1["Input Validation"]
    C["denominator == 0 && numerator != 0"]
    D["numerator == 0"]
    E["Normal case"]
end
subgraph subGraph0["Constructor Methods"]
    A["new(numerator, denominator)"]
    B["zero()"]
end

A --> C
A --> D
A --> E
B --> G
C --> F
D --> G
E --> H

Sources: src/lib.rs(L37 - L44)  src/lib.rs(L51 - L84) 

The new() constructor implements a sophisticated optimization algorithm:

  1. Shift Maximization: Starts with shift = 32 and decreases until mult fits in u32 src/lib.rs(L63 - L71) 
  2. Precision Optimization: Uses (numerator << shift + denominator/2) / denominator for rounding src/lib.rs(L66) 
  3. Factor Reduction: Removes common factors of 2 to minimize storage requirements src/lib.rs(L73 - L76) 

The special zero() constructor creates a 0/0 ratio that behaves safely in operations, particularly inverse() which returns another zero ratio instead of panicking.

Arithmetic Operations Implementation

The core arithmetic methods leverage the mult/shift representation for efficient computation:

Operation Implementation Details

flowchart TD
subgraph subGraph2["mul_round Implementation"]
    F["(value as u128 * mult as u128)"]
    G["Unsupported markdown: list"]
    H["result >> shift"]
    I["Rounded u64 result"]
end
subgraph subGraph1["mul_trunc Implementation"]
    C["(value as u128 * mult as u128)"]
    D["result >> shift"]
    E["Truncated u64 result"]
end
subgraph Input["Input"]
    A["value: u64"]
    B["Ratio{mult, shift}"]
end

A --> C
A --> F
B --> C
B --> F
C --> D
D --> E
F --> G
G --> H
H --> I

Sources: src/lib.rs(L114 - L116)  src/lib.rs(L130 - L132) 

Key implementation characteristics:

  • Widening Multiplication: Uses u128 intermediate to prevent overflow src/lib.rs(L115)  src/lib.rs(L131) 
  • Truncation vs Rounding: mul_trunc() simply shifts, while mul_round() adds (1 << shift >> 1) for rounding
  • Bit-Shift Performance: Final division replaced with right-shift operation
  • No Division Operations: All arithmetic uses multiplication and bit manipulation

Performance and Precision Characteristics

The Ratio implementation achieves optimal performance through several design decisions:

AspectTraditional ApproachRatioImplementation
Division OperationsRequired for each calculationPre-computed in constructor
Intermediate PrecisionLimited by operand widthMaximized through optimalshift
Platform DependenciesVaries by division hardwareConsistent across architectures
Memory OverheadMinimal (8 bytes)Moderate (16 bytes) for optimization

The Debug implementation reveals the transformation: Ratio(numerator/denominator ~= mult/(1<<shift)) src/lib.rs(L137 - L145)  allowing verification of the optimization quality.

Equality and Comparison Semantics

The PartialEq implementation compares the optimized representation rather than the original values:

// Equality based on mult and shift, not numerator/denominator
self.mult == other.mult && self.shift == other.shift

This design choice ensures that mathematically equivalent ratios with different original representations are considered equal after optimization.

Sources: src/lib.rs(L148 - L153) 

Internal Architecture

Relevant source files

This document explains the internal design and optimization strategies of the Ratio struct, focusing on how it transforms traditional fractional representation into a high-performance mult/(1<<shift) format. This page covers the core data structure, field relationships, and the constructor optimization algorithm that enables division-free arithmetic operations.

For information about the public API methods and their usage, see API Reference. For the mathematical principles underlying the optimization strategy, see Mathematical Foundation.

Core Data Structure

The Ratio struct stores both the original rational number representation and its optimized computational form. This dual representation enables both mathematical correctness and performance optimization.

flowchart TD
subgraph subGraph3["Mathematical Equivalence"]
    G["numerator / denominator ≈ mult / (1 << shift)"]
end
subgraph subGraph2["Optimized Representation"]
    F["mult / (1 << shift)"]
end
subgraph subGraph1["Original Representation"]
    E["numerator / denominator"]
end
subgraph subGraph0["Ratio Struct Fields"]
    A["numerator: u32"]
    B["denominator: u32"]
    C["mult: u32"]
    D["shift: u32"]
end

A --> E
B --> E
C --> F
D --> F
E --> G
F --> G

Ratio Struct Field Mapping

The struct maintains four fields that work together to provide both the original rational number and its optimized computational equivalent:

FieldTypePurposeUsage
numeratoru32Original fraction numeratorMathematical operations, inverse calculation
denominatoru32Original fraction denominatorMathematical operations, inverse calculation
multu32Optimized multiplierFast arithmetic via(value * mult) >> shift
shiftu32Bit shift amountReplaces division with bit shifting

Sources: src/lib.rs(L13 - L19) 

Mult/Shift Transformation Algorithm

The core optimization transforms any rational number numerator/denominator into an equivalent mult/(1<<shift) representation. This transformation enables replacing expensive division operations with fast bit shifts during arithmetic operations.

flowchart TD
subgraph subGraph3["Final Result"]
    L["Optimized mult, shift values"]
end
subgraph subGraph2["Factor Reduction"]
    H["mult % 2 == 0 && shift > 0?"]
    I["mult /= 2"]
    J["shift -= 1"]
    K["Continue reduction"]
end
subgraph subGraph1["Shift Optimization Loop"]
    C["shift = 32"]
    D["mult = (numerator << shift + denominator/2) / denominator"]
    E["mult <= u32::MAX?"]
    F["shift -= 1"]
    G["Continue loop"]
end
subgraph subGraph0["Input Processing"]
    A["numerator: u32"]
    B["denominator: u32"]
end

A --> C
B --> C
C --> D
D --> E
E --> F
E --> H
F --> G
G --> D
H --> I
H --> L
I --> J
J --> K
K --> H

Transformation Process

The algorithm operates in two phases to find the optimal mult and shift values:

  1. Shift Maximization Phase src/lib.rs(L63 - L71) :
  • Starts with shift = 32 (maximum precision)
  • Calculates mult = (numerator << shift + denominator/2) / denominator
  • Reduces shift until mult fits in u32::MAX
  • The + denominator/2 term provides rounding during the division
  1. Factor Reduction Phase src/lib.rs(L73 - L76) :
  • Removes common factors of 2 from mult and shift
  • Continues while mult is even and shift > 0
  • Optimizes for the smallest possible mult value

Sources: src/lib.rs(L62 - L84) 

Constructor Logic Flow

The new constructor implements the transformation algorithm with special handling for edge cases and zero values.

flowchart TD
subgraph subGraph2["Optimization Algorithm"]
    F["Execute shift maximization loop"]
    G["Execute factor reduction loop"]
    H["Construct final Ratio struct"]
end
subgraph subGraph1["Zero Handling"]
    D["numerator == 0?"]
    E["Return Ratio with mult=0, shift=0"]
end
subgraph subGraph0["Input Validation"]
    A["new(numerator, denominator)"]
    B["denominator == 0 && numerator != 0?"]
    C["panic!"]
end

A --> B
B --> C
B --> D
D --> E
D --> F
F --> G
G --> H

Edge Case Handling

The constructor provides specific behavior for mathematical edge cases:

Sources: src/lib.rs(L46 - L84) 

Internal Field Relationships

The optimized fields maintain mathematical equivalence with the original fraction while enabling performance optimizations.

flowchart TD
subgraph subGraph2["Performance Benefits"]
    G["Expensive: 64-bit division"]
    H["Fast: 64-bit multiplication + bit shift"]
end
subgraph subGraph0["Mathematical Equivalence"]
    A["numerator / denominator"]
    B["≈"]
    C["mult / (1 << shift)"]
end
subgraph subGraph1["Arithmetic Operations"]
    D["value * numerator / denominator"]
    E["≈"]
    F["(value * mult) >> shift"]
end

A --> B
A --> G
B --> C
C --> H
D --> E
E --> F

Precision vs Performance Trade-offs

The transformation balances mathematical precision with computational efficiency:

AspectTraditionalOptimized
Representationnumerator/denominatormult/(1<<shift)
Arithmetic(value * num) / denom(value * mult) >> shift
OperationsDivision (expensive)Multiplication + Bit shift
PrecisionExactApproximated withinu32constraints
PerformanceSlower on embeddedOptimized for embedded targets

The shift value is maximized to preserve as much precision as possible within the u32 constraint for mult. The factor reduction phase then optimizes the representation by removing unnecessary powers of 2.

Sources: src/lib.rs(L8 - L11)  src/lib.rs(L114 - L132) 

Zero Value Optimization

The crate provides optimized handling for zero values to avoid computational overhead and support mathematical edge cases.

flowchart TD
subgraph subGraph2["Behavioral Differences"]
    E["Ratio::new(0,x).inverse() → panic"]
    F["Ratio::zero().inverse() → Ratio::zero()"]
end
subgraph subGraph1["Internal Representation"]
    C["mult = 0, shift = 0"]
    D["denominator = 0 (zero case only)"]
end
subgraph subGraph0["Zero Value Types"]
    A["Ratio::new(0, x) where x != 0"]
    B["Ratio::zero() // 0/0 special case"]
end

A --> C
A --> E
B --> C
B --> D
B --> F

The zero optimization eliminates unnecessary computation for zero multiplication operations while providing safe inversion behavior for the special 0/0 case.

Sources: src/lib.rs(L22 - L44)  src/lib.rs(L99 - L101) 

API Reference

Relevant source files

This document provides comprehensive documentation of all public APIs in the int_ratio crate. It covers the Ratio struct, its constructors, arithmetic operations, and utility methods. For implementation details of the internal architecture, see Internal Architecture. For mathematical principles behind the optimization strategy, see Mathematical Foundation.

Core Type

Ratio Struct

The Ratio struct is the central type that represents a rational number optimized for performance through a mult/(1<<shift) representation.

FieldTypePurpose
numeratoru32Original numerator value
denominatoru32Original denominator value
multu32Optimized multiplication factor
shiftu32Bit shift amount for division

The struct implements Clone, Copy, Debug, and PartialEq traits for convenience.

Sources: src/lib.rs(L13 - L19) 

Constructor Methods

Ratio::new

pub const fn new(numerator: u32, denominator: u32) -> Self

Creates a new ratio from numerator/denominator. The constructor performs optimization to convert the fraction into mult/(1<<shift) form for efficient arithmetic operations.

Behavior:

  • Panics if denominator is zero and numerator is non-zero
  • Returns a zero ratio if numerator is zero
  • Calculates optimal mult and shift values to maximize precision
  • Removes common factors of 2 to minimize shift

Example from codebase:

let ratio = Ratio::new(625_000, 1_000_000); // Results in mult=5, shift=3

Sources: src/lib.rs(L51 - L84) 

Ratio::zero

pub const fn zero() -> Self

Creates a special zero ratio with numerator=0 and denominator=0. This differs from other zero ratios (0/x) because it does not panic when inverted, instead returning another zero ratio.

Example from codebase:

let zero = Ratio::zero();
assert_eq!(zero.mul_trunc(123), 0);
assert_eq!(zero.inverse(), Ratio::zero());

Sources: src/lib.rs(L37 - L44) 

Arithmetic Operations

mul_trunc

pub const fn mul_trunc(self, value: u64) -> u64

Multiplies the ratio by a value and truncates (rounds down) the result. Uses the optimized mult and shift values to avoid expensive division.

Implementation: ((value * mult) >> shift)

Example from codebase:

let ratio = Ratio::new(2, 3);
assert_eq!(ratio.mul_trunc(100), 66); // trunc(100 * 2 / 3) = trunc(66.66...) = 66

Sources: src/lib.rs(L114 - L116) 

mul_round

pub const fn mul_round(self, value: u64) -> u64

Multiplies the ratio by a value and rounds to the nearest whole number. Adds a rounding factor before shifting.

Implementation: ((value * mult + (1 << shift >> 1)) >> shift)

Example from codebase:

let ratio = Ratio::new(2, 3);
assert_eq!(ratio.mul_round(100), 67); // round(100 * 2 / 3) = round(66.66...) = 67

Sources: src/lib.rs(L130 - L132) 

Utility Methods

inverse

pub const fn inverse(self) -> Self

Returns the mathematical inverse of the ratio by swapping numerator and denominator. Handles the special zero ratio case gracefully.

Example from codebase:

let ratio = Ratio::new(1, 2);
assert_eq!(ratio.inverse(), Ratio::new(2, 1));

Sources: src/lib.rs(L99 - L101) 

Trait Implementations

Debug

The Debug trait provides readable output showing both original and optimized representations:

// Format: "Ratio(numerator/denominator ~= mult/(1<<shift))"
println!("{:?}", Ratio::new(1, 3)); // Ratio(1/3 ~= 1431655765/4294967296)

Sources: src/lib.rs(L135 - L146) 

PartialEq

Equality comparison based on the optimized mult and shift values rather than original numerator/denominator:

#![allow(unused)]
fn main() {
fn eq(&self, other: &Ratio) -> bool {
    self.mult == other.mult && self.shift == other.shift
}
}

Sources: src/lib.rs(L148 - L153) 

API Usage Patterns

Method Call Flow Diagram

flowchart TD
A["Ratio::new(num, den)"]
B["Ratio instance"]
C["Ratio::zero()"]
D["mul_trunc(value)"]
E["mul_round(value)"]
F["inverse()"]
G["u64 result (truncated)"]
H["u64 result (rounded)"]
I["Ratio instance (inverted)"]
J["Debug formatting"]
K["PartialEq comparison"]
L["String representation"]
M["bool result"]

A --> B
B --> D
B --> E
B --> F
B --> J
B --> K
C --> B
D --> G
E --> H
F --> I
J --> L
K --> M

Sources: src/lib.rs(L21 - L153) 

Method Relationship and Data Flow

Ratio Internal State and Operations

flowchart TD
subgraph Output["Output"]
    J["u64 truncated result"]
    K["u64 rounded result"]
    L["Ratio inverted instance"]
end
subgraph Operations["Operations"]
    G["mul_trunc: (value * mult) >> shift"]
    H["mul_round: (value * mult + round) >> shift"]
    I["inverse: new(denominator, numerator)"]
end
subgraph Construction["Construction"]
    A["new(numerator, denominator)"]
    B["mult calculation"]
    C["zero()"]
    D["mult=0, shift=0"]
    E["shift optimization"]
    F["Ratio{numerator, denominator, mult, shift}"]
end

A --> B
B --> E
C --> D
D --> F
E --> F
F --> G
F --> H
F --> I
G --> J
H --> K
I --> L

Sources: src/lib.rs(L51 - L132) 

Performance Characteristics

OperationTime ComplexityNotes
new()O(1)Constant time optimization during construction
zero()O(1)Direct field assignment
mul_trunc()O(1)Single multiplication and bit shift
mul_round()O(1)Single multiplication, addition, and bit shift
inverse()O(1)Callsnew()with swapped parameters

The key performance benefit is that all arithmetic operations use multiplication and bit shifts instead of division, making them suitable for embedded and real-time systems.

Sources: src/lib.rs(L114 - L116)  src/lib.rs(L130 - L132) 

Mathematical Foundation

Relevant source files

This document explains the mathematical principles underlying the Ratio type's optimization strategy and arithmetic operations. It covers the core transformation from traditional rational arithmetic to fixed-point representation, precision analysis, and the algorithms used to maximize computational efficiency while maintaining mathematical accuracy.

For implementation details of the Ratio API, see API Reference. For architectural specifics of the internal data structures, see Internal Architecture.

Core Mathematical Transformation

The fundamental innovation of the int_ratio crate lies in transforming traditional rational arithmetic numerator/denominator into an optimized fixed-point representation mult/(1<<shift). This transformation enables fast multiplication operations using bit shifts instead of expensive division operations.

Mathematical Equivalence

The transformation maintains mathematical equivalence through the following relationship:

numerator/denominator = mult/(1<<shift)

Where:

  • mult = (numerator × (1<<shift) + denominator/2) / denominator
  • shift is maximized subject to mult ≤ u32::MAX

Transformation Algorithm Flow

flowchart TD
A["Input: numerator, denominator"]
B["Initialize shift = 32"]
C["Calculate mult = (numerator × 2^shift + denominator/2) / denominator"]
D["mult ≤ u32::MAX?"]
E["shift -= 1"]
F["Optimize: Remove factors of 2"]
G["mult % 2 == 0 AND shift > 0?"]
H["mult /= 2, shift -= 1"]
I["Result: mult, shift"]

A --> B
B --> C
C --> D
D --> E
D --> F
E --> C
F --> G
G --> H
G --> I
H --> G

Sources: src/lib.rs(L62 - L83) 

Precision Optimization Strategy

The algorithm maximizes precision by selecting the largest possible shift value while ensuring mult fits within a u32. This approach balances two competing requirements:

RequirementImplementationBenefit
Maximize precisionStart withshift = 32, decrement untilmult ≤ u32::MAXUses maximum available bits
Minimize computationRemove common factors of 2 frommultandshiftReduces multiplication overhead
Maintain accuracyAdddenominator/2before divisionImplements banker's rounding

Sources: src/lib.rs(L63 - L76) 

Arithmetic Operation Mathematics

Multiplication with Truncation

The mul_trunc operation implements the mathematical formula:

result = ⌊(value × mult) / (1<<shift)⌋

This is efficiently computed as a bit shift operation:

flowchart TD
A["value: u64"]
B["Cast to u128"]
C["Multiply by mult"]
D["Right shift by shift bits"]
E["Cast to u64"]
F["Truncated result"]

A --> B
B --> C
C --> D
D --> E
E --> F

Sources: src/lib.rs(L114 - L116) 

Multiplication with Rounding

The mul_round operation adds a rounding term before the shift:

result = ⌊(value × mult + 2^(shift-1)) / (1<<shift)⌋

The rounding term 2^(shift-1) represents half of the divisor 2^shift, implementing proper mathematical rounding:

Rounding Behavior Analysis

flowchart TD
A["Original: value × numerator / denominator"]
B["Transformed: (value × mult) >> shift"]
C["With rounding: (value × mult + round_term) >> shift"]
D["round_term = 1 << (shift - 1)"]
E["Fractional part < 0.5"]
F["Rounds down"]
G["Fractional part ≥ 0.5"]
H["Rounds up"]

A --> B
B --> C
C --> E
C --> G
D --> C
E --> F
G --> H

Sources: src/lib.rs(L130 - L132) 

Precision Analysis and Bounds

Maximum Precision Calculation

The precision of the representation depends on the selected shift value. The algorithm maximizes precision by:

  1. Starting with maximum shift: Begin with shift = 32 to use all available bits
  2. Constraint enforcement: Ensure mult ≤ u32::MAX to prevent overflow
  3. Optimization: Remove unnecessary factors of 2 to minimize computational cost

Error Bounds

The maximum relative error introduced by the transformation is bounded by:

|error| ≤ 1 / (2^shift)

This bound is achieved because the worst-case rounding error in computing mult is at most 0.5, which translates to a relative error of 0.5 / (2^shift) in the final result.

Error Analysis by Shift Value

Shift ValueMaximum Relative ErrorPrecision (decimal places)
322.33 × 10⁻¹⁰~9.7
161.53 × 10⁻⁵~4.8
83.91 × 10⁻³~2.4

Sources: src/lib.rs(L66 - L77) 

Special Cases and Edge Conditions

Zero Handling

The zero ratio requires special mathematical treatment to avoid division by zero in inverse operations:

flowchart TD
A["Input: numerator = 0"]
B["denominator = 0?"]
C["Zero ratio: 0/0"]
D["Standard zero: 0/denominator"]
E["mult = 0, shift = 0"]
F["Inverse of zero ratio"]
G["Returns zero ratio"]
H["Inverse of standard zero"]
I["Panics: division by zero"]

A --> B
B --> C
B --> D
C --> E
D --> E
F --> G
H --> I

Sources: src/lib.rs(L37 - L44)  src/lib.rs(L53 - L60) 

Boundary Conditions

The algorithm handles several mathematical boundary conditions:

  1. Maximum values: When numerator = u32::MAX and denominator = 1, the result is mult = u32::MAX, shift = 0
  2. Minimum precision: When numerator = 1 and denominator = u32::MAX, maximum shift is used
  3. Unity ratio: When numerator = denominator, the result is mult = 1, shift = 0

Sources: src/lib.rs(L161 - L185) 

Computational Complexity

The mathematical operations achieve the following complexity characteristics:

OperationTime ComplexitySpace ComplexityNotes
new()O(log n)O(1)Due to shift optimization loop
mul_trunc()O(1)O(1)Single multiplication + shift
mul_round()O(1)O(1)Single multiplication + shift
inverse()O(log n)O(1)Callsnew()with swapped parameters

The O(log n) complexity of construction comes from the iterative reduction of the shift value and the factor-of-2 elimination loop.

Sources: src/lib.rs(L65 - L83)  src/lib.rs(L99 - L101) 

Usage Guide

Relevant source files

This guide demonstrates how to use the int_ratio crate in practical applications. It covers the core usage patterns, arithmetic operations, and performance considerations for working with the Ratio type.

For detailed information about the internal architecture and optimization strategies, see Internal Architecture. For comprehensive API documentation, see API Reference.

Creating and Using Ratios

The Ratio type provides efficient rational arithmetic through its optimized mult/(1<<shift) representation. The basic workflow involves creating a ratio from integers and then performing multiplication operations with either truncation or rounding behavior.

Basic Ratio Creation Workflow

flowchart TD
A["Input: numerator, denominator"]
B["Ratio::new()"]
C["Internal optimization"]
D["Ready for calculations"]
E["mul_trunc()"]
F["mul_round()"]
G["inverse()"]
H["Truncated result"]
I["Rounded result"]
J["Inverted ratio"]
K["Special case: Ratio::zero()"]

A --> B
B --> C
C --> D
D --> E
D --> F
D --> G
E --> H
F --> I
G --> J
K --> D

Creating Ratios from Integers

The primary constructor Ratio::new() accepts u32 values for both numerator and denominator:

use int_ratio::Ratio;

// Create a ratio representing 2/3
let ratio = Ratio::new(2, 3);

// Create a ratio representing 625/1000 (0.625)
let fraction = Ratio::new(625, 1000);

// Create unit ratio (1/1)
let unit = Ratio::new(1, 1);

Sources: src/lib.rs(L46 - L84) 

Special Zero Ratio

The Ratio::zero() constructor creates a special 0/0 ratio that behaves safely in all operations, including inversion:

let zero = Ratio::zero();
assert_eq!(zero.mul_trunc(123), 0);
assert_eq!(zero.inverse(), Ratio::zero()); // Safe inversion

Sources: src/lib.rs(L22 - L44) 

Arithmetic Operations

The crate provides two multiplication methods with different rounding behaviors, plus ratio inversion.

Truncation vs Rounding Comparison

OperationMethodBehaviorExample (2/3 × 100)
Truncationmul_trunc()Round down66(from 66.666...)
Roundingmul_round()Round to nearest67(from 66.666...)
Inversioninverse()Swap num/denRatio::new(3, 2)

Truncation Operations

The mul_trunc() method always rounds down to the nearest integer:

let ratio = Ratio::new(2, 3);

// These demonstrate truncation behavior
assert_eq!(ratio.mul_trunc(99), 66);   // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_trunc(100), 66);  // 100 × 2/3 = 66.666... → 66
assert_eq!(ratio.mul_trunc(101), 67);  // 101 × 2/3 = 67.333... → 67

Sources: src/lib.rs(L103 - L116) 

Rounding Operations

The mul_round() method rounds to the nearest integer using standard rounding rules:

let ratio = Ratio::new(2, 3);

// These demonstrate rounding behavior  
assert_eq!(ratio.mul_round(99), 66);   // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_round(100), 67);  // 100 × 2/3 = 66.666... → 67
assert_eq!(ratio.mul_round(101), 67);  // 101 × 2/3 = 67.333... → 67

Sources: src/lib.rs(L118 - L132) 

Common Usage Patterns

Pattern: Time and Frequency Conversion

flowchart TD
A["Clock frequency"]
B["Ratio::new(target_freq, source_freq)"]
C["mul_round(time_value)"]
D["Converted time units"]
E["Timer ticks"]
F["Ratio::new(nanoseconds, tick_freq)"]
G["mul_trunc(tick_count)"]
H["Nanosecond duration"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H

Example: Converting between time units

// Convert microseconds to nanoseconds (1000:1 ratio)
let us_to_ns = Ratio::new(1000, 1);
let nanoseconds = us_to_ns.mul_trunc(42); // 42000 ns

// Convert CPU cycles to milliseconds (assuming 1GHz CPU)
let cycles_to_ms = Ratio::new(1, 1_000_000); // 1ms per 1M cycles  
let duration_ms = cycles_to_ms.mul_round(2_500_000); // ~3ms

Pattern: Resource Scaling

flowchart TD
A["Available memory"]
B["Ratio::new(allocated_portion, total_memory)"]
C["mul_trunc(request_size)"]
D["Allocated amount"]
E["CPU load"]
F["Ratio::new(used_cores, total_cores)"]
G["mul_round(workload)"]
H["Distributed load"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H

Example: Memory allocation ratios

// Allocate 3/4 of available memory to cache
let cache_ratio = Ratio::new(3, 4);
let total_memory = 1024 * 1024 * 1024; // 1GB
let cache_size = cache_ratio.mul_trunc(total_memory); // 768MB

// Scale down resource requests by 2/3
let scale_down = Ratio::new(2, 3);
let reduced_request = scale_down.mul_round(original_request);

Pattern: Rate Limiting and Throttling

// Throttle to 70% of maximum rate
let throttle = Ratio::new(7, 10);
let allowed_requests = throttle.mul_trunc(max_requests_per_second);

// Convert packet rate between different time windows
let per_second_to_per_minute = Ratio::new(60, 1);
let packets_per_minute = per_second_to_per_minute.mul_round(packets_per_second);

Performance Considerations

The Ratio type is optimized for performance-critical code through its mult/(1<<shift) representation.

Performance Benefits Workflow

flowchart TD
A["Traditional: value * num / den"]
B["Expensive division"]
C["Variable performance"]
D["int_ratio: (value * mult) >> shift"]
E["Bit shift operation"]
F["Consistent fast performance"]
G["Construction time"]
H["Pre-compute mult/shift"]
I["Amortized cost over many operations"]

A --> B
B --> C
D --> E
E --> F
G --> H
H --> I

When to Use Each Operation:

Use CaseRecommended MethodRationale
Memory allocationmul_trunc()Never over-allocate
Time calculationsmul_round()Minimize timing errors
Rate limitingmul_trunc()Conservative limiting
Display/UI valuesmul_round()Better visual appearance

Construction Cost vs Runtime Benefit

The Ratio::new() constructor performs optimization work upfront to enable fast runtime operations:

// One-time construction cost
let ratio = Ratio::new(2, 3); // Computes optimal mult/shift values

// Many fast runtime operations  
for value in large_dataset {
    let result = ratio.mul_trunc(value); // Just multiplication + shift
    process(result);
}

Sources: src/lib.rs(L62 - L83) 

Edge Cases and Error Handling

Division by Zero Protection

The constructor panics for invalid ratios but allows the special zero case:

// Panics: invalid ratio
// let invalid = Ratio::new(5, 0); 

// Safe: zero ratio
let zero_num = Ratio::new(0, 5);    // Represents 0/5 = 0
let zero_special = Ratio::zero();   // Special 0/0 case

// Safe inversion of zero ratios
assert_eq!(zero_special.inverse(), Ratio::zero());

Precision Limits

The Ratio type works with u32 inputs and u64 calculation values:

// Maximum values supported
let max_ratio = Ratio::new(u32::MAX, u32::MAX); // Represents 1.0
let tiny_ratio = Ratio::new(1, u32::MAX);       // Very small fraction

// Large value calculations
let large_result = max_ratio.mul_trunc(u64::MAX); // Uses u128 internally

Sources: src/lib.rs(L159 - L185) 

Debug and Inspection

The Debug implementation shows both the original and optimized representations:

let ratio = Ratio::new(625, 1000);
println!("{:?}", ratio); 
// Output: Ratio(625/1000 ~= 5/8)

This format displays:

  • Original numerator/denominator
  • Optimized mult/shift representation as an equivalent fraction

Sources: src/lib.rs(L135 - L146) 

Development Guide

Relevant source files

This document provides comprehensive information for developers contributing to the int_ratio crate. It covers development environment setup, continuous integration pipeline, code quality standards, and testing procedures across multiple target architectures. For information about using the int_ratio API in applications, see Usage Guide. For details about the internal implementation, see Ratio Type Implementation.

Development Environment Setup

The int_ratio crate requires a Rust nightly toolchain with specific components and target support for cross-platform compatibility testing.

Required Toolchain Configuration

The development environment must include:

  • Rust Toolchain: nightly channel
  • Components: rust-src, clippy, rustfmt
  • Target Architectures: x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat
flowchart TD
A["Rust Nightly Toolchain"]
B["rust-src component"]
C["clippy component"]
D["rustfmt component"]
E["Target Architectures"]
F["x86_64-unknown-linux-gnu"]
G["x86_64-unknown-none"]
H["riscv64gc-unknown-none-elf"]
I["aarch64-unknown-none-softfloat"]
J["Linux Testing Platform"]
K["Bare Metal x86_64"]
L["RISC-V Embedded"]
M["ARM64 Embedded"]

A --> B
A --> C
A --> D
E --> F
E --> G
E --> H
E --> I
F --> J
G --> K
H --> L
I --> M

Development Environment Architecture

Sources: .github/workflows/ci.yml(L15 - L19) 

CI Pipeline Architecture

The continuous integration system employs a matrix strategy to ensure cross-platform compatibility and maintain code quality standards.

flowchart TD
subgraph subGraph0["Target Matrix"]
    J["x86_64-unknown-linux-gnu"]
    K["x86_64-unknown-none"]
    L["riscv64gc-unknown-none-elf"]
    M["aarch64-unknown-none-softfloat"]
end
A["Push/PR Event"]
B["CI Job Matrix"]
C["ubuntu-latest Runner"]
D["Rust Toolchain Setup"]
E["rustc Version Check"]
F["Code Format Check"]
G["Clippy Linting"]
H["Build Process"]
I["Unit Testing"]
N["Test Results"]
O["Documentation Job"]
P["cargo doc"]
Q["GitHub Pages Deploy"]

A --> B
B --> C
B --> J
B --> K
B --> L
B --> M
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> N
O --> P
P --> Q

CI Pipeline Flow and Target Matrix

Pipeline Stages

StageCommandPurposeTarget Scope
Format Checkcargo fmt --all -- --checkEnforce code style consistencyAll targets
Lintingcargo clippy --target $TARGET --all-featuresStatic analysis and best practicesPer target
Buildcargo build --target $TARGET --all-featuresCompilation verificationPer target
Unit Testcargo test --target $TARGET -- --nocaptureFunctional testingLinux only

Sources: .github/workflows/ci.yml(L22 - L30) 

Code Quality Standards

The project enforces strict code quality through automated tooling integrated into the CI pipeline.

Formatting Requirements

Code formatting uses rustfmt with default configuration. All contributions must pass the format check defined in .github/workflows/ci.yml(L22 - L23) 

Linting Standards

Clippy linting runs with custom configuration allowing the clippy::new_without_default warning, as specified in .github/workflows/ci.yml(L24 - L25)  This exception acknowledges that the Ratio::new constructor appropriately requires explicit parameters.

Documentation Standards

Documentation builds enforce strict standards through RUSTDOCFLAGS configuration:

  • -D rustdoc::broken_intra_doc_links: Fails on broken internal links
  • -D missing-docs: Requires documentation for all public items

Sources: .github/workflows/ci.yml(L40) 

Testing Strategy

The testing approach balances comprehensive platform coverage with practical constraints of embedded target environments.

flowchart TD
A["Test Execution Strategy"]
B["Full Test Suite"]
C["Build-Only Verification"]
D["x86_64-unknown-linux-gnu"]
E["x86_64-unknown-none"]
F["riscv64gc-unknown-none-elf"]
G["aarch64-unknown-none-softfloat"]
H["Standard Library Available"]
I["Bare Metal Environment"]
J["RISC-V Embedded"]
K["ARM64 Embedded"]
L["Unit Tests Execute"]
M["Compilation Verified"]

A --> B
A --> C
B --> D
C --> E
C --> F
C --> G
D --> H
E --> I
F --> J
G --> K
H --> L
I --> M
J --> M
K --> M

Multi-Architecture Testing Strategy

Platform-Specific Testing

  • Linux Platform (x86_64-unknown-linux-gnu): Full unit test execution with --nocapture flag for detailed output
  • Embedded Targets: Build verification only due to runtime environment constraints

The conditional test execution logic is implemented in .github/workflows/ci.yml(L28 - L30) 

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L28 - L30) 

Documentation Deployment

The project maintains automated documentation deployment to GitHub Pages with the following workflow:

flowchart TD
A["Documentation Job"]
B["Rust Toolchain Setup"]
C["cargo doc --no-deps --all-features"]
D["Index HTML Generation"]
E["Branch Check"]
F["Default Branch?"]
G["Deploy to gh-pages"]
H["Skip Deployment"]
I["GitHub Pages"]

A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
F --> H
G --> I

Documentation Build and Deployment Process

The documentation system automatically generates API documentation and deploys it to GitHub Pages when changes are pushed to the default branch, as configured in .github/workflows/ci.yml(L49 - L55) 

Sources: .github/workflows/ci.yml(L32 - L55) 

Contributing Workflow

Pre-Contribution Checklist

Before submitting contributions, ensure:

  1. Local Testing: Run cargo test on x86_64-unknown-linux-gnu target
  2. Format Compliance: Execute cargo fmt --all -- --check
  3. Lint Compliance: Run cargo clippy --all-features for each target
  4. Build Verification: Confirm cargo build succeeds on all targets

Repository Configuration

The project uses standard Git configuration with build artifacts excluded via .gitignore(L1 - L4) 

Sources: .gitignore(L1 - L4) 

Overview

Relevant source files

Purpose and Scope

This document provides an overview of the flatten_objects crate, a specialized Rust library that implements a container data structure for managing numbered objects with unique ID assignment. The crate is designed specifically for resource-constrained environments such as operating system kernels and embedded systems where the standard library is unavailable (no_std).

This overview covers the fundamental concepts, core data structures, and high-level architecture of the library. For detailed API documentation, see FlattenObjects API Documentation. For implementation specifics and memory management details, see Implementation Details. For practical usage examples and integration patterns, see Usage Guide and Examples.

Sources: Cargo.toml(L1 - L17)  README.md(L1 - L37) 

Core Functionality

The flatten_objects crate provides the FlattenObjects<T, CAP> container, which serves as a fixed-capacity array-like structure that automatically assigns unique integer IDs to stored objects. The container supports efficient ID reuse, allowing previously removed object IDs to be reassigned to new objects, making it ideal for implementing object pools, handle tables, and resource management systems in kernel environments.

The primary operations include:

  • Object Addition: add() for automatic ID assignment, add_at() for specific ID placement
  • Object Removal: remove() to free objects and make their IDs available for reuse
  • Object Access: get() and get_mut() for safe reference retrieval
  • State Inspection: is_assigned(), count(), capacity(), and ids() for container introspection

Sources: README.md(L7 - L11)  README.md(L14 - L36) 

System Architecture

Core Components Diagram

flowchart TD
subgraph subGraph2["External Dependencies"]
    BitmapsCrate["bitmaps crate v3.2"]
    CoreMem["core::mem::MaybeUninit"]
end
subgraph subGraph1["Public API Methods"]
    AddMethods["add() / add_at() / add_or_replace_at()"]
    AccessMethods["get() / get_mut() / is_assigned()"]
    RemoveMethods["remove()"]
    QueryMethods["capacity() / count() / ids()"]
end
subgraph subGraph0["FlattenObjects"]
    ObjectArray["objects: [MaybeUninit; CAP]"]
    IDBitmap["id_bitmap: Bitmap"]
    Count["count: usize"]
end

AccessMethods --> IDBitmap
AccessMethods --> ObjectArray
AddMethods --> Count
AddMethods --> IDBitmap
AddMethods --> ObjectArray
IDBitmap --> BitmapsCrate
ObjectArray --> CoreMem
QueryMethods --> Count
QueryMethods --> IDBitmap
RemoveMethods --> Count
RemoveMethods --> IDBitmap
RemoveMethods --> ObjectArray

Sources: Cargo.toml(L15 - L16)  README.md(L14 - L36) 

Integration Context Diagram

flowchart TD
subgraph subGraph3["Target Environments"]
    NoStdEnvironment["no_std Environment"]
    EmbeddedSystems["Embedded Systems"]
    KernelSpace["Kernel Space"]
end
subgraph subGraph2["flatten_objects Library"]
    FlattenObjectsContainer["FlattenObjects"]
    subgraph subGraph1["Common Use Cases"]
        HandleTables["Handle Tables"]
        ObjectPools["Object Pools"]
        ResourceManagers["Resource Managers"]
    end
end
subgraph subGraph0["ArceOS Ecosystem"]
    ArceOSKernel["ArceOS Kernel"]
    ProcessManagement["Process Management"]
    MemoryManagement["Memory Management"]
    FileSystem["File System"]
end

ArceOSKernel --> FlattenObjectsContainer
FileSystem --> ResourceManagers
FlattenObjectsContainer --> EmbeddedSystems
FlattenObjectsContainer --> KernelSpace
FlattenObjectsContainer --> NoStdEnvironment
HandleTables --> FlattenObjectsContainer
MemoryManagement --> ObjectPools
ObjectPools --> FlattenObjectsContainer
ProcessManagement --> HandleTables
ResourceManagers --> FlattenObjectsContainer

Sources: Cargo.toml(L8 - L12)  Cargo.toml(L15 - L16) 

Key Characteristics

CharacteristicDescription
Fixed CapacityCompile-time constantCAPparameter defines maximum object count
ID Reuse StrategyAutomatically reuses IDs from removed objects via bitmap tracking
Memory EfficiencyUsesMaybeUninitto avoid unnecessary object initialization
no_std CompatibleDesigned for environments without standard library support
Type SafetyMaintains safe abstractions over unsafe memory operations
Zero Runtime AllocationAll memory allocated at compile-time, no heap usage

The library is particularly well-suited for scenarios requiring predictable memory usage and deterministic performance characteristics, such as real-time systems, kernel modules, and embedded applications where dynamic allocation is prohibited or undesirable.

Sources: Cargo.toml(L12)  Cargo.toml(L15 - L16)  README.md(L17 - L36) 

Project Structure and Dependencies

Relevant source files

This document details the crate configuration, external dependencies, and organizational structure of the flatten_objects library. It explains how the project is configured for the ArceOS ecosystem and its role as a no_std compatible container library.

For implementation details of the core data structures, see Internal Data Structures. For build and testing procedures, see Building and Testing.

Crate Configuration and Metadata

The flatten_objects crate is configured as a library package targeting resource-constrained environments. The crate metadata defines its purpose as a numbered object container with unique ID assignment capabilities.

Project Metadata

FieldValuePurpose
nameflatten_objectsCrate identifier for the Rust ecosystem
version0.2.3Current release version following semantic versioning
edition2024Uses Rust 2024 edition features
description"A container that stores numbered objects..."Describes the core functionality
licenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0Triple-licensed for maximum compatibility
homepagehttps://github.com/arceos-org/arceosLinks to the broader ArceOS project
repositoryhttps://github.com/arceos-org/flatten_objectsSource code location
rust-version1.85Minimum supported Rust version

The crate is categorized under no-std and data-structures, indicating its suitability for embedded and kernel development environments where the standard library is unavailable.

Crate Classification and Targets

flowchart TD
subgraph target_environments["Target Environments"]
    edition_2024["Rust Edition 2024"]
    triple_license["Triple License Compatibility"]
    min_rust["Minimum Rust 1.85"]
    no_std["no_std Environment"]
    embedded["Embedded Systems"]
    kernel["Kernel Development"]
    arceos_os["ArceOS Operating System"]
    metadata["Crate Metadata"]
end
subgraph flatten_objects_crate["flatten_objects Crate"]
    arceos_os["ArceOS Operating System"]
    categories["categories: [no-std, data-structures]"]
    keywords["keywords: [arceos, data-structures]"]
    rust_version["rust-version: 1.85"]
    subgraph compatibility_matrix["Compatibility Matrix"]
        edition_2024["Rust Edition 2024"]
        triple_license["Triple License Compatibility"]
        min_rust["Minimum Rust 1.85"]
        no_std["no_std Environment"]
        embedded["Embedded Systems"]
        kernel["Kernel Development"]
        metadata["Crate Metadata"]
    end
end

metadata --> categories
metadata --> keywords
metadata --> rust_version

Sources: Cargo.toml(L1 - L13) 

Dependencies Analysis

The crate maintains a minimal dependency footprint with only one external dependency to ensure compatibility with resource-constrained environments.

External Dependencies

The bitmaps crate version 3.2 serves as the sole external dependency, providing efficient bitmap operations for ID management:

[dependencies]
bitmaps = { version = "3.2", default-features = false }

The default-features = false configuration ensures no_std compatibility by disabling standard library features of the bitmaps crate.

Dependency Relationship Diagram

flowchart TD
subgraph internal_usage["Internal Usage in flatten_objects"]
    id_bitmap["id_bitmap: Bitmap"]
    id_management["ID Management System"]
    allocation_tracking["Allocation Tracking"]
end
subgraph bitmaps_features["bitmaps Crate Features"]
    default_features_disabled["default-features = false"]
    no_std_compat["no_std compatibility"]
    bitmap_ops["Bitmap Operations"]
end
subgraph flatten_objects_dependencies["flatten_objects Dependencies"]
    flatten_objects["flatten_objects"]
    bitmaps_dep["bitmaps v3.2"]
    core_std["core (no_std)"]
end

bitmap_ops --> id_bitmap
bitmaps_dep --> bitmap_ops
bitmaps_dep --> default_features_disabled
default_features_disabled --> no_std_compat
flatten_objects --> bitmaps_dep
flatten_objects --> core_std
id_bitmap --> id_management
id_management --> allocation_tracking

Dependency Rationale

DependencyVersionConfigurationPurpose
bitmaps3.2default-features = falseProvidesBitmapfor efficient ID tracking
Standard LibraryN/AExcluded (no_std)Maintains compatibility with kernel environments
coreImplicitAlways availableProvidesMaybeUninitand essential types

The bitmaps crate integration enables the FlattenObjects container to efficiently track which IDs are allocated using bitwise operations, essential for the ID reuse mechanism.

Sources: Cargo.toml(L15 - L16) 

ArceOS Ecosystem Integration

The flatten_objects crate is designed specifically for integration within the ArceOS operating system ecosystem, serving as a foundational data structure for kernel-level resource management.

Ecosystem Positioning

The crate's homepage points to the broader ArceOS project (https://github.com/arceos-org/arceos), indicating its role as a component within a larger operating system framework.

ArceOS Integration Architecture

flowchart TD
subgraph kernel_integration_points["Kernel Integration Points"]
    process_descriptors["Process Descriptors"]
    file_handles["File Handles"]
    memory_regions["Memory Regions"]
    device_handles["Device Handles"]
end
subgraph flatten_objects_role["flatten_objects Role"]
    flatten_objects_lib["flatten_objects Library"]
    object_pools["Object Pools"]
    handle_tables["Handle Tables"]
    resource_mgmt["Resource Management"]
    id_allocation["ID Allocation"]
end
subgraph arceos_ecosystem["ArceOS Ecosystem"]
    arceos_kernel["ArceOS Kernel"]
    process_mgmt["Process Management"]
    memory_mgmt["Memory Management"]
    file_system["File System"]
    device_drivers["Device Drivers"]
end

arceos_kernel --> flatten_objects_lib
device_drivers --> id_allocation
file_system --> resource_mgmt
flatten_objects_lib --> handle_tables
flatten_objects_lib --> id_allocation
flatten_objects_lib --> object_pools
flatten_objects_lib --> resource_mgmt
handle_tables --> file_handles
id_allocation --> device_handles
memory_mgmt --> handle_tables
object_pools --> process_descriptors
process_mgmt --> object_pools
resource_mgmt --> memory_regions

Target Use Cases

The crate addresses specific requirements in kernel and embedded development:

  • Fixed-capacity containers: Essential for predictable memory usage in kernel environments
  • Efficient ID reuse: Critical for long-running systems that create and destroy many objects
  • No heap allocation: Required for kernel code and embedded systems with limited memory
  • Type-safe object storage: Maintains memory safety while allowing low-level optimizations

Sources: Cargo.toml(L8 - L11) 

Project Structure Overview

Based on the crate configuration and typical Rust library conventions, the project follows a standard structure optimized for library distribution and ArceOS integration.

Repository Structure

Project Organization Diagram

flowchart TD
subgraph repository_root["Repository Root"]
    ci_workflow["CI/CD Pipeline"]
    package_metadata["Package Metadata"]
    public_api["Public API Methods"]
    cargo_toml["Cargo.toml"]
    readme_md["README.md"]
    gitignore[".gitignore"]
    src_dir["src/"]
end
subgraph configuration_files["Configuration Files"]
    flatten_objects_struct["FlattenObjects"]
    subgraph development_infrastructure["Development Infrastructure"]
        ci_workflow["CI/CD Pipeline"]
        documentation["API Documentation"]
        multi_target["Multi-target Support"]
        package_metadata["Package Metadata"]
        dependency_spec["Dependency Specification"]
        build_config["Build Configuration"]
        lib_rs["src/lib.rs"]
    end
end
subgraph source_structure["Source Structure"]
    ci_workflow["CI/CD Pipeline"]
    documentation["API Documentation"]
    package_metadata["Package Metadata"]
    dependency_spec["Dependency Specification"]
    lib_rs["src/lib.rs"]
    flatten_objects_struct["FlattenObjects"]
    public_api["Public API Methods"]
    internal_impl["Internal Implementation"]
    cargo_toml["Cargo.toml"]
    readme_md["README.md"]
end

flatten_objects_struct --> internal_impl
flatten_objects_struct --> public_api
lib_rs --> flatten_objects_struct

Build and Distribution Configuration

The crate is configured for distribution through the standard Rust package ecosystem while maintaining specialized requirements for kernel development:

Configuration AspectSettingImpact
Edition2024Access to latest Rust language features
Categories["no-std", "data-structures"]Discoverable by embedded/kernel developers
Keywords["arceos", "data-structures"]Associated with ArceOS ecosystem
Documentationdocs.rsintegrationAutomatic API documentation generation
Multi-target supportImplicit viano_stdCompatible with diverse hardware platforms

The minimal dependency configuration ensures fast compilation and reduces the attack surface for security-critical kernel components.

Sources: Cargo.toml(L1 - L17) 

Key Concepts and Terminology

Relevant source files

This document explains the fundamental concepts and terminology used throughout the flatten_objects crate. It covers the core principles of object containers, unique ID assignment, ID reuse strategies, and memory management in no_std environments. For implementation details of the data structures, see Internal Data Structures. For specific API usage patterns, see Basic Operations.

Object Container Fundamentals

The flatten_objects crate provides a specialized container called FlattenObjects<T, CAP> that stores objects of type T with a fixed capacity of CAP items. Unlike standard collections, this container assigns each stored object a unique numerical identifier (ID) that can be used for subsequent access.

flowchart TD
subgraph Operations["Key Operations"]
    IDAssignment["ID Assignment"]
    ObjectRetrieval["Object Retrieval"]
    IDRecycling["ID Recycling"]
end
subgraph Container["FlattenObjects<T, CAP>"]
    Bitmap["id_bitmap: Bitmap<CAP>"]
    Count["count: usize"]
    Slot0["objects[0]: MaybeUninit<T>"]
    Slot1["objects[1]: MaybeUninit<T>"]
    subgraph Interface["Public Interface"]
        AddMethods["add(), add_at(), add_or_replace_at()"]
        AccessMethods["get(), get_mut(), is_assigned()"]
        RemoveMethods["remove()"]
        QueryMethods["capacity(), count(), ids()"]
    end
    subgraph Storage["Object Storage"]
        SlotN["objects[N]: MaybeUninit<T>"]
        subgraph Tracking["ID Tracking"]
            Bitmap["id_bitmap: Bitmap<CAP>"]
            Count["count: usize"]
            Slot0["objects[0]: MaybeUninit<T>"]
            Slot1["objects[1]: MaybeUninit<T>"]
        end
    end
end

AccessMethods --> ObjectRetrieval
AddMethods --> IDAssignment
IDAssignment --> ObjectRetrieval
ObjectRetrieval --> IDRecycling
RemoveMethods --> IDRecycling

Container Characteristics

  • Fixed capacity: The container can hold at most CAP objects, where CAP is a compile-time constant
  • Numbered storage: Each object is stored at a specific index corresponding to its ID
  • Sparse allocation: Not all slots need to be filled; IDs can have gaps
  • Type safety: All objects in the container must be of the same type T

Sources: src/lib.rs(L44 - L51)  src/lib.rs(L1 - L4) 

Unique ID Assignment System

The container assigns unique IDs to objects using a bitmap-based allocation strategy. IDs are non-negative integers ranging from 0 to CAP-1.

flowchart TD
subgraph AllocationStrategy["Allocation Strategy"]
    FirstFit["first_false_index()"]
    ExplicitID["add_at(id, value)"]
    SmallestAvailable["add(value) → smallest ID"]
end
subgraph BitmapState["id_bitmap State"]
    Bit0["bit[0]: false (available)"]
    Bit1["bit[1]: true (assigned)"]
    Bit2["bit[2]: false (available)"]
    BitN["bit[N]: true (assigned)"]
end
subgraph IDSpace["ID Space (0 to CAP-1)"]
    ID0["ID 0"]
    ID1["ID 1"]
    ID2["ID 2"]
    IDN["ID N"]
end

FirstFit --> SmallestAvailable
ID0 --> Bit0
ID1 --> Bit1
ID2 --> Bit2
IDN --> BitN

Assignment Methods

  • Automatic assignment: add(value) finds the smallest available ID using id_bitmap.first_false_index()
  • Explicit assignment: add_at(id, value) assigns a specific ID if available
  • Replacement assignment: add_or_replace_at(id, value) overwrites existing objects

ID Constraints

  • IDs must be in range <FileRef file-url="https://github.com/arceos-org/flatten_objects/blob/ac0a74b9/0, CAP)\n- Each ID can be assigned to at most one object at a time\n- The maximum capacity CAP is limited to 1024 due to bitmap implementation constraints\n\nSources#LNaN-LNaN" NaN file-path="0, CAP)\n- Each ID can be assigned to at most one object at a time\n- The maximum capacityCAP` is limited to 1024 due to bitmap implementation constraints\n\nSources">Hii src/lib.rs(L249 - L257)  src/lib.rs(L42 - L43) 

ID Reuse and Lifecycle Management

When objects are removed from the container, their IDs become available for reuse. This recycling mechanism ensures efficient utilization of the limited ID space.

stateDiagram-v2
[*] --> Unassigned
Unassigned --> Reserved : "add() or add_at()"
note left of Reserved : ['"id_bitmap.set(id, true)<br>count += 1"']
[*] --> Occupied
Occupied --> Accessed : "get() or get_mut()"
Accessed --> Occupied : "Reference dropped"
note left of Occupied : ['"Object stored at objects[id]<br>MaybeUninit contains valid T"']
[*] --> root_start : "Container initialized"
[*] --> Assigned_start : "Object added"
[*] --> Available_start : "remove(id)"
note left of Available----note : []
note left of Occupied : ['"Bitmap bit = true<br>Slot contains valid object"']

Lifecycle States

  • Available: ID slot is free and can be assigned (id_bitmap.get(id) == false)
  • Assigned: ID slot contains a valid object (id_bitmap.get(id) == true)
  • Recycled: Previously assigned ID becomes available again after remove()

Reuse Strategy

  • IDs are reused in ascending order when add() is called
  • The first_false_index() method finds the lowest available ID
  • No guarantees about ID ordering when mixing add() and add_at() calls

Sources: src/lib.rs(L315 - L326)  src/lib.rs(L223 - L224) 

Memory Safety inno_stdEnvironments

The crate operates in no_std environments, which imposes specific constraints on memory management and available standard library features.

no_std Implications

  • No heap allocation: All storage uses stack-allocated arrays
  • No std::collections: Custom container implementation required
  • Limited error handling: No std::error trait or panic handling
  • Core library only: Restricted to core::mem and similar basic utilities

Memory Safety Mechanisms

  • MaybeUninit<T> wrapper: Safely handles uninitialized memory slots
  • Bitmap synchronization: Ensures id_bitmap state matches object initialization
  • Unsafe operations: Contained within safe public interfaces with documented invariants
flowchart TD
subgraph SafetyInvariants["Safety Invariants"]
    Invariant1["If id_bitmap.get(i) == truethen objects[i] contains valid T"]
    Invariant2["If id_bitmap.get(i) == falsethen objects[i] is uninitialized"]
    Invariant3["count equals number of true bitsin id_bitmap"]
end
subgraph MemoryLayout["Memory Layout"]
    subgraph BitmapArray["id_bitmap: Bitmap<CAP>"]
        FalseBits["false bits (unassigned)"]
        TrueBits["true bits (assigned)"]
    end
    subgraph ObjectArray["objects: [MaybeUninit<T>; CAP]"]
        Uninit["MaybeUninit::uninit()"]
        Valid["MaybeUninit with valid T"]
    end
end

FalseBits --> Uninit
TrueBits --> Valid
Uninit --> Invariant2
Valid --> Invariant1

Safety Guarantees

  • Objects are only accessible when their corresponding bitmap bit is set
  • assume_init_ref() and assume_init_mut() are only called on initialized slots
  • Memory is properly cleaned up when objects are removed via assume_init_read()

Sources: src/lib.rs(L32)  src/lib.rs(L48)  src/lib.rs(L79 - L83)  src/lib.rs(L165 - L172) 

Capacity Constraints and Generic Parameters

The container uses compile-time generic parameters to ensure type safety and memory efficiency.

Generic Parameters

  • T: The type of objects stored in the container
  • CAP: Compile-time constant defining maximum capacity

Type Constraints

  • BitsImpl<{ CAP }>: Bits: Ensures the bitmap implementation supports the specified capacity
  • CAP must not exceed 1024 due to bitmap library limitations

Capacity Characteristics

  • Fixed size: Memory allocation is determined at compile time
  • Zero-cost abstraction: No runtime overhead for capacity checks
  • Stack allocated: Entire container lives on the stack (no heap allocation)

Sources: src/lib.rs(L44 - L46)  src/lib.rs(L61)  src/lib.rs(L86 - L101) 

Dependency Integration

The crate integrates with the bitmaps crate for efficient ID tracking and management.

External Dependencies

  • bitmaps crate (v3.2): Provides Bitmap<CAP> implementation
  • core::mem: Provides MaybeUninit for safe uninitialized memory handling

Bitmap Integration

  • Bitmap<CAP> tracks which IDs are assigned
  • first_false_index() method enables efficient ID allocation
  • Iter<CAP> provides iteration over assigned IDs via ids() method

Sources: src/lib.rs(L34)  src/lib.rs(L344 - L346) 

FlattenObjects API Documentation

Relevant source files

This document provides comprehensive API documentation for the FlattenObjects<T, CAP> struct, which serves as the core container type in the flatten_objects crate. The documentation covers all public methods, their signatures, behavior, error conditions, and usage patterns.

For conceptual information about the crate's purpose and design principles, see Overview. For implementation details and internal architecture, see Implementation Details. For practical usage examples and patterns, see Usage Guide and Examples.

API Structure Overview

The FlattenObjects API is organized around three primary operation categories: container management, object lifecycle operations, and query/inspection methods.

flowchart TD
subgraph Return_Types["Return_Types"]
    Result_usize_T["Result"]
    Result_usize_Option_T["Result>"]
    Option_ref_T["Option<&T>"]
    Option_mut_T["Option<&mut T>"]
    Option_T["Option"]
    Iter_CAP["Iter"]
end
subgraph subGraph6["FlattenObjects"]
    subgraph Internal_State["Internal_State"]
        objects_field["objects: [MaybeUninit; CAP]"]
        id_bitmap["id_bitmap: Bitmap"]
        count_field["count: usize"]
    end
    subgraph Iteration["Iteration"]
        ids["ids()"]
    end
    subgraph Access_Operations["Access_Operations"]
        get["get(id: usize)"]
        get_mut["get_mut(id: usize)"]
        is_assigned["is_assigned(id: usize)"]
    end
    subgraph Object_Lifecycle["Object_Lifecycle"]
        capacity["capacity()"]
        new["new()"]
        add["add(value: T)"]
        add_at["add_at(id: usize, value: T)"]
        add_or_replace["add_or_replace_at(id: usize, value: T)"]
        remove["remove(id: usize)"]
    end
    subgraph Configuration["Configuration"]
        count["count()"]
        subgraph Creation["Creation"]
            capacity["capacity()"]
            new["new()"]
        end
    end
end
bool["bool"]

add --> Result_usize_T
add --> count_field
add --> id_bitmap
add --> objects_field
add_at --> Result_usize_T
add_at --> count_field
add_at --> id_bitmap
add_at --> objects_field
add_or_replace --> Result_usize_Option_T
get --> Option_ref_T
get --> id_bitmap
get --> objects_field
get_mut --> Option_mut_T
get_mut --> id_bitmap
get_mut --> objects_field
ids --> Iter_CAP
ids --> id_bitmap
is_assigned --> bool
is_assigned --> id_bitmap
remove --> Option_T
remove --> count_field
remove --> id_bitmap
remove --> objects_field

Sources: src/lib.rs(L37 - L347) 

Method Classification and Signatures

The API methods can be classified by their primary function and the type of operations they perform on the container state.

CategoryMethodSignatureState Changes
Creationnewconst fn new() -> SelfInitializes empty container
Inspectioncapacityconst fn capacity(&self) -> usizeNone
Inspectioncountconst fn count(&self) -> usizeNone
Queryis_assignedfn is_assigned(&self, id: usize) -> boolNone
Accessgetfn get(&self, id: usize) -> Option<&T>None
Accessget_mutfn get_mut(&mut self, id: usize) -> Option<&mut T>None
Mutationaddfn add(&mut self, value: T) -> Result<usize, T>Modifies objects, id_bitmap, count
Mutationadd_atfn add_at(&mut self, id: usize, value: T) -> Result<usize, T>Modifies objects, id_bitmap, count
Mutationadd_or_replace_atfn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option>Modifies objects, id_bitmap, potentially count
Mutationremovefn remove(&mut self, id: usize) -> OptionModifies objects, id_bitmap, count
Iterationidsfn ids(&self) -> IterNone

Sources: src/lib.rs(L77 - L84)  src/lib.rs(L98 - L101)  src/lib.rs(L121 - L124)  src/lib.rs(L143 - L146)  src/lib.rs(L164 - L173)  src/lib.rs(L193 - L202)  src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297)  src/lib.rs(L315 - L326)  src/lib.rs(L343 - L346) 

Object Lifecycle State Machine

The following diagram shows how objects transition through different states within the FlattenObjects container, mapped to specific API operations.

stateDiagram-v2
[*] --> Unallocated : "new()"
[*] --> Available
Available --> Available
Available --> Available : "Failed add/add_at calls"
Available --> Available
[*] --> Assigned
Assigned --> Modified : "get_mut(id)"
Modified --> Assigned : "Access complete"
Assigned --> Modified
Modified --> Modified : "get(id), is_assigned(id)"
Modified --> Assigned
note left of Available----note : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"']
note left of Available : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"']
note left of Assigned : ['"id_bitmap.get(id) == true<br>objects[id] contains valid T"']
note left of Modified : ['"Mutable reference active<br>Temporary state during get_mut"']
[*] --> alloc_start : "add(), add_at(), add_or_replace_at()"
[*] --> unalloc_start : "remove(id)"
note left of alloc_start : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"']
note left of Available----note : ['"id_bitmap.get(id) == false<br>objects[id] uninitialized"']

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297)  src/lib.rs(L315 - L326)  src/lib.rs(L164 - L173)  src/lib.rs(L193 - L202)  src/lib.rs(L143 - L146) 

Container Creation and Configuration

Constructor Method

The new() method creates an empty FlattenObjects container with all slots unallocated.

// From src/lib.rs:77-84
pub const fn new() -> Self

Constraints:

  • CAP must not exceed 1024 (enforced at compile time)
  • Function is const, enabling compile-time initialization
  • Zero-initializes the id_bitmap safely using MaybeUninit::zeroed()

Example behavior:

let objects = FlattenObjects::<u32, 20>::new();
// All IDs 0..19 are available
// count() returns 0
// No memory is initialized for objects

Sources: src/lib.rs(L77 - L84) 

Capacity and Size Methods

The container provides compile-time and runtime information about its configuration and current state.

MethodReturn TypeDescription
capacity()usizeMaximum objects that can be stored (CAP)
count()usizeCurrent number of assigned objects

The relationship between these values follows the invariant: 0 <= count() <= capacity().

Sources: src/lib.rs(L98 - L101)  src/lib.rs(L121 - L124) 

Object Management Operations

Addition Methods

The container provides three distinct methods for adding objects, each with different ID assignment strategies:

flowchart TD
subgraph Return_Values["Return_Values"]
    Ok_id["Ok(id)"]
    Err_value["Err(value)"]
    Err_Some_old["Err(Some(old_value))"]
    Err_None["Err(None)"]
end
subgraph Success_Conditions["Success_Conditions"]
    available_slot["Available slot exists"]
    id_not_assigned["ID not assigned && id < CAP"]
    id_in_range["id < CAP"]
end
subgraph ID_Assignment["ID_Assignment"]
    auto_id["Automatic ID(first_false_index)"]
    explicit_id["Explicit ID(user-specified)"]
    replace_id["Explicit ID(with replacement)"]
end
subgraph Addition_Methods["Addition_Methods"]
    add_method["add(value: T)"]
    add_at_method["add_at(id: usize, value: T)"]
    add_or_replace_method["add_or_replace_at(id: usize, value: T)"]
end

add_at_method --> explicit_id
add_method --> auto_id
add_or_replace_method --> replace_id
auto_id --> available_slot
available_slot --> Err_value
available_slot --> Ok_id
explicit_id --> id_not_assigned
id_in_range --> Err_None
id_in_range --> Err_Some_old
id_in_range --> Ok_id
id_not_assigned --> Err_value
id_not_assigned --> Ok_id
replace_id --> id_in_range

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297) 

Automatic ID Assignment:add()

#![allow(unused)]
fn main() {
// From src/lib.rs:222-232
pub fn add(&mut self, value: T) -> Result<usize, T>
}

Behavior:

  1. Finds the first available ID using id_bitmap.first_false_index()
  2. Assigns the object to that ID if available
  3. Updates count, id_bitmap, and initializes objects[id]

Error conditions:

  • Returns Err(value) if no available slots exist
  • Returns Err(value) if first_false_index() returns ID >= CAP

Explicit ID Assignment:add_at()

#![allow(unused)]
fn main() {
// From src/lib.rs:249-257
pub fn add_at(&mut self, id: usize, value: T) -> Result<usize, T>
}

Behavior:

  1. Validates id < CAP and !is_assigned(id)
  2. Assigns object to specified ID if validation passes
  3. Updates count, id_bitmap, and initializes objects[id]

Error conditions:

  • Returns Err(value) if id >= CAP
  • Returns Err(value) if is_assigned(id) returns true

Replacement Assignment:add_or_replace_at()

#![allow(unused)]
fn main() {
// From src/lib.rs:277-297
pub fn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option<T>>
}

Behavior:

  1. Validates id < CAP
  2. If ID assigned: replaces object and returns Err(Some(old_value))
  3. If ID unassigned: assigns object and returns Ok(id)

Error conditions:

  • Returns Err(None) if id >= CAP
  • Returns Err(Some(old)) if replacing existing object

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297) 

Removal Method

#![allow(unused)]
fn main() {
// From src/lib.rs:315-326
pub fn remove(&mut self, id: usize) -> Option<T>
}

State changes:

  1. Sets id_bitmap.set(id, false) to mark ID as available
  2. Decrements count
  3. Reads and returns object using assume_init_read()

Safety considerations:

  • Object memory is not overwritten, only marked as uninitialized
  • ID becomes available for reuse immediately
  • Calling remove() on unassigned ID returns None without side effects

Sources: src/lib.rs(L315 - L326) 

Query and Inspection Methods

Access Methods

The container provides immutable and mutable access to stored objects through bounds-checked operations.

flowchart TD
subgraph Return_Types["Return_Types"]
    Option_ref["Option<&T>"]
    Option_mut["Option<&mut T>"]
    bool_result["bool"]
end
subgraph Memory_Operations["Memory_Operations"]
    assume_init_ref["assume_init_ref()"]
    assume_init_mut["assume_init_mut()"]
    no_memory_access["Return boolean only"]
end
subgraph Validation_Steps["Validation_Steps"]
    bounds_check["id < CAP"]
    bitmap_check["id_bitmap.get(id)"]
    combined_check["is_assigned(id)"]
end
subgraph Access_Methods["Access_Methods"]
    get_method["get(id: usize)"]
    get_mut_method["get_mut(id: usize)"]
    is_assigned_method["is_assigned(id: usize)"]
end

assume_init_mut --> Option_mut
assume_init_ref --> Option_ref
bitmap_check --> no_memory_access
combined_check --> assume_init_mut
combined_check --> assume_init_ref
get_method --> combined_check
get_mut_method --> combined_check
is_assigned_method --> bitmap_check
is_assigned_method --> bounds_check
no_memory_access --> bool_result

Sources: src/lib.rs(L164 - L173)  src/lib.rs(L193 - L202)  src/lib.rs(L143 - L146) 

Immutable Access:get()

#![allow(unused)]
fn main() {
// From src/lib.rs:164-173
pub fn get(&self, id: usize) -> Option<&T>
}

Implementation details:

  • Calls is_assigned(id) for validation
  • Uses assume_init_ref() to create reference from MaybeUninit<T>
  • Returns None for unassigned IDs or out-of-bounds access

Mutable Access:get_mut()

#![allow(unused)]
fn main() {
// From src/lib.rs:193-202
pub fn get_mut(&mut self, id: usize) -> Option<&mut T>
}

Implementation details:

  • Identical validation logic to get()
  • Uses assume_init_mut() to create mutable reference
  • Requires exclusive container access (&mut self)

Assignment Check:is_assigned()

#![allow(unused)]
fn main() {
// From src/lib.rs:143-146
pub fn is_assigned(&self, id: usize) -> bool
}

Implementation details:

  • Returns false for id >= CAP (bounds check)
  • Returns id_bitmap.get(id) for valid IDs
  • Does not access object memory, only bitmap state

Sources: src/lib.rs(L164 - L173)  src/lib.rs(L193 - L202)  src/lib.rs(L143 - L146) 

Iteration Support

#![allow(unused)]
fn main() {
// From src/lib.rs:343-346
pub fn ids(&self) -> Iter<CAP>
}

The ids() method provides an iterator over assigned object IDs by delegating to the bitmap's iterator implementation.

Characteristics:

  • Returns Iter<CAP> from the bitmaps crate
  • Iterates only over assigned IDs (where bitmap bit is true)
  • Order follows the bitmap's internal iteration order (typically ascending)
  • Iterator is independent of object values, only reads bitmap state

Sources: src/lib.rs(L343 - L346) 

Container Creation and Configuration

Relevant source files

This document covers the creation and configuration of FlattenObjects containers, including struct definition, generic parameters, capacity constraints, and initialization methods. For information about object management operations like adding and removing objects, see Object Management Operations. For internal implementation details, see Internal Data Structures.

FlattenObjects Struct Definition

The FlattenObjects<T, CAP> struct serves as the core container for managing numbered objects with unique IDs. The structure is designed for no_std environments and provides compile-time capacity configuration.

flowchart TD
subgraph subGraph3["FlattenObjects Structure"]
    FlattenObjects["FlattenObjects<T, CAP>"]
    subgraph subGraph2["Trait Bounds"]
        bits_impl["BitsImpl<{CAP}>: Bits"]
    end
    subgraph subGraph1["Generic Parameters"]
        T_param["T: Object Type"]
        CAP_param["CAP: Capacity (const usize)"]
    end
    subgraph Fields["Fields"]
        objects_field["objects: [MaybeUninit<T>; CAP]"]
        id_bitmap_field["id_bitmap: Bitmap<CAP>"]
        count_field["count: usize"]
    end
end

CAP_param --> id_bitmap_field
CAP_param --> objects_field
FlattenObjects --> CAP_param
FlattenObjects --> T_param
FlattenObjects --> bits_impl
FlattenObjects --> count_field
FlattenObjects --> id_bitmap_field
FlattenObjects --> objects_field
T_param --> objects_field
bits_impl --> id_bitmap_field

The struct contains three essential fields that work together to provide efficient object storage and ID management:

FieldTypePurpose
objects[MaybeUninit; CAP]Fixed-size array storing the actual objects
id_bitmapBitmapTracks which IDs are currently assigned
countusizeMaintains the current number of stored objects

Sources: src/lib.rs(L44 - L51) 

Generic Parameters and Constraints

The FlattenObjects struct uses two generic parameters that determine its behavior and capabilities:

flowchart TD
subgraph subGraph3["Generic Parameter System"]
    subgraph subGraph2["Trait Bound"]
        BitsImpl["BitsImpl<{CAP}>: Bits"]
        bitmap_dep["Required bybitmaps crate"]
    end
    subgraph subGraph1["Const Parameter CAP"]
        CAP["const CAP: usize"]
        CAP_constraints["Constraints:• Must be ≤ 1024• Compile-time constant• Determines max objects"]
    end
    subgraph subGraph0["Type Parameter T"]
        T["T"]
        T_examples["Examples:• u32• String• ProcessDescriptor• FileHandle"]
    end
end

BitsImpl --> bitmap_dep
CAP --> BitsImpl
CAP --> CAP_constraints
T --> T_examples

Type ParameterT

The T parameter represents the type of objects stored in the container. It can be any type that implements the required traits for the operations you intend to perform. Common examples include primitive types, structs representing system resources, or handles.

Capacity ParameterCAP

The CAP parameter is a compile-time constant that defines:

  • Maximum number of objects the container can hold
  • Maximum ID value (CAP - 1)
  • Size of the internal bitmap and object array

The capacity is subject to a hard limit of 1024 objects, enforced by the bitmaps crate dependency.

Trait Bounds

The constraint BitsImpl<{ CAP }>: Bits ensures that the bitmaps crate can handle the specified capacity. This trait bound is automatically satisfied for capacities up to 1024.

Sources: src/lib.rs(L44 - L46)  src/lib.rs(L54 - L55) 

Container Initialization

Container creation is handled through the new() constructor method, which provides compile-time initialization with proper memory layout.

flowchart TD
subgraph subGraph3["Container Initialization Process"]
    new_call["FlattenObjects::<T, CAP>::new()"]
    subgraph Result["Result"]
        empty_container["Empty FlattenObjects instance"]
    end
    subgraph Validation["Validation"]
        cap_check["CAP ≤ 1024 check"]
        compile_panic["Compile-time panic if CAP > 1024"]
    end
    subgraph subGraph0["Field Initialization"]
        init_objects["objects: [MaybeUninit::uninit(); CAP]"]
        init_bitmap["id_bitmap: Bitmap zeroed"]
        init_count["count: 0"]
    end
end

cap_check --> compile_panic
cap_check --> init_bitmap
cap_check --> init_count
cap_check --> init_objects
init_bitmap --> empty_container
init_count --> empty_container
init_objects --> empty_container
new_call --> cap_check

Constructor Method

The new() method is declared as const fn, enabling compile-time evaluation and static initialization. Key initialization steps include:

  1. Object Array: Initialized with MaybeUninit::uninit() to avoid unnecessary initialization overhead
  2. ID Bitmap: Zero-initialized using unsafe operations, which is valid for the Bitmap type
  3. Count Field: Set to 0 to indicate an empty container

Capacity Validation

The constructor enforces the 1024 capacity limit at compile time. Attempts to create containers with CAP > 1024 will result in compilation failure.

// Valid - compiles successfully
let objects = FlattenObjects::<u32, 20>::new();

// Invalid - compilation error
let objects = FlattenObjects::<u32, 1025>::new();

Memory Safety Considerations

The initialization process carefully handles memory safety:

  • Uses MaybeUninit<T> to avoid constructing T instances prematurely
  • Employs safe zero-initialization for the bitmap
  • Maintains proper alignment and memory layout

Sources: src/lib.rs(L77 - L84)  src/lib.rs(L59 - L61) 

Capacity Configuration and Limits

The capacity system provides both flexibility and safety through compile-time configuration with runtime access methods.

Capacity Access

The capacity() method provides read-only access to the container's maximum capacity:

MethodReturn TypeDescription
capacity()usizeReturns theCAPgeneric parameter value
count()usizeReturns current number of stored objects

Capacity Relationship with ID Range

The capacity directly determines the valid ID range for objects:

  • Valid IDs: 0 to CAP - 1 (inclusive)
  • Total possible IDs: CAP
  • Maximum objects: CAP

Implementation Constraints

The 1024 limit stems from the underlying bitmaps crate implementation, which uses fixed-size bitmap representations optimized for performance in constrained environments.

Sources: src/lib.rs(L98 - L101)  src/lib.rs(L41 - L43) 

Object Management Operations

Relevant source files

This document covers the core object management operations provided by the FlattenObjects<T, CAP> container. These operations enable adding, removing, and accessing objects within the fixed-capacity container while maintaining unique ID assignments and memory safety.

The operations documented here handle the fundamental lifecycle of objects within the container. For information about container creation and configuration, see Container Creation and Configuration. For query and inspection methods that don't modify objects, see Query and Inspection Methods.

Operation Categories Overview

The FlattenObjects container provides three primary categories of object management operations:

CategoryMethodsPurpose
Adding Objectsadd(),add_at(),add_or_replace_at()Insert objects with automatic or manual ID assignment
Removing Objectsremove()Remove objects and free their IDs for reuse
Accessing Objectsget(),get_mut()Retrieve immutable or mutable references to stored objects

Each operation maintains the invariant relationship between the id_bitmap and objects array, ensuring that only initialized memory locations are accessible and that ID assignments remain consistent.

Object Management Operation Flow

flowchart TD
subgraph subGraph3["Internal State Updates"]
    BitmapSet["id_bitmap.set(id, true)"]
    BitmapClear["id_bitmap.set(id, false)"]
    CountIncr["count += 1"]
    CountDecr["count -= 1"]
    WriteObj["objects[id].write(value)"]
    ReadObj["objects[id].assume_init_ref()"]
    TakeObj["objects[id].assume_init_read()"]
end
subgraph subGraph2["Remove Operations"]
    Remove["remove(id)"]
end
subgraph subGraph1["Access Operations"]
    Get["get(id)"]
    GetMut["get_mut(id)"]
end
subgraph subGraph0["Add Operations"]
    Add["add(value)"]
    AddAt["add_at(id, value)"]
    AddReplace["add_or_replace_at(id, value)"]
end
Start["Object Management Request"]

Add --> BitmapSet
AddAt --> BitmapSet
AddReplace --> BitmapSet
BitmapClear --> CountDecr
BitmapSet --> CountIncr
CountDecr --> TakeObj
CountIncr --> WriteObj
Get --> ReadObj
GetMut --> ReadObj
Remove --> BitmapClear
Start --> Add
Start --> AddAt
Start --> AddReplace
Start --> Get
Start --> GetMut
Start --> Remove

Sources: src/lib.rs(L222 - L347) 

Adding Objects

The container provides three methods for adding objects, each with different ID assignment strategies and error handling behaviors.

Automatic ID Assignment:add()

The add() method assigns the smallest available ID to a new object using the bitmap's first_false_index() function.

flowchart TD
AddCall["add(value: T)"]
FindID["id_bitmap.first_false_index()"]
CheckCap["id < CAP?"]
UpdateBitmap["id_bitmap.set(id, true)"]
IncrCount["count += 1"]
WriteValue["objects[id].write(value)"]
ReturnOk["Ok(id)"]
ReturnErr["Err(value)"]

AddCall --> FindID
CheckCap --> ReturnErr
CheckCap --> UpdateBitmap
FindID --> CheckCap
IncrCount --> WriteValue
UpdateBitmap --> IncrCount
WriteValue --> ReturnOk

Method Signature: pub fn add(&mut self, value: T) -> Result<usize, T>

Behavior:

  • Searches for the first unassigned ID using first_false_index()
  • Validates the ID is within capacity bounds (id < CAP)
  • Updates id_bitmap, increments count, and writes the object
  • Returns the assigned ID on success, or the original value on failure

Error Conditions:

  • Container is at full capacity
  • No available IDs found

Sources: src/lib.rs(L222 - L232) 

Manual ID Assignment:add_at()

The add_at() method places an object at a specific ID if that ID is available.

Method Signature: pub fn add_at(&mut self, id: usize, value: T) -> Result<usize, T>

Behavior:

  • Validates id < CAP and !is_assigned(id)
  • If valid, performs the same state updates as add()
  • Returns the specified ID on success

Error Conditions:

  • ID is out of range (id >= CAP)
  • ID is already assigned (is_assigned(id) returns true)

Sources: src/lib.rs(L249 - L257) 

Replace-or-Add:add_or_replace_at()

The add_or_replace_at() method either adds a new object or replaces an existing one at the specified ID.

Method Signature: pub fn add_or_replace_at(&mut self, id: usize, value: T) -> Result<usize, Option<T>>

Behavior:

  • If ID is unassigned: performs standard add operation
  • If ID is assigned: replaces existing object without changing count
  • Uses assume_init_read() to safely extract the old value

Return Values:

  • Ok(id): Object added successfully at the specified ID
  • Err(Some(old_value)): ID was assigned, old object returned
  • Err(None): ID is out of range

Sources: src/lib.rs(L277 - L297) 

Removing Objects

The remove() method extracts an object from the container and frees its ID for reuse.

Object Removal State Transition

stateDiagram-v2
state Assigned ID {
    [*] --> BitmapTrue : "id_bitmap.get(id) == true"
    [*] --> ObjectInit : "objects[id] contains valid T"
    [*] --> CountIncluded : "included in count"
}
state Remove Operation {
    [*] --> CheckAssigned : "remove(id) called"
    CheckAssigned --> ClearBitmap : "is_assigned(id) == true"
    ClearBitmap --> DecrCount : "id_bitmap.set(id, false)"
    DecrCount --> ReadObject : "count -= 1"
    ReadObject --> ReturnObject : "assume_init_read()"
    CheckAssigned --> ReturnNone : "is_assigned(id) == false"
}
state Available ID {
    [*] --> BitmapFalse : "id_bitmap.get(id) == false"
    [*] --> ObjectUninit : "objects[id] uninitialized"
    [*] --> CountExcluded : "not included in count"
}
[*] --> Assigned ID : "object added"
Assigned ID --> Remove Operation : "remove(id)"
Remove Operation --> Available ID : "success"
Remove Operation --> Assigned ID : "failure (return None)"

Method Signature: pub fn remove(&mut self, id: usize) -> Option<T>

Behavior:

  1. Checks is_assigned(id) to validate the operation
  2. Clears the bitmap bit: id_bitmap.set(id, false)
  3. Decrements the object count: count -= 1
  4. Safely reads the object value using assume_init_read()
  5. Returns the extracted object

Memory Safety: The method uses assume_init_read() which is safe because the bitmap ensures only initialized memory locations are accessed.

Sources: src/lib.rs(L315 - L326) 

Accessing Objects

The container provides both immutable and mutable access to stored objects through bounds-checked operations.

Immutable Access:get()

Method Signature: pub fn get(&self, id: usize) -> Option<&T>

Behavior:

  • Validates ID assignment using is_assigned(id)
  • Returns immutable reference via assume_init_ref()
  • Returns None for unassigned or out-of-range IDs

Mutable Access:get_mut()

Method Signature: pub fn get_mut(&mut self, id: usize) -> Option<&mut T>

Behavior:

  • Identical validation to get()
  • Returns mutable reference via assume_init_mut()
  • Enables in-place modification of stored objects

Safety Invariant: Both methods rely on the bitmap to ensure that only initialized MaybeUninit<T> slots are accessed, maintaining memory safety without runtime initialization checks.

Sources: src/lib.rs(L165 - L202) 

Error Handling Patterns

The object management operations use consistent error handling patterns that preserve the original values when operations fail:

OperationSuccess TypeError TypeError Preservation
add()Ok(usize)Err(T)Returns original value
add_at()Ok(usize)Err(T)Returns original value
add_or_replace_at()Ok(usize)Err(Option)Returns old value orNone
remove()Some(T)NoneN/A
get()Some(&T)NoneN/A
get_mut()Some(&mut T)NoneN/A

This design ensures that failed operations don't consume the input values, allowing callers to retry or handle errors appropriately.

ID Assignment and Reuse Logic

The container implements an efficient ID reuse strategy through the bitmap-based allocation system:

  1. Initial Assignment: add() uses first_false_index() to find the lowest available ID
  2. ID Liberation: remove() immediately marks IDs as available by clearing bitmap bits
  3. Reuse Priority: Newly available IDs become candidates for the next add() operation
  4. Capacity Bounds: All operations respect the compile-time CAP limit

This approach ensures optimal ID utilization while maintaining O(1) allocation performance for most operations.

Sources: src/lib.rs(L223 - L231)  src/lib.rs(L315 - L325) 

Query and Inspection Methods

Relevant source files

This document covers the read-only methods provided by FlattenObjects for inspecting container state and querying information about stored objects. These methods allow users to check object assignment status, retrieve container metadata, and iterate over assigned IDs without modifying the container's contents.

For methods that modify container contents (adding, removing, and accessing objects), see Object Management Operations. For details about the underlying data structures, see Internal Data Structures.

Core State Inspection Methods

The FlattenObjects container provides several fundamental methods for querying its current state. These methods form the foundation for understanding what objects are stored and how the container's capacity is being utilized.

flowchart TD
subgraph subGraph2["Information Provided"]
    assignmentStatus["ID Assignment Status"]
    containerCapacity["Maximum Capacity"]
    currentUsage["Current Object Count"]
    assignedIds["List of Active IDs"]
end
subgraph subGraph1["Internal Data Structures"]
    idBitmap["id_bitmap: Bitmap"]
    objectsArray["objects: [MaybeUninit; CAP]"]
    countField["count: usize"]
    capConstant["CAP: const usize"]
end
subgraph subGraph0["Query Methods API"]
    isAssigned["is_assigned(id: usize) -> bool"]
    capacity["capacity() -> usize"]
    count["count() -> usize"]
    ids["ids() -> Iter"]
end

capacity --> capConstant
capacity --> containerCapacity
count --> countField
count --> currentUsage
ids --> assignedIds
ids --> idBitmap
isAssigned --> assignmentStatus
isAssigned --> idBitmap

Title: Query Methods and Data Structure Relationships

Sources: src/lib.rs(L44 - L51)  src/lib.rs(L86 - L146)  src/lib.rs(L328 - L346) 

Assignment Status Checking

The is_assigned method determines whether a specific ID currently has an object assigned to it. This method provides bounds checking and bitmap consultation in a single operation.

MethodParametersReturn TypePurpose
is_assignedid: usizeboolCheck if ID is assigned and valid

The method returns false for IDs that are out of range (≥ CAP) or unassigned. This dual behavior eliminates the need for separate bounds checking in user code.

Sources: src/lib.rs(L126 - L146) 

Container Capacity Information

The capacity method returns the compile-time constant CAP, representing the maximum number of objects the container can hold. This value also represents the highest valid ID plus one.

MethodParametersReturn TypePurpose
capacityNoneusizeGet maximum container capacity

The capacity is immutable and determined at compile time through the generic parameter CAP.

Sources: src/lib.rs(L86 - L101) 

Current Usage Tracking

The count method provides the current number of assigned objects in the container. This count is maintained automatically as objects are added and removed.

MethodParametersReturn TypePurpose
countNoneusizeGet current number of stored objects

The count reflects only assigned objects and decreases when objects are removed, allowing IDs to be reused.

Sources: src/lib.rs(L103 - L124) 

Container Iteration

The ids method provides an iterator over all currently assigned IDs, enabling enumeration of active objects without accessing their values.

sequenceDiagram
    participant UserCode as "User Code"
    participant FlattenObjects as "FlattenObjects"
    participant id_bitmapBitmapCAP as "id_bitmap: Bitmap<CAP>"
    participant IterCAP as "Iter<CAP>"

    UserCode ->> FlattenObjects: ids()
    FlattenObjects ->> id_bitmapBitmapCAP: into_iter()
    id_bitmapBitmapCAP ->> IterCAP: create iterator
    IterCAP -->> FlattenObjects: Iter<CAP>
    FlattenObjects -->> UserCode: Iter<CAP>
    loop "For each assigned ID"
        UserCode ->> IterCAP: next()
        IterCAP ->> id_bitmapBitmapCAP: find next set bit
        id_bitmapBitmapCAP -->> IterCAP: Option<usize>
        IterCAP -->> UserCode: Option<usize>
    end

Title: ID Iterator Workflow

The iterator is provided by the bitmaps crate and efficiently traverses only the set bits in the bitmap, skipping unassigned IDs.

MethodParametersReturn TypePurpose
idsNoneIterIterator over assigned IDs

Sources: src/lib.rs(L328 - L346)  src/lib.rs(L34) 

State Inspection Patterns

These query methods are commonly used together to implement various inspection patterns for understanding container state.

flowchart TD
subgraph subGraph2["Use Cases"]
    resourceManagement["Resource Management"]
    debugging["Debugging/Monitoring"]
    algorithms["Algorithm Logic"]
    safety["Safety Checks"]
end
subgraph subGraph1["Method Combinations"]
    countVsCapacity["count() vs capacity()"]
    isAssignedCheck["is_assigned(id)"]
    idsIteration["ids() iteration"]
    combined["Multiple methods"]
end
subgraph subGraph0["Common Inspection Patterns"]
    fullnessCheck["Fullness Check"]
    availabilityCheck["ID Availability Check"]
    usageAnalysis["Usage Analysis"]
    validation["State Validation"]
end

availabilityCheck --> algorithms
availabilityCheck --> isAssignedCheck
fullnessCheck --> countVsCapacity
fullnessCheck --> resourceManagement
usageAnalysis --> debugging
usageAnalysis --> idsIteration
validation --> combined
validation --> safety

Title: Query Method Usage Patterns

Capacity and Usage Analysis

Comparing count() and capacity() provides insight into container utilization:

  • Full container: count() == capacity()
  • Empty container: count() == 0
  • Utilization ratio: count() as f32 / capacity() as f32

ID Validation Workflows

The is_assigned method serves multiple validation purposes:

  • Pre-access validation before calling get or get_mut
  • Availability checking before add_at operations
  • State consistency verification in algorithms

Comprehensive State Enumeration

The ids() iterator enables complete state inspection:

  • Enumerating all active objects without accessing values
  • Building secondary data structures based on assigned IDs
  • Implementing custom iteration patterns over active objects

Sources: src/lib.rs(L8 - L30)  src/lib.rs(L332 - L342) 

Implementation Details

The query methods leverage the internal bitmap and counter to provide efficient, constant-time operations for most queries.

flowchart TD
subgraph subGraph2["Safety Guarantees"]
    boundsChecking["Automatic Bounds Checking"]
    noUnsafeCode["No Unsafe Operations"]
    stateConsistency["State Consistency"]
end
subgraph subGraph1["Data Dependencies"]
    bitmapOps["Bitmap Operations"]
    constantAccess["Constant Access"]
    iteratorLogic["Iterator Logic"]
end
subgraph subGraph0["Performance Characteristics"]
    isAssignedTime["is_assigned: O(1)"]
    capacityTime["capacity: O(1)"]
    countTime["count: O(1)"]
    idsTime["ids: O(n) iteration"]
end

bitmapOps --> boundsChecking
capacityTime --> constantAccess
constantAccess --> noUnsafeCode
countTime --> constantAccess
idsTime --> iteratorLogic
isAssignedTime --> bitmapOps
iteratorLogic --> stateConsistency

Title: Query Method Implementation Characteristics

Bounds Checking Integration

The is_assigned method combines bounds checking with bitmap lookup: id < CAP && self.id_bitmap.get(id). This ensures that out-of-range IDs are handled safely without requiring separate validation.

Bitmap Integration

All query operations rely on the bitmaps crate for efficient bit manipulation. The bitmap provides O(1) bit access and optimized iteration over set bits.

Memory Safety

Unlike the object management methods, query methods require no unsafe operations. They only access the bitmap and counter fields, which are always in a valid state.

Sources: src/lib.rs(L144 - L146)  src/lib.rs(L98 - L100)  src/lib.rs(L121 - L123)  src/lib.rs(L343 - L345) 

Implementation Details

Relevant source files

This document provides a comprehensive analysis of the internal architecture, data structures, and implementation strategies used in the flatten_objects crate. It covers the low-level details of how the FlattenObjects container manages memory, maintains safety invariants, and implements efficient ID allocation.

For high-level API usage patterns, see Usage Guide and Examples. For specific API method documentation, see FlattenObjects API Documentation.

Core Architecture Overview

The FlattenObjects<T, CAP> struct implements a fixed-capacity object container through three interconnected components that work together to provide safe, efficient object storage with unique ID assignment.

Internal Data Structure Layout

flowchart TD
subgraph subGraph3["External Dependencies"]
    BitsImpl["BitsImpl<CAP>: Bits"]
    MaybeUninit_core["core::mem::MaybeUninit"]
    bitmaps_crate["bitmaps::Bitmap"]
end
subgraph subGraph2["Bitmap Representation"]
    bit0["bit 0: assigned/free"]
    bit1["bit 1: assigned/free"]
    bitN["bit CAP-1: assigned/free"]
end
subgraph subGraph1["Memory Organization"]
    slot0["objects[0]: MaybeUninit<T>"]
    slot1["objects[1]: MaybeUninit<T>"]
    slotN["objects[CAP-1]: MaybeUninit<T>"]
end
subgraph FlattenObjects["FlattenObjects<T, CAP>"]
    objects["objects: [MaybeUninit<T>; CAP]"]
    id_bitmap["id_bitmap: Bitmap<CAP>"]
    count["count: usize"]
end

bit0 --> slot0
bit1 --> slot1
bitN --> slotN
id_bitmap --> bit0
id_bitmap --> bit1
id_bitmap --> bitN
id_bitmap --> bitmaps_crate
objects --> MaybeUninit_core
objects --> slot0
objects --> slot1
objects --> slotN

The structure maintains three critical invariants:

  • id_bitmap.get(i) == true if and only if objects[i] contains an initialized value
  • count equals the number of set bits in id_bitmap
  • All operations preserve these relationships atomically

Sources: src/lib.rs(L44 - L51) 

Generic Parameter Constraints

The FlattenObjects implementation relies on compile-time constraints that ensure type safety and capacity limits:

ParameterConstraintPurpose
TAny typeThe stored object type
CAPconst usizeMaximum container capacity
BitsImpl<{CAP}>BitsEnables bitmap operations for the given capacity

The capacity constraint CAP <= 1024 is enforced through the bitmaps crate's type system and validated at construction time in new().

Sources: src/lib.rs(L44 - L46)  src/lib.rs(L59 - L61) 

Memory Management Strategy

The implementation uses MaybeUninit<T> to avoid unnecessary initialization overhead and enable precise control over object lifecycle:

Initialization States and Transitions


The bitmap serves as the authoritative source of truth for memory initialization state. All access to objects[i] is guarded by checking id_bitmap.get(i) first.

Sources: src/lib.rs(L48 - L49)  src/lib.rs(L77 - L84) 

Unsafe Operation Boundaries

The implementation carefully encapsulates unsafe operations within safe method boundaries:

Unsafe OperationLocationSafety Guarantee
assume_init_ref()get()methodProtected byis_assigned()check
assume_init_mut()get_mut()methodProtected byis_assigned()check
assume_init_read()remove()andadd_or_replace_at()Protected byis_assigned()check
MaybeUninit::zeroed().assume_init()new()methodValid forBitmaptype

Sources: src/lib.rs(L169)  src/lib.rs(L198)  src/lib.rs(L286)  src/lib.rs(L322)  src/lib.rs(L81) 

ID Allocation and Management

The ID management system uses the bitmaps crate to efficiently track available slots and implement ID reuse:

ID Allocation Flow

flowchart TD
add_request["add(value)"]
find_id["id_bitmap.first_false_index()"]
add_at_request["add_at(id, value)"]
check_bounds["id < CAP"]
add_or_replace_request["add_or_replace_at(id, value)"]
id_available["id.is_some() && id < CAP"]
check_assigned["is_assigned(id)"]
allocate_id["Allocate ID"]
return_error["Err(value)"]
replace_object["Replace existing"]
update_bitmap["id_bitmap.set(id, true)"]
write_value["objects[id].write(value)"]
increment_count["count += 1"]
return_success["Ok(id)"]
end_flow["End"]

add_at_request --> check_bounds
add_or_replace_request --> check_bounds
add_request --> find_id
allocate_id --> update_bitmap
check_assigned --> allocate_id
check_assigned --> replace_object
check_assigned --> return_error
check_bounds --> check_assigned
find_id --> id_available
id_available --> allocate_id
id_available --> return_error
increment_count --> write_value
replace_object --> write_value
return_error --> end_flow
return_success --> end_flow
update_bitmap --> increment_count
write_value --> return_success

The first_false_index() method from the bitmaps crate provides O(1) amortized ID allocation by efficiently finding the first unset bit.

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297) 

Critical Safety Invariants

The implementation maintains several critical invariants that ensure memory safety:

  1. Bitmap-Memory Synchronization: id_bitmap.get(i) == true if and only if objects[i] is initialized
  2. Count Consistency: count equals id_bitmap.into_iter().count()
  3. Bounds Safety: All array access is bounds-checked against CAP
  4. Initialization Ordering: id_bitmap.set(id, true) occurs before objects[id].write(value)
  5. Deinitialization Ordering: id_bitmap.set(id, false) occurs after objects[id].assume_init_read()

These invariants are maintained through careful ordering in methods like add(), remove(), and add_or_replace_at().

Sources: src/lib.rs(L225 - L228)  src/lib.rs(L253 - L256)  src/lib.rs(L317 - L322) 

Capacity Constraints and Compilation

The CAP parameter is constrained by the bitmaps crate's Bits trait implementation, which currently supports capacities up to 1024. This constraint is enforced at compile time through the trait bound BitsImpl<{CAP}>: Bits.

The new() method includes a runtime panic for capacities exceeding 1024, providing clear error messaging during development while maintaining const fn compatibility.

Sources: src/lib.rs(L46)  src/lib.rs(L59 - L61)  src/lib.rs(L77) 

Internal Data Structures

Relevant source files

This document details the internal data structures that comprise the FlattenObjects container, explaining how the three core fields work together to provide efficient object storage with bitmap-based ID management. This covers the memory layout, field purposes, and structural relationships that enable the container's functionality.

For information about the public API methods that operate on these structures, see Object Management Operations. For details about memory safety and MaybeUninit usage, see Memory Management and Safety.

Core Data Structure Overview

The FlattenObjects<T, CAP> struct contains exactly three fields that work in coordination to manage object storage and ID assignment. Each field serves a distinct purpose in maintaining the container's invariants.

flowchart TD
subgraph subGraph2["External Dependencies"]
    MaybeUninit["core::mem::MaybeUninit"]
    Bitmap["bitmaps::Bitmap"]
end
subgraph Purpose["Purpose"]
    storage["Object Storage Array"]
    tracking["ID Assignment Tracking"]
    metadata["Count Metadata"]
end
subgraph subGraph0["FlattenObjects"]
    objects["objects: [MaybeUninit<T>; CAP]"]
    id_bitmap["id_bitmap: Bitmap<CAP>"]
    count["count: usize"]
end

count --> metadata
id_bitmap --> Bitmap
id_bitmap --> tracking
objects --> MaybeUninit
objects --> storage

Core Fields Summary

FieldTypePurposeSize
objects[MaybeUninit; CAP]Stores the actual objectsCAP * size_of::()bytes
id_bitmapBitmapTracks which IDs are assignedImplementation-dependent
countusizeNumber of currently assigned objects8 bytes (on 64-bit)

Sources: src/lib.rs(L44 - L51) 

Object Storage Array

The objects field is a fixed-size array that provides the actual storage space for objects. Each array slot corresponds directly to a potential ID, creating a one-to-one mapping between array indices and object IDs.

MaybeUninit Wrapper

The array uses MaybeUninit<T> rather than T directly, which allows the container to:

  • Avoid default initialization of unused slots
  • Safely represent uninitialized memory states
  • Enable manual control over object lifecycle
flowchart TD
subgraph subGraph2["ID Mapping"]
    id0["ID 0"]
    id1["ID 1"]
    id2["ID 2"]
    idN["ID CAP-1"]
end
subgraph subGraph1["Possible States"]
    uninit["Uninitialized(ID not assigned)"]
    init["Initialized(ID assigned)"]
end
subgraph subGraph0["objects Array Layout"]
    slot0["objects[0]MaybeUninit<T>"]
    slot1["objects[1]MaybeUninit<T>"]
    slot2["objects[2]MaybeUninit<T>"]
    dotdot["..."]
    slotN["objects[CAP-1]MaybeUninit<T>"]
end

slot0 --> id0
slot0 --> init
slot1 --> id1
slot1 --> uninit
slot2 --> id2
slot2 --> init
slotN --> idN
slotN --> uninit

Array Operations

The container performs these operations on the objects array:

  • Write: self.objects[id].write(value) - Initialize a slot with a new object
  • Read: self.objects[id].assume_init_read() - Move an object out of a slot
  • Borrow: self.objects[id].assume_init_ref() - Get an immutable reference
  • Borrow Mut: self.objects[id].assume_init_mut() - Get a mutable reference

Sources: src/lib.rs(L48)  src/lib.rs(L227)  src/lib.rs(L255)  src/lib.rs(L287)  src/lib.rs(L322) 

ID Bitmap Management

The id_bitmap field uses the external bitmaps crate to efficiently track which IDs are currently assigned. Each bit position corresponds to an array index in the objects field.

Bitmap State Representation

Bit ValueID StateObjects Array State
false(0)ID availableMaybeUninituninitialized
true(1)ID assignedMaybeUninitcontains validT

Key Bitmap Operations

The container uses these bitmap operations:

  • Assignment Check: self.id_bitmap.get(id) - Test if an ID is assigned
  • Mark Assigned: self.id_bitmap.set(id, true) - Mark an ID as assigned
  • Mark Available: self.id_bitmap.set(id, false) - Free an ID for reuse
  • Find Available: self.id_bitmap.first_false_index() - Locate next available ID
  • Iterate Assigned: self.id_bitmap.into_iter() - Enumerate all assigned IDs
flowchart TD
subgraph subGraph1["Corresponding Actions"]
    add_op["add() operation"]
    assign_op["Assignment complete"]
    access_op["get()/get_mut() access"]
    remove_op["remove() operation"]
    ids_op["ids() iterator"]
end
subgraph subGraph0["Bitmap Operations Flow"]
    find_available["first_false_index()"]
    set_true["set(id, true)"]
    get_check["get(id)"]
    set_false["set(id, false)"]
    iterate["into_iter()"]
end

access_op --> get_check
add_op --> set_true
find_available --> add_op
ids_op --> iterate
remove_op --> set_false
set_true --> assign_op

Sources: src/lib.rs(L49)  src/lib.rs(L145)  src/lib.rs(L223)  src/lib.rs(L226)  src/lib.rs(L317)  src/lib.rs(L344 - L346) 

Count Field

The count field maintains an accurate count of currently assigned objects, eliminating the need to iterate through the bitmap for this common query.

Count Maintenance

The count field is updated during these operations:

OperationCount ChangeCondition
add()+1When ID assignment succeeds
add_at()+1When ID is available
add_or_replace_at()+1When ID was not previously assigned
remove()-1When ID was assigned

Invariant Relationship

The container maintains this invariant at all times:

count == number of true bits in id_bitmap == number of initialized slots in objects

This invariant ensures that count() method calls execute in constant time rather than requiring bitmap iteration.

Sources: src/lib.rs(L50)  src/lib.rs(L122 - L124)  src/lib.rs(L225)  src/lib.rs(L253)  src/lib.rs(L291)  src/lib.rs(L318) 

Data Structure Coordination

The three fields work together to maintain consistency through coordinated updates. Every operation that modifies the container state updates multiple fields atomically.

sequenceDiagram
    participant APIMethod as "API Method"
    participant id_bitmap as "id_bitmap"
    participant objectsarray as "objects array"
    participant countfield as "count field"

    Note over APIMethod: add(value) operation
    APIMethod ->> id_bitmap: first_false_index()
    id_bitmap -->> APIMethod: Some(id)
    APIMethod ->> countfield: count += 1
    APIMethod ->> id_bitmap: set(id, true)
    APIMethod ->> objectsarray: objects[id].write(value)
    Note over APIMethod: remove(id) operation
    APIMethod ->> id_bitmap: get(id)
    id_bitmap -->> APIMethod: true
    APIMethod ->> id_bitmap: set(id, false)
    APIMethod ->> countfield: count -= 1
    APIMethod ->> objectsarray: objects[id].assume_init_read()

Synchronization Requirements

Since all operations are performed through &mut self, the Rust borrow checker ensures that no concurrent modifications can occur. This eliminates the need for internal synchronization primitives while maintaining data structure integrity.

Memory Layout Considerations

The struct fields are laid out in declaration order, with potential padding for alignment:

  1. objects: [MaybeUninit<T>; CAP] - Largest field, aligned to T's alignment
  2. id_bitmap: Bitmap<CAP> - Size depends on CAP and bitmap implementation
  3. count: usize - 8 bytes on 64-bit platforms

The total memory footprint is approximately CAP * size_of::<T>() + bitmap_size + 8 bytes, making it suitable for resource-constrained environments when CAP is chosen appropriately.

Sources: src/lib.rs(L44 - L51)  src/lib.rs(L222 - L232)  src/lib.rs(L315 - L326) 

Memory Management and Safety

Relevant source files

This document details the memory management strategies and safety mechanisms employed by the FlattenObjects container. It covers the use of uninitialized memory, unsafe operations, and the critical safety invariants that ensure memory safety despite storing objects in a fixed-capacity array.

For information about the ID management system that works alongside these memory safety mechanisms, see ID Management System. For details about the public API that maintains these safety guarantees, see Object Management Operations.

Memory Layout and Data Structures

The FlattenObjects container uses a carefully designed memory layout that balances performance with safety in no_std environments.

Core Memory Components

flowchart TD
subgraph subGraph2["Safety Invariants"]
    invariant1["If bitmap[i] == true, then objects[i] is initialized"]
    invariant2["If bitmap[i] == false, then objects[i] is uninitialized"]
    invariant3["count == number of true bits in bitmap"]
end
subgraph subGraph1["Memory States"]
    uninit["MaybeUninit::uninit()"]
    init["MaybeUninit with valid T"]
    bitmap_false["Bitmap bit = false"]
    bitmap_true["Bitmap bit = true"]
end
subgraph subGraph0["FlattenObjects"]
    objects["objects: [MaybeUninit; CAP]"]
    id_bitmap["id_bitmap: Bitmap"]
    count["count: usize"]
end

bitmap_false --> invariant2
bitmap_true --> invariant1
count --> invariant3
id_bitmap --> bitmap_false
id_bitmap --> bitmap_true
objects --> init
objects --> uninit

Sources: src/lib.rs(L44 - L51) 

The container maintains three core fields that work together to provide safe object storage:

FieldTypePurpose
objects[MaybeUninit; CAP]Stores potentially uninitialized objects
id_bitmapBitmapTracks which array slots contain valid objects
countusizeNumber of currently stored objects

MaybeUninit Usage Pattern

The container uses MaybeUninit<T> to defer object initialization until actually needed, avoiding the overhead of default initialization for the entire array.


Sources: src/lib.rs(L79)  src/lib.rs(L227)  src/lib.rs(L255)  src/lib.rs(L286 - L287)  src/lib.rs(L322) 

Safety Mechanisms and Unsafe Operations

The FlattenObjects implementation contains several unsafe operations that are carefully controlled by safety invariants.

Unsafe Operation Sites

flowchart TD
subgraph subGraph0["Constructor Safety"]
    remove_fn["remove()"]
    assume_init_read["assume_init_read()"]
    replace_fn["add_or_replace_at()"]
    get_fn["get()"]
    assume_init_ref["assume_init_ref()"]
    get_mut_fn["get_mut()"]
    assume_init_mut["assume_init_mut()"]
    new_fn["new()"]
    bitmap_init["MaybeUninit::zeroed().assume_init()"]
    array_init["[MaybeUninit::uninit(); CAP]"]
    subgraph subGraph1["Access Operations"]
        subgraph subGraph2["Removal Operations"]
            remove_fn["remove()"]
            assume_init_read["assume_init_read()"]
            replace_fn["add_or_replace_at()"]
            get_fn["get()"]
            assume_init_ref["assume_init_ref()"]
            get_mut_fn["get_mut()"]
            assume_init_mut["assume_init_mut()"]
            new_fn["new()"]
            bitmap_init["MaybeUninit::zeroed().assume_init()"]
        end
    end
end

get_fn --> assume_init_ref
get_mut_fn --> assume_init_mut
new_fn --> array_init
new_fn --> bitmap_init
remove_fn --> assume_init_read
replace_fn --> assume_init_read

Sources: src/lib.rs(L77 - L84)  src/lib.rs(L165 - L173)  src/lib.rs(L194 - L202)  src/lib.rs(L315 - L326)  src/lib.rs(L277 - L297) 

Critical Safety Contracts

Each unsafe operation in the codebase follows specific safety contracts:

OperationLocationSafety Contract
MaybeUninit::zeroed().assume_init()src/lib.rs81Safe for bitmap (array of integers)
assume_init_ref()src/lib.rs169Called only whenis_assigned(id)returns true
assume_init_mut()src/lib.rs198Called only whenis_assigned(id)returns true
assume_init_read()src/lib.rs286src/lib.rs322Called only whenis_assigned(id)returns true

Safety Invariant Enforcement

The container maintains critical safety invariants through careful coordination between the bitmap and object array states.

Invariant Maintenance Operations

sequenceDiagram
    participant Client as Client
    participant FlattenObjects as FlattenObjects
    participant Bitmap as Bitmap
    participant ObjectArray as ObjectArray

    Note over FlattenObjects: Add Operation
    Client ->> FlattenObjects: add(value)
    FlattenObjects ->> Bitmap: first_false_index()
    Bitmap -->> FlattenObjects: Some(id)
    FlattenObjects ->> FlattenObjects: count += 1
    FlattenObjects ->> Bitmap: set(id, true)
    FlattenObjects ->> ObjectArray: objects[id].write(value)
    FlattenObjects -->> Client: Ok(id)
    Note over FlattenObjects: Access Operation
    Client ->> FlattenObjects: get(id)
    FlattenObjects ->> Bitmap: get(id)
    Bitmap -->> FlattenObjects: true
    FlattenObjects ->> ObjectArray: assume_init_ref()
    ObjectArray -->> FlattenObjects: &T
    FlattenObjects -->> Client: Some(&T)
    Note over FlattenObjects: Remove Operation
    Client ->> FlattenObjects: remove(id)
    FlattenObjects ->> Bitmap: get(id)
    Bitmap -->> FlattenObjects: true
    FlattenObjects ->> Bitmap: set(id, false)
    FlattenObjects ->> FlattenObjects: count -= 1
    FlattenObjects ->> ObjectArray: assume_init_read()
    ObjectArray -->> FlattenObjects: T
    FlattenObjects -->> Client: Some(T)

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L165 - L173)  src/lib.rs(L315 - L326) 

Bitmap Synchronization Strategy

The id_bitmap field serves as the authoritative source of truth for object initialization state. All access to MaybeUninit storage goes through bitmap checks:

flowchart TD
subgraph subGraph1["Safe Failure Pattern"]
    check_false["is_assigned(id) == false"]
    none_return["Return None"]
end
subgraph subGraph0["Safe Access Pattern"]
    check["is_assigned(id)"]
    bitmap_get["id_bitmap.get(id)"]
    range_check["id < CAP"]
    unsafe_access["assume_init_*()"]
    safe_return["Return Some(T)"]
end

bitmap_get --> unsafe_access
check --> bitmap_get
check --> check_false
check --> range_check
check_false --> none_return
range_check --> unsafe_access
unsafe_access --> safe_return

Sources: src/lib.rs(L144 - L146)  src/lib.rs(L166)  src/lib.rs(L195) 

Memory Efficiency Considerations

The design prioritizes memory efficiency through several strategies suitable for resource-constrained environments.

Zero-Initialization Avoidance

flowchart TD
subgraph Benefits["Benefits"]
    no_default["No Default trait requirement"]
    fast_creation["O(1) container creation"]
    mem_efficient["Memory efficient for sparse usage"]
end
subgraph subGraph1["FlattenObjects Approach"]
    lazy_init["Objects initialized only when added"]
    compact_storage["Minimal stack footprint"]
    subgraph subGraph0["Traditional Array[T; CAP]"]
        trad_init["Default::default() called CAP times"]
        trad_memory["Full T initialization overhead"]
        trad_stack["Large stack usage"]
        maybe_init["MaybeUninit::uninit() - zero cost"]
    end
end

compact_storage --> mem_efficient
lazy_init --> fast_creation
maybe_init --> no_default

Sources: src/lib.rs(L79)  src/lib.rs(L227) 

Const Construction Support

The container supports const construction for compile-time initialization in embedded contexts:

flowchart TD
subgraph subGraph1["Runtime Benefits"]
    no_runtime_init["No runtime initialization cost"]
    static_storage["Can be stored in static memory"]
    embedded_friendly["Suitable for embedded systems"]
end
subgraph subGraph0["Const Construction"]
    const_new["const fn new()"]
    const_array["const MaybeUninit array"]
    const_bitmap["zeroed bitmap"]
    const_count["count = 0"]
end

const_array --> static_storage
const_bitmap --> embedded_friendly
const_new --> no_runtime_init

Sources: src/lib.rs(L77 - L84) 

The memory management strategy ensures that FlattenObjects maintains safety guarantees while providing efficient object storage suitable for kernel-level and embedded programming contexts where traditional heap allocation is not available.

ID Management System

Relevant source files

This document covers the bitmap-based ID allocation and management system used by FlattenObjects to assign unique identifiers to stored objects. The system provides efficient ID allocation, reuse, and tracking using a bitmap data structure from the external bitmaps crate.

For information about the overall container architecture, see Internal Data Structures. For details about memory safety and object lifecycle, see Memory Management and Safety.

Core ID Management Architecture

The ID management system centers around a bitmap that tracks which IDs are currently assigned. Each bit position corresponds to a potential object ID, with true indicating an assigned ID and false indicating an available ID.

flowchart TD
subgraph subGraph2["ID Operations"]
    add_op["add()"]
    add_at_op["add_at(id, value)"]
    remove_op["remove(id)"]
    is_assigned_op["is_assigned(id)"]
end
subgraph subGraph1["bitmaps Crate"]
    Bitmap["Bitmap"]
    BitsImpl["BitsImpl: Bits"]
    first_false_index["first_false_index()"]
    get_method["get(id: usize)"]
    set_method["set(id: usize, value: bool)"]
    into_iter["into_iter() -> Iter"]
end
subgraph subGraph0["FlattenObjects"]
    id_bitmap["id_bitmap: Bitmap"]
    objects["objects: [MaybeUninit; CAP]"]
    count["count: usize"]
end

Bitmap --> BitsImpl
add_at_op --> get_method
add_op --> first_false_index
first_false_index --> objects
id_bitmap --> Bitmap
is_assigned_op --> get_method
objects --> count
remove_op --> set_method
set_method --> count
set_method --> objects

Bitmap-based ID Management

Sources: src/lib.rs(L34)  src/lib.rs(L49)  Cargo.toml(L16) 

ID Allocation Algorithm

The system uses a "first-fit" allocation strategy where new objects receive the smallest available ID. This is implemented through the first_false_index() method from the bitmaps crate.

flowchart TD
add_call["add(value: T)"]
find_id["id_bitmap.first_false_index()"]
check_cap["id < CAP?"]
check_available["ID available?"]
assign_id["id_bitmap.set(id, true)"]
write_object["objects[id].write(value)"]
increment_count["count += 1"]
return_ok["return Ok(id)"]
return_err["return Err(value)"]

add_call --> find_id
assign_id --> write_object
check_available --> assign_id
check_available --> return_err
check_cap --> check_available
check_cap --> return_err
find_id --> check_cap
increment_count --> return_ok
write_object --> increment_count

ID Allocation Flow

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L223)  src/lib.rs(L226) 

ID State Transitions

Each ID in the system can be in one of two states, tracked by the corresponding bitmap bit. The state transitions occur during object lifecycle operations.

OperationBitmap Bit ChangeCount ChangeObject Array Effect
add()false→true+1MaybeUninit::uninit()→MaybeUninit::new(value)
add_at(id)false→true+1MaybeUninit::uninit()→MaybeUninit::new(value)
remove(id)true→false-1MaybeUninit::new(value)→MaybeUninit::uninit()
add_or_replace_at(id)unchanged0or+1Replaces existing value

ID State Transition Table

Sources: src/lib.rs(L226)  src/lib.rs(L254)  src/lib.rs(L317)  src/lib.rs(L292) 

Bitmap Integration Details

The system integrates with the bitmaps crate v3.2, which provides efficient bitmap operations with compile-time size verification through the Bits trait constraint.

flowchart TD
subgraph subGraph2["FlattenObjects Methods"]
    CAP["const CAP: usize"]
    BitsImpl_CAP["BitsImpl<{CAP}>"]
    Bits_trait["Bits trait"]
    add_method["add()"]
    is_assigned_method["is_assigned()"]
    remove_method["remove()"]
    ids_method["ids()"]
end
subgraph subGraph1["Bitmap Operations"]
    first_false["first_false_index() -> Option"]
    get_bit["get(index: usize) -> bool"]
    set_bit["set(index: usize, value: bool)"]
    iter_bits["into_iter() -> Iter"]
end
subgraph subGraph0["Type Constraints"]
    CAP["const CAP: usize"]
    BitsImpl_CAP["BitsImpl<{CAP}>"]
    Bits_trait["Bits trait"]
    add_method["add()"]
    is_assigned_method["is_assigned()"]
    remove_method["remove()"]
end

BitsImpl_CAP --> Bits_trait
CAP --> BitsImpl_CAP
add_method --> first_false
ids_method --> iter_bits
is_assigned_method --> get_bit
remove_method --> set_bit

Bitmap Integration Architecture

Sources: src/lib.rs(L46)  src/lib.rs(L34)  src/lib.rs(L144 - L146)  src/lib.rs(L344 - L346) 

ID Reuse Strategy

The system implements immediate ID reuse - when an object is removed, its ID becomes immediately available for the next allocation. This is achieved by setting the corresponding bitmap bit to false.

sequenceDiagram
    participant Client as Client
    participant FlattenObjects as FlattenObjects
    participant id_bitmap as id_bitmap
    participant objects_array as objects_array

    Client ->> FlattenObjects: add(value1)
    FlattenObjects ->> id_bitmap: first_false_index()
    id_bitmap -->> FlattenObjects: Some(0)
    FlattenObjects ->> id_bitmap: set(0, true)
    FlattenObjects ->> objects_array: objects[0].write(value1)
    FlattenObjects -->> Client: Ok(0)
    Client ->> FlattenObjects: add(value2)
    FlattenObjects ->> id_bitmap: first_false_index()
    id_bitmap -->> FlattenObjects: Some(1)
    FlattenObjects ->> id_bitmap: set(1, true)
    FlattenObjects ->> objects_array: objects[1].write(value2)
    FlattenObjects -->> Client: Ok(1)
    Client ->> FlattenObjects: remove(0)
    FlattenObjects ->> id_bitmap: set(0, false)
    FlattenObjects ->> objects_array: objects[0].assume_init_read()
    FlattenObjects -->> Client: Some(value1)
    Client ->> FlattenObjects: add(value3)
    FlattenObjects ->> id_bitmap: first_false_index()
    id_bitmap -->> FlattenObjects: Some(0)
    Note over FlattenObjects: ID 0 is reused
    FlattenObjects ->> id_bitmap: set(0, true)
    FlattenObjects ->> objects_array: objects[0].write(value3)
    FlattenObjects -->> Client: Ok(0)

ID Reuse Sequence

Sources: src/lib.rs(L315 - L326)  src/lib.rs(L317)  src/lib.rs(L322) 

Capacity Constraints and Limitations

The ID management system has several built-in constraints that ensure safe operation within the bounds of the bitmaps crate implementation.

Maximum Capacity Constraint

The system enforces a maximum capacity of 1024 objects, which is a limitation of the underlying bitmaps crate implementation.

flowchart TD
CAP_check["CAP <= 1024?"]
compile_success["Compilation succeeds"]
compile_fail["Compilation fails"]
BitsImpl_constraint["BitsImpl<{CAP}>: Bits"]

BitsImpl_constraint --> compile_success
CAP_check --> BitsImpl_constraint
CAP_check --> compile_fail

Capacity Constraint Validation

Sources: src/lib.rs(L42 - L43)  src/lib.rs(L61)  src/lib.rs(L75 - L76) 

ID Range Validation

All ID operations include bounds checking to ensure IDs remain within the valid range <FileRef file-url="https://github.com/arceos-org/flatten_objects/blob/ac0a74b9/0, CAP).\n\n| Method | ID Validation | Behavior on Invalid ID |\n|--------|---------------|----------------------|\n| is_assigned(id) | id < CAP | Returns false |\n| add_at(id, value) | id >= CAP | Returns Err(value) |\n| add_or_replace_at(id, value) | id >= CAP | Returns Err(None) |\n| get(id) / get_mut(id) | Via is_assigned(id) | Returns None |\n\nID Validation Table\n\nSources#LNaN-LNaN" NaN file-path="0, CAP).\n\n| Method | ID Validation | Behavior on Invalid ID |\n|--------|---------------|----------------------|\n|is_assigned(id)|id < CAP| Returnsfalse|\n|add_at(id, value)|id >= CAP| ReturnsErr(value)|\n|add_or_replace_at(id, value)|id >= CAP| ReturnsErr(None)|\n|get(id)/get_mut(id)| Viais_assigned(id)| ReturnsNone` |\n\nID Validation Table\n\nSources">Hii src/lib.rs(L250)  src/lib.rs(L278) 

Bitmap Initialization and Safety

The bitmap is initialized with zero values, ensuring all IDs start as available. This initialization uses unsafe code but is considered safe because zero-initialization is valid for integer arrays.

// From new() method - line 81
id_bitmap: unsafe { MaybeUninit::zeroed().assume_init() }

The zero-initialization strategy ensures:

  • All bitmap bits start as false (available)
  • No undefined behavior from uninitialized memory
  • Deterministic initial state for ID allocation

Sources: src/lib.rs(L80 - L82) 

Iterator Support

The system provides iteration over assigned IDs through the ids() method, which returns an iterator from the underlying bitmap.

flowchart TD
ids_method["ids() -> Iter"]
bitmap_iter["id_bitmap.into_iter()"]
assigned_ids["Iterator over assigned IDs"]
note1["Only returns IDs where bitmap bit = true"]

assigned_ids --> note1
bitmap_iter --> assigned_ids
ids_method --> bitmap_iter

ID Iterator Implementation

Sources: src/lib.rs(L344 - L346) 

Usage Guide and Examples

Relevant source files

This document provides practical guidance for using the FlattenObjects container in real-world applications. It covers common usage patterns, best practices, and integration strategies for kernel-level and embedded systems development.

For detailed API documentation, see FlattenObjects API Documentation. For implementation details and internal architecture, see Implementation Details.

Container Creation and Basic Operations

The FlattenObjects container requires compile-time specification of both the object type and maximum capacity. This design ensures memory efficiency and predictable behavior in resource-constrained environments.

Basic Container Workflow

flowchart TD
subgraph RemoveOps["Remove Operations"]
    Remove["remove(id)"]
    RemoveResult["Returns Option"]
    MaybeEmpty["Container may become empty"]
end
subgraph AddOps["Object Addition Operations"]
    Add["add(object)"]
    FindID["Find next available ID"]
    AddAt["add_at(id, object)"]
    CheckID["Check if ID available"]
    AddReplace["add_or_replace_at(id, object)"]
    ForceID["Use specified ID"]
    Success1["Returns assigned ID"]
    Success2["Returns () on success"]
    Success3["Returns previous object if any"]
end
subgraph QueryOps["Query Operations"]
    Get["get(id)"]
    GetResult["Returns Option<&T>"]
    GetMut["get_mut(id)"]
    GetMutResult["Returns Option<&mut T>"]
    IsAssigned["is_assigned(id)"]
    BoolResult["Returns bool"]
    Create["FlattenObjects::new()"]
    Empty["Empty Containercount = 0"]
end
Active["Active Containercount > 0"]

Add --> FindID
AddAt --> CheckID
AddReplace --> ForceID
CheckID --> Success2
Create --> Empty
FindID --> Success1
ForceID --> Success3
Get --> GetResult
GetMut --> GetMutResult
IsAssigned --> BoolResult
MaybeEmpty --> Empty
Remove --> RemoveResult
RemoveResult --> MaybeEmpty
Success1 --> Active
Success2 --> Active
Success3 --> Active

Basic Usage Example from README

The fundamental usage pattern demonstrates object insertion, retrieval, and removal:

README.md(L14 - L36) 

This example shows:

  • Container creation with FlattenObjects::<u32, 20>::new()
  • Sequential ID assignment using add_at()
  • Object removal with remove()
  • Automatic ID reuse when using add()
  • State checking with is_assigned()

Sources: README.md(L14 - L36) 

Object Lifecycle Management


Sources: README.md(L19 - L35) 

ID Management Patterns

The container provides flexible ID assignment strategies for different use cases:

Automatic ID Assignment

// From README example - automatic ID assignment
let id = objects.add(42).unwrap();
// ID 6 was reused from previously removed object
assert_eq!(id, 6);

This pattern uses add() to let the container find the next available ID, prioritizing ID reuse for memory efficiency.

Explicit ID Assignment

// From README example - explicit ID assignment
for i in 0..=9 {
    objects.add_at(i, 23).unwrap();
}

This pattern uses add_at() when specific ID values are required, such as for handle tables or slot-based allocation systems.

ID Assignment Strategy Comparison

MethodUse CaseID SelectionError on Conflict
add()General object poolingAutomatic (reuses lowest)No (finds available)
add_at()Handle tables, specific slotsExplicitYes (fails if occupied)
add_or_replace_at()Overwrite semanticsExplicitNo (replaces existing)

Sources: README.md(L19 - L35) 

Error Handling Strategies

The container uses Result types for operations that can fail, enabling robust error handling in system code:

Common Error Scenarios

flowchart TD
subgraph subGraph2["Return Types"]
    ResultID["Result"]
    ResultUnit["Result<(), ()>"]
    OptionRef["Option<&T>"]
    OptionOwned["Option"]
end
subgraph subGraph1["Error Conditions"]
    CapFull["Container at capacity(count == CAP)"]
    IDTaken["ID already assigned"]
    IDInvalid["ID not assigned"]
end
subgraph subGraph0["Operation Types"]
    AddOp["add() operation"]
    AddAtOp["add_at() operation"]
    GetOp["get() / get_mut() operation"]
    RemoveOp["remove() operation"]
end

AddAtOp --> CapFull
AddAtOp --> IDTaken
AddAtOp --> ResultUnit
AddOp --> CapFull
AddOp --> ResultID
CapFull --> ResultID
CapFull --> ResultUnit
GetOp --> IDInvalid
GetOp --> OptionRef
IDInvalid --> OptionOwned
IDInvalid --> OptionRef
IDTaken --> ResultUnit
RemoveOp --> IDInvalid
RemoveOp --> OptionOwned

Error Handling Pattern Example

// Based on README pattern - defensive programming
match objects.add(value) {
    Ok(id) => {
        // Successfully added, use the returned ID
        println!("Object assigned ID: {}", id);
    }
    Err(()) => {
        // Container is full, handle gracefully
        eprintln!("Container capacity exceeded");
    }
}

Sources: README.md(L30 - L31) 

Integration with System Components

The FlattenObjects container is designed for integration into kernel and embedded system components:

Typical Integration Patterns

flowchart TD
subgraph subGraph3["Common Operations"]
    Alloc["Handle Allocationadd() / add_at()"]
    Lookup["Handle Resolutionget() / get_mut()"]
    Release["Resource Cleanupremove()"]
end
subgraph subGraph2["Object Types"]
    Proc["ProcessDescriptor"]
    File["FileHandle"]
    Conn["TcpConnection"]
    Dev["DeviceHandle"]
end
subgraph subGraph1["FlattenObjects Usage"]
    ProcTable["Process Handle TableFlattenObjects"]
    FileTable["File Descriptor TableFlattenObjects"]
    ConnTable["Connection PoolFlattenObjects"]
    DevTable["Device RegistryFlattenObjects"]
end
subgraph subGraph0["System Layer"]
    Kernel["Kernel Subsystem"]
    Driver["Device Driver"]
    FS["File System"]
    Net["Network Stack"]
end

ConnTable --> Conn
ConnTable --> Release
DevTable --> Alloc
DevTable --> Dev
Driver --> DevTable
FS --> FileTable
FileTable --> File
FileTable --> Lookup
Kernel --> ProcTable
Net --> ConnTable
ProcTable --> Alloc
ProcTable --> Proc

Resource Management Pattern

#![allow(unused)]
fn main() {
// Typical kernel subsystem integration pattern
struct ProcessManager {
    processes: FlattenObjects<ProcessDescriptor, MAX_PROCESSES>,
}

impl ProcessManager {
    pub fn spawn_process(&mut self, descriptor: ProcessDescriptor) -> Result<ProcessId, SpawnError> {
        match self.processes.add(descriptor) {
            Ok(pid) => Ok(ProcessId(pid)),
            Err(()) => Err(SpawnError::TooManyProcesses),
        }
    }
    
    pub fn get_process(&self, pid: ProcessId) -> Option<&ProcessDescriptor> {
        self.processes.get(pid.0)
    }
    
    pub fn terminate_process(&mut self, pid: ProcessId) -> Option<ProcessDescriptor> {
        self.processes.remove(pid.0)
    }
}
}

This pattern demonstrates:

  • Wrapping FlattenObjects in higher-level abstractions
  • Converting container IDs to domain-specific handle types
  • Providing semantic error types for integration

Sources: README.md(L7 - L11) 

Container State Inspection

The container provides methods for monitoring and debugging:

State Inspection Methods

MethodReturn TypePurpose
capacity()usizeMaximum number of objects
count()usizeCurrent number of objects
is_assigned(id)boolCheck if specific ID is in use
ids()IteratorIterate over assigned IDs

Monitoring Pattern

// Based on available methods from API
fn print_container_status<T, const CAP: usize>(container: &FlattenObjects<T, CAP>) {
    println!("Capacity: {}/{}", container.count(), container.capacity());
    println!("Assigned IDs: {:?}", container.ids().collect::<Vec<_>>());
    
    // Check specific ID ranges
    for id in 0..container.capacity() {
        if container.is_assigned(id) {
            println!("ID {} is assigned", id);
        }
    }
}

This monitoring approach is essential for:

  • Resource usage tracking in kernel subsystems
  • Debugging handle leaks
  • Capacity planning for system limits

Sources: README.md(L22 - L32) 

Basic Operations

Relevant source files

This document provides step-by-step examples of the fundamental operations available in the FlattenObjects container. It covers container creation, object addition, retrieval, removal, and state querying operations. These examples demonstrate the core functionality needed for most use cases with practical code patterns.

For detailed API documentation, see FlattenObjects API Documentation. For advanced usage patterns and integration strategies, see Advanced Patterns and Best Practices.

Container Creation

The first step in using FlattenObjects is creating a new container instance. The container requires two generic parameters: the object type T and the maximum capacity CAP.

use flatten_objects::FlattenObjects;

// Create a container that can hold up to 20 u32 values
let mut objects = FlattenObjects::<u32, 20>::new();

The new() method creates an empty container with zero-initialized internal state. The capacity is fixed at compile time and cannot exceed 1024 objects.

Container Creation Workflow

flowchart TD
Start["Start"]
CreateContainer["FlattenObjects::new()"]
InitObjects["Initialize objects: [MaybeUninit; CAP]"]
InitBitmap["Initialize id_bitmap: Bitmap"]
InitCount["Initialize count: 0"]
Ready["Container Ready"]
CheckCapacity["capacity() returns CAP"]
CheckCount["count() returns 0"]
CheckEmpty["All IDs unassigned"]

CreateContainer --> InitObjects
InitBitmap --> InitCount
InitCount --> Ready
InitObjects --> InitBitmap
Ready --> CheckCapacity
Ready --> CheckCount
Ready --> CheckEmpty
Start --> CreateContainer

Sources: src/lib.rs(L77 - L84)  src/lib.rs(L44 - L51) 

Adding Objects

The FlattenObjects container provides three methods for adding objects, each with different ID assignment strategies.

Automatic ID Assignment withadd()

The add() method automatically assigns the smallest available ID to the object:

let mut objects = FlattenObjects::<u32, 20>::new();

// Add objects with automatic ID assignment
let id1 = objects.add(23).unwrap();  // Returns 0
let id2 = objects.add(42).unwrap();  // Returns 1  
let id3 = objects.add(100).unwrap(); // Returns 2

assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(id3, 2);

When the container reaches capacity, add() returns Err(value) containing the object that couldn't be added.

Specific ID Assignment withadd_at()

The add_at() method allows placing an object at a specific ID:

let mut objects = FlattenObjects::<u32, 20>::new();

// Add objects at specific IDs
objects.add_at(5, 23).unwrap();   // Assign ID 5
objects.add_at(10, 42).unwrap();  // Assign ID 10
objects.add_at(0, 100).unwrap();  // Assign ID 0

// Attempting to use an already assigned ID fails
assert_eq!(objects.add_at(5, 999), Err(999));

Replacement withadd_or_replace_at()

The add_or_replace_at() method adds an object at a specific ID, optionally replacing any existing object:

let mut objects = FlattenObjects::<u32, 20>::new();

// Initial placement
objects.add_or_replace_at(5, 23).unwrap();  // Returns Ok(5)

// Replacement - returns the old value
let result = objects.add_or_replace_at(5, 42);
assert_eq!(result, Err(Some(23)));  // Old value returned
assert_eq!(objects.get(5), Some(&42));  // New value in place

Object Addition State Transitions


Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297) 

Retrieving Objects

Objects can be accessed using their assigned IDs through immutable and mutable reference operations.

Immutable Access withget()

The get() method returns an immutable reference to the object at the specified ID:

let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();

// Get immutable reference
if let Some(value) = objects.get(5) {
    assert_eq!(*value, 42);
}

// Non-existent ID returns None
assert_eq!(objects.get(10), None);

Mutable Access withget_mut()

The get_mut() method returns a mutable reference for in-place modification:

let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();

// Modify object in place
if let Some(value) = objects.get_mut(5) {
    *value = 100;
}

assert_eq!(objects.get(5), Some(&100));

Checking Assignment Status withis_assigned()

The is_assigned() method checks whether a specific ID has an object assigned:

let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();

assert!(objects.is_assigned(5));   // true - ID 5 has an object
assert!(!objects.is_assigned(10)); // false - ID 10 is unassigned
assert!(!objects.is_assigned(25)); // false - ID 25 is out of range

Object Retrieval Operations

flowchart TD
GetRequest["get(id) / get_mut(id) / is_assigned(id)"]
CheckRange["id < CAP?"]
ReturnNone["Return None/false"]
CheckBitmap["id_bitmap.get(id)?"]
ValidID["ID is assigned"]
GetOp["get(id)"]
GetMutOp["get_mut(id)"]
IsAssignedOp["is_assigned(id)"]
UnsafeRef["unsafe { objects[id].assume_init_ref() }"]
UnsafeMut["unsafe { objects[id].assume_init_mut() }"]
ReturnTrue["Return true"]
ReturnSomeRef["Return Some(&T)"]
ReturnSomeMut["Return Some(&mut T)"]

CheckBitmap --> ReturnNone
CheckBitmap --> ValidID
CheckRange --> CheckBitmap
CheckRange --> ReturnNone
GetMutOp --> UnsafeMut
GetOp --> UnsafeRef
GetRequest --> CheckRange
IsAssignedOp --> ReturnTrue
UnsafeMut --> ReturnSomeMut
UnsafeRef --> ReturnSomeRef
ValidID --> GetMutOp
ValidID --> GetOp
ValidID --> IsAssignedOp

Sources: src/lib.rs(L165 - L173)  src/lib.rs(L194 - L202)  src/lib.rs(L144 - L146) 

Removing Objects

The remove() method extracts an object from the container and frees its ID for reuse:

let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(5, 42).unwrap();

// Remove and return the object
let removed = objects.remove(5);
assert_eq!(removed, Some(42));

// ID is now available for reuse
assert!(!objects.is_assigned(5));

// Removing again returns None
assert_eq!(objects.remove(5), None);

When an object is removed, the ID becomes available for future add() operations, following a lowest-ID-first reuse strategy.

ID Reuse After Removal

let mut objects = FlattenObjects::<u32, 20>::new();

// Fill some IDs
objects.add_at(0, 10).unwrap();
objects.add_at(1, 20).unwrap();
objects.add_at(2, 30).unwrap();

// Remove ID 1
objects.remove(1);

// Next add() will reuse ID 1 (lowest available)
let reused_id = objects.add(999).unwrap();
assert_eq!(reused_id, 1);

Sources: src/lib.rs(L315 - L326) 

Querying Container State

The container provides several methods to inspect its current state and metadata.

Capacity and Count

let mut objects = FlattenObjects::<u32, 20>::new();

assert_eq!(objects.capacity(), 20);  // Maximum capacity
assert_eq!(objects.count(), 0);      // Current object count

objects.add(42).unwrap();
assert_eq!(objects.count(), 1);      // Count increases

objects.remove(0);
assert_eq!(objects.count(), 0);      // Count decreases

Iterating Assigned IDs

The ids() method returns an iterator over all currently assigned IDs:

let mut objects = FlattenObjects::<u32, 20>::new();
objects.add_at(0, 10).unwrap();
objects.add_at(5, 50).unwrap();
objects.add_at(2, 20).unwrap();

let assigned_ids: Vec<usize> = objects.ids().collect();
assert_eq!(assigned_ids, vec![0, 2, 5]);  // Sorted order

Sources: src/lib.rs(L99 - L101)  src/lib.rs(L122 - L124)  src/lib.rs(L344 - L346) 

Complete Workflow Example

The following example demonstrates a complete workflow combining all basic operations:

use flatten_objects::FlattenObjects;

// 1. Create container
let mut objects = FlattenObjects::<String, 10>::new();

// 2. Add objects with different methods
let auto_id = objects.add("First".to_string()).unwrap();  // ID 0
objects.add_at(5, "At Five".to_string()).unwrap();        // ID 5
objects.add_or_replace_at(2, "At Two".to_string()).unwrap(); // ID 2

// 3. Query state
assert_eq!(objects.count(), 3);
assert!(objects.is_assigned(0));
assert!(objects.is_assigned(2));
assert!(objects.is_assigned(5));

// 4. Access objects
if let Some(first) = objects.get(0) {
    println!("Object at ID 0: {}", first);
}

// 5. Modify object
if let Some(second) = objects.get_mut(2) {
    second.push_str(" (Modified)");
}

// 6. Remove object and reuse ID
objects.remove(0);
let reused_id = objects.add("Reused".to_string()).unwrap();
assert_eq!(reused_id, 0);  // Reuses the freed ID

// 7. Iterate over assigned IDs
for id in objects.ids() {
    println!("ID {}: {:?}", id, objects.get(id));
}

Complete Operation Flow

sequenceDiagram
    participant User as User
    participant FlattenObjects as "FlattenObjects"
    participant id_bitmap as "id_bitmap"
    participant objectsCAP as "objects[CAP]"

    User ->> FlattenObjects: new()
    FlattenObjects ->> id_bitmap: Initialize (all false)
    FlattenObjects ->> objectsCAP: Initialize (all uninit)
    User ->> FlattenObjects: add(value)
    FlattenObjects ->> id_bitmap: first_false_index()
    id_bitmap -->> FlattenObjects: Some(id)
    FlattenObjects ->> id_bitmap: set(id, true)
    FlattenObjects ->> objectsCAP: objects[id].write(value)
    FlattenObjects -->> User: Ok(id)
    User ->> FlattenObjects: get(id)
    FlattenObjects ->> id_bitmap: get(id)
    id_bitmap -->> FlattenObjects: true
    FlattenObjects ->> objectsCAP: objects[id].assume_init_ref()
    FlattenObjects -->> User: Some(&T)
    User ->> FlattenObjects: remove(id)
    FlattenObjects ->> id_bitmap: set(id, false)
    FlattenObjects ->> objectsCAP: objects[id].assume_init_read()
    FlattenObjects -->> User: Some(T)

Sources: src/lib.rs(L6 - L30)  README.md(L12 - L36) 

Advanced Patterns and Best Practices

Relevant source files

This document covers advanced usage patterns, error handling strategies, and integration techniques for the FlattenObjects container in resource-constrained and kernel environments. It focuses on production-ready patterns beyond basic operations.

For basic container operations and initialization, see Basic Operations. For implementation details of the underlying data structures, see Implementation Details.

Error Handling Strategies

The FlattenObjects API provides different error handling mechanisms depending on the operation semantics. Understanding these patterns is crucial for robust system integration.

Capacity-Aware Addition Patterns

The container provides three distinct addition methods with different error semantics:

flowchart TD
AddRequest["Object Addition Request"]
AutoID["add(value)"]
SpecificID["add_at(id, value)"]
ReplaceID["add_or_replace_at(id, value)"]
AutoSuccess["Ok(assigned_id)"]
AutoFail["Err(value)"]
SpecificSuccess["Ok(id)"]
SpecificFail["Err(value)"]
ReplaceSuccess["Ok(id)"]
ReplaceOccupied["Err(Some(old_value))"]
ReplaceOutOfRange["Err(None)"]
RecoveryStrategy["Recovery Strategy"]
IDConflictHandling["ID Conflict Handling"]
OldValueProcessing["Process Replaced Value"]
BoundsErrorHandling["Bounds Error Handling"]

AddRequest --> AutoID
AddRequest --> ReplaceID
AddRequest --> SpecificID
AutoFail --> RecoveryStrategy
AutoID --> AutoFail
AutoID --> AutoSuccess
ReplaceID --> ReplaceOccupied
ReplaceID --> ReplaceOutOfRange
ReplaceID --> ReplaceSuccess
ReplaceOccupied --> OldValueProcessing
ReplaceOutOfRange --> BoundsErrorHandling
SpecificFail --> IDConflictHandling
SpecificID --> SpecificFail
SpecificID --> SpecificSuccess

Pattern 1: Graceful Degradation with Automatic ID Assignment

// Pattern for systems that can fallback to alternative storage
match container.add(expensive_object) {
    Ok(id) => {
        // Store ID for later retrieval
        register_object_id(id);
    }
    Err(object) => {
        // Container full - fallback to alternative storage
        fallback_storage.store(object);
    }
}

Pattern 2: Pre-validation for Specific ID Assignment

// Pattern for systems with predetermined ID requirements
if container.is_assigned(required_id) {
    return Err(IdAlreadyAssigned(required_id));
}
match container.add_at(required_id, object) {
    Ok(id) => process_success(id),
    Err(object) => {
        // This should not happen if pre-validation succeeded
        panic!("Unexpected ID assignment failure");
    }
}

Sources: src/lib.rs(L222 - L232)  src/lib.rs(L249 - L257)  src/lib.rs(L277 - L297) 

Resource Exhaustion Handling

The 1024 capacity limit requires careful resource management:

flowchart TD
subgraph subGraph2["Recovery Mechanisms"]
    LRUEviction["LRU Eviction"]
    CompactionGC["Compaction GC"]
    EmergencyCleanup["Emergency Cleanup"]
end
subgraph subGraph1["Allocation Strategy"]
    PreCheck["Pre-allocation Check"]
    ReservationSystem["ID Reservation"]
    PriorityQueuing["Priority-based Queuing"]
end
subgraph subGraph0["Capacity Management"]
    CurrentCount["count()"]
    MaxCapacity["capacity()"]
    AvailableSlots["capacity() - count()"]
end

AvailableSlots --> LRUEviction
CompactionGC --> EmergencyCleanup
CurrentCount --> PreCheck
LRUEviction --> CompactionGC
MaxCapacity --> PreCheck
PreCheck --> ReservationSystem
ReservationSystem --> PriorityQueuing

Sources: src/lib.rs(L99 - L101)  src/lib.rs(L122 - L124)  src/lib.rs(L77 - L84) 

Memory Management Patterns

Zero-Copy Object Lifecycle

The MaybeUninit<T> backing storage enables zero-copy patterns critical for kernel environments:

flowchart TD
subgraph subGraph2["Safety Invariants"]
    SafetyCheck["is_assigned(id) check"]
    UnsafeAccess["Unsafe memory access"]
end
subgraph subGraph1["Bitmap States"]
    BitmapFalse["id_bitmap.get(id) == false"]
    BitmapTrue["id_bitmap.get(id) == true"]
end
subgraph subGraph0["Object States"]
    Uninit["MaybeUninit::uninit()"]
    Written["MaybeUninit::write(value)"]
    InitRef["assume_init_ref()"]
    InitMut["assume_init_mut()"]
    Read["assume_init_read()"]
end

BitmapFalse --> Uninit
BitmapTrue --> Read
BitmapTrue --> SafetyCheck
Read --> BitmapFalse
SafetyCheck --> InitMut
SafetyCheck --> InitRef
SafetyCheck --> UnsafeAccess
Uninit --> Written
Written --> BitmapTrue

Pattern: In-Place Modification

For large objects where copying is expensive, use get_mut for in-place modifications:

// Efficient pattern for large object updates
if let Some(large_object) = container.get_mut(object_id) {
    large_object.update_field_efficiently();
    large_object.perform_in_place_operation();
}

Sources: src/lib.rs(L48 - L51)  src/lib.rs(L194 - L202)  src/lib.rs(L79) 

Memory Layout Optimization

The fixed-size array layout provides predictable memory access patterns:

flowchart TD
subgraph subGraph1["Cache Locality"]
    SequentialAccess["Sequential ID Access"]
    LocalityPreservation["Cache Line Preservation"]
    PrefetchOptimization["Prefetch Optimization"]
end
subgraph subGraph0["Memory Layout"]
    ObjectArray["objects: [MaybeUninit<T>; CAP]"]
    Bitmap["id_bitmap: Bitmap<CAP>"]
    Counter["count: usize"]
end

LocalityPreservation --> PrefetchOptimization
ObjectArray --> SequentialAccess
SequentialAccess --> LocalityPreservation

Sources: src/lib.rs(L44 - L51) 

Performance Optimization Techniques

Batch Operations Pattern

For systems that need to add multiple objects efficiently:

flowchart TD
subgraph subGraph1["ID Allocation Optimization"]
    FirstFalse["first_false_index()"]
    BitmapScan["Bitmap Scanning"]
    IDPooling["ID Pooling Strategy"]
end
subgraph subGraph0["Batch Addition Strategy"]
    BatchRequest["Batch Object Request"]
    CapacityCheck["Available Capacity Check"]
    ReserveIDs["Reserve ID Block"]
    BulkInsert["Bulk Insert Operations"]
end

BatchRequest --> CapacityCheck
BitmapScan --> IDPooling
CapacityCheck --> ReserveIDs
FirstFalse --> BitmapScan
IDPooling --> BulkInsert
ReserveIDs --> FirstFalse

Implementation Pattern:

// Efficient batch addition with pre-validation
let required_capacity = objects_to_add.len();
if container.count() + required_capacity > container.capacity() {
    return Err(InsufficientCapacity);
}

let mut assigned_ids = Vec::with_capacity(required_capacity);
for object in objects_to_add {
    match container.add(object) {
        Ok(id) => assigned_ids.push(id),
        Err(_) => {
            // Should not happen due to pre-validation
            // Cleanup already assigned objects
            for &cleanup_id in &assigned_ids {
                container.remove(cleanup_id);
            }
            return Err(BatchAdditionFailed);
        }
    }
}

Sources: src/lib.rs(L223 - L224)  src/lib.rs(L222 - L232) 

Iterator-Based Access Patterns

The ids() iterator enables efficient bulk operations:

flowchart TD
subgraph subGraph1["Bulk Operations"]
    BulkRead["Bulk Read Operations"]
    ConditionalUpdate["Conditional Updates"]
    StatisticsGathering["Statistics Gathering"]
end
subgraph subGraph0["Iterator Usage"]
    IDsIterator["ids()"]
    BitmapIter["Bitmap::into_iter()"]
    FilteredAccess["Filtered Object Access"]
end

BitmapIter --> FilteredAccess
FilteredAccess --> BulkRead
FilteredAccess --> ConditionalUpdate
FilteredAccess --> StatisticsGathering
IDsIterator --> BitmapIter

Sources: src/lib.rs(L344 - L346)  src/lib.rs(L328 - L347) 

Kernel Integration Patterns

Resource Table Implementation

FlattenObjects is commonly used to implement kernel resource tables:

flowchart TD
subgraph subGraph2["Resource Lifecycle"]
    Allocation["Resource Allocation"]
    ActiveUse["Active Resource Use"]
    Cleanup["Resource Cleanup"]
end
subgraph subGraph1["System Call Interface"]
    SyscallEntry["System Call Entry"]
    IDValidation["Resource ID Validation"]
    ResourceAccess["Resource Access"]
    PermissionCheck["Permission Checking"]
end
subgraph subGraph0["Kernel Resource Management"]
    ProcessTable["FlattenObjects<ProcessDescriptor, 1024>"]
    FileTable["FlattenObjects<FileHandle, 512>"]
    SocketTable["FlattenObjects<NetworkSocket, 256>"]
end

ActiveUse --> ProcessTable
Allocation --> ProcessTable
Cleanup --> ProcessTable
IDValidation --> FileTable
IDValidation --> ProcessTable
IDValidation --> SocketTable
PermissionCheck --> ActiveUse
ResourceAccess --> PermissionCheck
SyscallEntry --> IDValidation

Kernel Pattern: Process Management

// Kernel process table implementation
pub struct ProcessManager {
    processes: FlattenObjects<ProcessDescriptor, 1024>,
}

impl ProcessManager {
    pub fn create_process(&mut self, executable: &Path) -> Result<ProcessId, ProcessError> {
        let process = ProcessDescriptor::new(executable)?;
        match self.processes.add(process) {
            Ok(pid) => Ok(ProcessId(pid)),
            Err(_) => Err(ProcessError::ProcessLimitExceeded),
        }
    }
    
    pub fn get_process(&self, pid: ProcessId) -> Option<&ProcessDescriptor> {
        self.processes.get(pid.0)
    }
}

Sources: src/lib.rs(L44 - L51)  src/lib.rs(L32) 

Handle Table Pattern

For systems requiring opaque handle management:

flowchart TD
subgraph subGraph1["Handle Validation"]
    HandleLookup["Handle Lookup"]
    TokenVerification["Token Verification"]
    ResourceAccess["Safe Resource Access"]
end
subgraph subGraph0["Handle Generation"]
    IDAssignment["ID Assignment"]
    HandleOpaque["Opaque Handle Creation"]
    SecurityToken["Security Token Embedding"]
end

HandleLookup --> TokenVerification
HandleOpaque --> SecurityToken
IDAssignment --> HandleOpaque
SecurityToken --> HandleLookup
TokenVerification --> ResourceAccess

Sources: src/lib.rs(L164 - L173)  src/lib.rs(L144 - L146) 

Safety and Correctness Patterns

Unsafe Code Encapsulation

The library encapsulates unsafe operations behind safe interfaces:

flowchart TD
subgraph subGraph2["Unsafe Operations"]
    AssumeInitRef["assume_init_ref()"]
    AssumeInitMut["assume_init_mut()"]
    AssumeInitRead["assume_init_read()"]
    MaybeUninitWrite["MaybeUninit::write()"]
end
subgraph subGraph1["Memory Safety Invariants"]
    BitmapSync["Bitmap-Array Synchronization"]
    InitializationState["Initialization State Tracking"]
    LifecycleManagement["Object Lifecycle Management"]
end
subgraph subGraph0["Safety Boundaries"]
    PublicAPI["Public Safe API"]
    SafetyChecks["is_assigned() Validation"]
    UnsafeOps["Unsafe Memory Operations"]
end

BitmapSync --> InitializationState
InitializationState --> UnsafeOps
PublicAPI --> SafetyChecks
SafetyChecks --> BitmapSync
UnsafeOps --> AssumeInitMut
UnsafeOps --> AssumeInitRead
UnsafeOps --> AssumeInitRef
UnsafeOps --> MaybeUninitWrite

Critical Safety Pattern:

The library maintains the invariant that id_bitmap.get(id) == true if and only if objects[id] contains an initialized value. This enables safe access through the public API.

Sources: src/lib.rs(L165 - L172)  src/lib.rs(L195 - L201)  src/lib.rs(L283 - L286)  src/lib.rs(L316 - L325) 

Const-Correctness in No-Std

The const fn new() pattern enables compile-time initialization:

#![allow(unused)]
fn main() {
// Static allocation pattern for kernel use
static GLOBAL_PROCESS_TABLE: FlattenObjects<ProcessDescriptor, 1024> = 
    FlattenObjects::new();

// Boot-time initialization
static mut INITIALIZED: bool = false;

pub fn init_process_system() {
    unsafe {
        if !INITIALIZED {
            // Perform additional initialization if needed
            INITIALIZED = true;
        }
    }
}
}

Sources: src/lib.rs(L77 - L84)  src/lib.rs(L32) 

Resource Lifecycle Management

RAII Integration Pattern

For automatic resource cleanup in kernel contexts:

flowchart TD
subgraph subGraph2["Automatic Cleanup"]
    DropImpl["Drop Implementation"]
    RemoveCall["container.remove(id)"]
    ResourceCleanup["Resource-specific cleanup"]
end
subgraph subGraph1["Lifecycle Events"]
    HandleWrapper["ResourceHandle<T>"]
    AcquireResource["acquire()"]
    UseResource["deref() / deref_mut()"]
    ReleaseResource["drop()"]
end
subgraph subGraph0["RAII Wrapper"]
    HandleWrapper["ResourceHandle<T>"]
    ContainerRef["&FlattenObjects<T, CAP>"]
    ResourceID["usize"]
    AcquireResource["acquire()"]
end

AcquireResource --> UseResource
DropImpl --> RemoveCall
HandleWrapper --> ContainerRef
HandleWrapper --> ResourceID
ReleaseResource --> DropImpl
RemoveCall --> ResourceCleanup
UseResource --> ReleaseResource

RAII Implementation Pattern:

pub struct ResourceHandle<'a, T> {
    container: &'a mut FlattenObjects<T, 1024>,
    id: usize,
}

impl<'a, T> Drop for ResourceHandle<'a, T> {
    fn drop(&mut self) {
        // Automatic cleanup when handle goes out of scope
        if let Some(resource) = self.container.remove(self.id) {
            // Perform resource-specific cleanup
            cleanup_resource(resource);
        }
    }
}

impl<'a, T> Deref for ResourceHandle<'a, T> {
    type Target = T;
    
    fn deref(&self) -> &Self::Target {
        self.container.get(self.id)
            .expect("Handle invariant violated")
    }
}

Sources: src/lib.rs(L315 - L326)  src/lib.rs(L164 - L173) 

Development and Maintenance

Relevant source files

This document covers the development infrastructure, build processes, and maintenance procedures for the flatten_objects crate. It provides guidance for contributors working on the codebase and maintainers responsible for releases and quality assurance.

For detailed API usage information, see Overview. For implementation specifics, see Implementation Details.

Development Workflow Overview

The flatten_objects crate follows a standard Rust development workflow with automated CI/CD pipelines and multi-target support for embedded and kernel environments.

Development Workflow

flowchart TD
Dev["Developer"]
LocalDev["Local Development"]
Format["cargo fmt"]
Clippy["cargo clippy"]
Build["cargo build"]
Test["cargo test"]
Push["git push"]
CI["GitHub Actions CI"]
FormatCheck["Format Check"]
ClippyCheck["Clippy Analysis"]
MultiTarget["Multi-Target Build"]
UnitTest["Unit Tests"]
DocBuild["Documentation Build"]
Linux["x86_64-unknown-linux-gnu"]
BareMetal["x86_64-unknown-none"]
RISCV["riscv64gc-unknown-none-elf"]
ARM["aarch64-unknown-none-softfloat"]
DocDeploy["GitHub Pages"]
Success["✓ All Checks Pass"]
Merge["Ready for Merge"]

CI --> ClippyCheck
CI --> DocBuild
CI --> FormatCheck
CI --> MultiTarget
CI --> UnitTest
Dev --> LocalDev
DocBuild --> DocDeploy
LocalDev --> Build
LocalDev --> Clippy
LocalDev --> Format
LocalDev --> Push
LocalDev --> Test
MultiTarget --> ARM
MultiTarget --> BareMetal
MultiTarget --> Linux
MultiTarget --> RISCV
Push --> CI
Success --> Merge
UnitTest --> Success

Sources: .github/workflows/ci.yml(L1 - L56) 

Continuous Integration Pipeline

The CI system runs on every push and pull request, performing comprehensive validation across multiple target platforms.

CI Job Configuration

ComponentConfiguration
Runnerubuntu-latest
Rust Toolchainnightly
Required Componentsrust-src,clippy,rustfmt
Target Platforms4 distinct targets
Failure Strategyfail-fast: false

CI Pipeline Stages

flowchart TD
subgraph Documentation["Documentation"]
    DocGen["cargo doc"]
    PagesDeploy["GitHub Pages Deploy"]
end
subgraph Testing["Testing"]
    UnitTests["cargo test"]
    LinuxOnly["Linux target only"]
end
subgraph subGraph1["Quality Assurance"]
    FmtCheck["cargo fmt --check"]
    ClippyRun["cargo clippy"]
    BuildStep["cargo build"]
end
subgraph subGraph0["Setup Phase"]
    Checkout["actions/checkout@v4"]
    Toolchain["dtolnay/rust-toolchain@nightly"]
    Version["rustc --version"]
end

BuildStep --> DocGen
BuildStep --> UnitTests
Checkout --> Toolchain
ClippyRun --> BuildStep
DocGen --> PagesDeploy
FmtCheck --> ClippyRun
Toolchain --> Version
UnitTests --> LinuxOnly
Version --> FmtCheck

Sources: .github/workflows/ci.yml(L5 - L31) 

Multi-Target Build Support

The crate supports multiple target architectures to accommodate different embedded and kernel environments.

Supported Target Platforms

TargetPurposeEnvironment
x86_64-unknown-linux-gnuDevelopment and testingStandard Linux userspace
x86_64-unknown-noneBare metal x86_64Kernel/bootloader environments
riscv64gc-unknown-none-elfRISC-V bare metalRISC-V embedded systems
aarch64-unknown-none-softfloatARM64 bare metalARM embedded systems

Build Matrix Execution

flowchart TD
subgraph subGraph2["Target-Specific Logic"]
    LinuxTest["Unit tests: Linux only"]
    BareMetalBuild["Bare metal: Build only"]
end
subgraph subGraph1["Per-Target Operations"]
    ClippyTarget["cargo clippy --target TARGET"]
    BuildTarget["cargo build --target TARGET"]
    TestTarget["cargo test --target TARGET"]
end
subgraph subGraph0["Matrix Strategy"]
    Toolchain["nightly toolchain"]
    Targets["4 target platforms"]
end

BuildTarget --> TestTarget
ClippyTarget --> BuildTarget
Targets --> ClippyTarget
TestTarget --> BareMetalBuild
TestTarget --> LinuxTest
Toolchain --> ClippyTarget

Sources: .github/workflows/ci.yml(L8 - L30) 

Code Quality Assurance

The CI pipeline enforces code quality through multiple automated checks.

Quality Check Commands

CheckCommandPurpose
Formattingcargo fmt --all -- --checkEnforce consistent code style
Lintingcargo clippy --target TARGET --all-featuresStatic analysis and best practices
Build Verificationcargo build --target TARGET --all-featuresCompilation validation
Testingcargo test --target TARGET -- --nocaptureFunctional correctness

Clippy Configuration

The CI pipeline includes specific clippy configuration to suppress certain warnings:

  • Suppressed: clippy::new_without_default - Allows new() methods without Default trait
  • Features: --all-features - Enables all crate features during analysis

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation System

The crate maintains automatically generated and deployed documentation.

Documentation Pipeline

flowchart TD
subgraph Configuration["Configuration"]
    RustDocFlags["RUSTDOCFLAGS environment"]
    BrokenLinks["-D rustdoc::broken_intra_doc_links"]
    MissingDocs["-D missing-docs"]
end
subgraph subGraph1["Deployment Logic"]
    BranchCheck["Check if default branch"]
    Deploy["JamesIves/github-pages-deploy-action@v4"]
    PagesUpdate["Update gh-pages branch"]
end
subgraph subGraph0["Documentation Generation"]
    DocBuild["cargo doc --no-deps"]
    IndexGen["Generate index.html redirect"]
    TreeParse["cargo tree | head -1"]
end

BranchCheck --> Deploy
BrokenLinks --> DocBuild
Deploy --> PagesUpdate
DocBuild --> IndexGen
IndexGen --> BranchCheck
MissingDocs --> DocBuild
RustDocFlags --> BrokenLinks
RustDocFlags --> MissingDocs
TreeParse --> IndexGen

Documentation Configuration

SettingValuePurpose
RUSTDOCFLAGS-D rustdoc::broken_intra_doc_links -D missing-docsEnforce complete documentation
Deployment Branchgh-pagesGitHub Pages hosting
Build Options--no-deps --all-featuresGenerate only crate docs with all features
Deployment TriggerDefault branch pushes onlyAutomatic documentation updates

Sources: .github/workflows/ci.yml(L32 - L56) 

Development Environment Requirements

Required Rust Components

ComponentPurpose
nightly toolchainRequired for no_std development
rust-srcSource code for cross-compilation
clippyLinting and static analysis
rustfmtCode formatting

Target Installation

To set up a complete development environment:

# Install nightly toolchain with required components
rustup toolchain install nightly --component rust-src clippy rustfmt

# Add target platforms
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf  
rustup target add aarch64-unknown-none-softfloat

Local Development Commands

OperationCommand
Format codecargo fmt
Check formattingcargo fmt --check
Run clippycargo clippy --all-features
Build for targetcargo build --target
Run testscargo test -- --nocapture
Generate docscargo doc --no-deps --all-features

Sources: .github/workflows/ci.yml(L15 - L19) 

Building and Testing

Relevant source files

This document details the continuous integration pipeline, multi-target build system, testing strategy, and development workflow for the flatten_objects crate. It covers the automated quality assurance processes, supported target platforms, and local development practices.

For information about project configuration and development environment setup, see Project Configuration.

CI Pipeline Overview

The flatten_objects crate uses GitHub Actions for continuous integration with two primary workflows: code quality validation and documentation deployment. The pipeline ensures compatibility across multiple target platforms while maintaining code quality standards.

CI Workflow Architecture

flowchart TD
subgraph subGraph3["Documentation Job"]
    doc_build["cargo doc --no-deps --all-features"]
    pages_deploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["Build Steps"]
    checkout["actions/checkout@v4"]
    toolchain["dtolnay/rust-toolchain@nightly"]
    rustc_version["rustc --version"]
    cargo_fmt["cargo fmt --all --check"]
    cargo_clippy["cargo clippy --target TARGET"]
    cargo_build["cargo build --target TARGET"]
    cargo_test["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["CI Job Matrix"]
    strategy["strategy.matrix"]
    rust_nightly["rust-toolchain: nightly"]
    target_linux["x86_64-unknown-linux-gnu"]
    target_none["x86_64-unknown-none"]
    target_riscv["riscv64gc-unknown-none-elf"]
    target_arm["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["GitHub Events"]
    push["push"]
    pr["pull_request"]
end

cargo_build --> cargo_test
cargo_clippy --> cargo_build
cargo_fmt --> cargo_clippy
checkout --> toolchain
doc_build --> pages_deploy
pr --> strategy
push --> doc_build
push --> strategy
rust_nightly --> target_arm
rust_nightly --> target_linux
rust_nightly --> target_none
rust_nightly --> target_riscv
rustc_version --> cargo_fmt
strategy --> rust_nightly
target_arm --> checkout
target_linux --> checkout
target_none --> checkout
target_riscv --> checkout
toolchain --> rustc_version

Sources: .github/workflows/ci.yml(L1 - L56) 

Job Matrix Configuration

The CI system uses a matrix strategy to test against multiple target platforms simultaneously:

Target PlatformPurposeTest Execution
x86_64-unknown-linux-gnuStandard Linux developmentFull test suite
x86_64-unknown-noneBare metal x86_64Build only
riscv64gc-unknown-none-elfRISC-V bare metalBuild only
aarch64-unknown-none-softfloatARM64 bare metalBuild only

Sources: .github/workflows/ci.yml(L12) 

Multi-Target Build System

The crate supports multiple target architectures to ensure compatibility with various embedded and bare metal environments. The build system validates compilation across all supported targets.

Target Platform Validation Flow

flowchart TD
subgraph subGraph3["Test Execution"]
    test_linux["cargo test --target x86_64-unknown-linux-gnu"]
    test_condition["if matrix.targets == 'x86_64-unknown-linux-gnu'"]
end
subgraph subGraph2["Build Validation"]
    build_linux["cargo build --target x86_64-unknown-linux-gnu"]
    build_none["cargo build --target x86_64-unknown-none"]
    build_riscv["cargo build --target riscv64gc-unknown-none-elf"]
    build_arm["cargo build --target aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Quality Checks"]
    fmt_check["cargo fmt --all --check"]
    clippy_check["cargo clippy --target TARGET"]
end
subgraph subGraph0["Rust Toolchain Setup"]
    nightly["nightly toolchain"]
    components["rust-src, clippy, rustfmt"]
    targets["target installation"]
end

build_linux --> test_condition
clippy_check --> build_arm
clippy_check --> build_linux
clippy_check --> build_none
clippy_check --> build_riscv
components --> targets
fmt_check --> clippy_check
nightly --> components
targets --> fmt_check
test_condition --> test_linux

Sources: .github/workflows/ci.yml(L15 - L30) 

Target-Specific Build Commands

Each target platform uses identical build commands but with different target specifications:

  • Format Check: cargo fmt --all -- --check validates code formatting consistency
  • Linting: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
  • Build: cargo build --target ${{ matrix.targets }} --all-features
  • Testing: cargo test --target ${{ matrix.targets }} -- --nocapture (Linux only)

Sources: .github/workflows/ci.yml(L23 - L30) 

Testing Strategy

The testing approach focuses on functional validation while accommodating the constraints of bare metal target platforms.

Test Execution Matrix

flowchart TD
subgraph subGraph2["Execution Strategy"]
    linux_only["x86_64-unknown-linux-gnu only"]
    build_only["Build validation only"]
    nocapture["--nocapture flag"]
end
subgraph subGraph1["Test Types"]
    unit_tests["Unit Tests"]
    integration_tests["Integration Tests"]
    doc_tests["Documentation Tests"]
end
subgraph subGraph0["Test Environments"]
    hosted["Hosted Environment"]
    bare_metal["Bare Metal Targets"]
end

bare_metal --> build_only
doc_tests --> linux_only
hosted --> doc_tests
hosted --> integration_tests
hosted --> unit_tests
integration_tests --> linux_only
linux_only --> nocapture
unit_tests --> linux_only

Sources: .github/workflows/ci.yml(L28 - L30) 

Test Execution Conditions

Tests are executed only on the x86_64-unknown-linux-gnu target due to the following constraints:

  • Bare metal targets lack test harness support
  • no_std environments have limited testing infrastructure
  • The --nocapture flag provides detailed test output for debugging

The conditional test execution is implemented using GitHub Actions matrix conditions:

if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}

Sources: .github/workflows/ci.yml(L29) 

Code Quality Checks

The CI pipeline enforces code quality through automated formatting and linting checks that run before build validation.

Quality Assurance Pipeline

flowchart TD
subgraph subGraph2["Quality Gates"]
    fmt_gate["Format validation"]
    lint_gate["Lint validation"]
    allowlist["clippy::new_without_default"]
end
subgraph subGraph1["Quality Commands"]
    fmt_cmd["cargo fmt --all --check"]
    clippy_cmd["cargo clippy --all-features"]
    version_cmd["rustc --version --verbose"]
end
subgraph subGraph0["Code Quality Tools"]
    rustfmt["rustfmt"]
    clippy["clippy"]
    rustc["rustc"]
end

clippy --> clippy_cmd
clippy_cmd --> lint_gate
fmt_cmd --> fmt_gate
lint_gate --> allowlist
rustc --> version_cmd
rustfmt --> fmt_cmd

Sources: .github/workflows/ci.yml(L20 - L25) 

Clippy Configuration

The linting process uses specific allowlist configurations:

  • Suppressed Warning: clippy::new_without_default is allowed since the crate provides specialized constructors
  • Target-Specific: Clippy runs against each target platform independently
  • Feature Complete: --all-features ensures comprehensive linting coverage

Sources: .github/workflows/ci.yml(L25) 

Documentation Build and Deployment

The documentation system automatically builds and deploys API documentation to GitHub Pages for the main branch.

Documentation Workflow

flowchart TD
subgraph subGraph3["Error Handling"]
    continue_on_error["continue-on-error"]
    branch_condition["github.ref == env.default-branch"]
end
subgraph Deployment["Deployment"]
    pages_action["JamesIves/github-pages-deploy-action@v4"]
    gh_pages["gh-pages branch"]
    single_commit["single-commit: true"]
end
subgraph subGraph1["Build Process"]
    cargo_doc["cargo doc --no-deps --all-features"]
    doc_tree["cargo tree"]
    index_html["index.html redirect"]
end
subgraph subGraph0["Documentation Job"]
    doc_trigger["default-branch push"]
    doc_permissions["contents: write"]
    rustdoc_flags["RUSTDOCFLAGS environment"]
end

branch_condition --> pages_action
cargo_doc --> continue_on_error
cargo_doc --> doc_tree
doc_permissions --> rustdoc_flags
doc_tree --> index_html
doc_trigger --> doc_permissions
index_html --> branch_condition
pages_action --> gh_pages
pages_action --> single_commit
rustdoc_flags --> cargo_doc

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Configuration

The documentation build process includes several important configurations:

  • Error Detection: RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
  • Dependency Exclusion: --no-deps focuses on crate-specific documentation
  • Branch Protection: Deployment only occurs on the default branch
  • Index Generation: Automatic redirect to main crate documentation

Sources: .github/workflows/ci.yml(L40 - L48) 

Local Development Workflow

For local development, developers should follow the same quality checks used in CI to ensure compatibility before pushing changes.

Local Development Commands

CommandPurposeTarget Requirement
cargo fmt --all --checkValidate formattingAny
cargo clippy --all-featuresRun lintingAny
cargo build --target Build validationSpecific target
cargo testExecute testsLinux host only
cargo doc --no-depsGenerate documentationAny
flowchart TD
subgraph subGraph2["Commit Process"]
    git_commit["git commit"]
    ci_trigger["CI pipeline trigger"]
end
subgraph subGraph1["Pre-commit Validation"]
    fmt_check_local["cargo fmt --all --check"]
    clippy_check_local["cargo clippy --all-features"]
    multi_target["Multi-target builds"]
end
subgraph subGraph0["Local Development"]
    code_change["Code changes"]
    fmt_local["cargo fmt --all"]
    clippy_local["cargo clippy --fix"]
    build_local["cargo build"]
    test_local["cargo test"]
end

build_local --> test_local
clippy_check_local --> multi_target
clippy_local --> build_local
code_change --> fmt_local
fmt_check_local --> clippy_check_local
fmt_local --> clippy_local
git_commit --> ci_trigger
multi_target --> git_commit
test_local --> fmt_check_local

Sources: .github/workflows/ci.yml(L22 - L27) 

Project Configuration

Relevant source files

This document covers the configuration files and settings that control the development environment, build process, and distribution of the flatten_objects crate. It focuses on the static configuration files that define project metadata, dependencies, and development workflows.

For information about the actual build and testing processes, see Building and Testing. For details about the codebase structure and implementation, see Implementation Details.

Package Configuration

The primary configuration for the flatten_objects crate is defined in Cargo.toml, which contains all package metadata, dependency specifications, and publication settings.

Package Metadata

The crate is configured as a library package with the following key attributes:

FieldValuePurpose
name"flatten_objects"Crate identifier for Cargo registry
version"0.2.3"Semantic version following SemVer
edition"2024"Rust edition (latest available)
rust-version"1.85"Minimum supported Rust version (MSRV)

The package description clearly identifies its purpose: "A container that stores numbered objects. Each object can be assigned with a unique ID." This aligns with the crate's role as a specialized data structure for resource-constrained environments.

Project Configuration Structure

flowchart TD
subgraph subGraph3["Dependency Specification"]
    Bitmaps["bitmaps = { version = 3.2, default-features = false }"]
end
subgraph subGraph2["Package Metadata"]
    Name["name = flatten_objects"]
    Version["version = 0.2.3"]
    Edition["edition = 2024"]
    Authors["authors"]
    License["license"]
    Links["repository, homepage, docs"]
end
subgraph subGraph1["Cargo.toml Sections"]
    Package["[package]"]
    Dependencies["[dependencies]"]
end
subgraph subGraph0["Project Root"]
    CargoToml["Cargo.toml"]
    GitIgnore[".gitignore"]
    SrcDir["src/"]
    TargetDir["target/ (ignored)"]
end

CargoToml --> Dependencies
CargoToml --> Package
Dependencies --> Bitmaps
GitIgnore --> TargetDir
Package --> Authors
Package --> Edition
Package --> License
Package --> Links
Package --> Name
Package --> Version

Sources: Cargo.toml(L1 - L17) 

The crate uses a triple-license configuration to maximize compatibility across different ecosystems:

license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"

This licensing strategy accommodates:

  • GPL-3.0-or-later: Copyleft compatibility for GPL projects
  • Apache-2.0: Permissive licensing for commercial use
  • MulanPSL-2.0: Chinese legal framework compliance

Sources: Cargo.toml(L7) 

The configuration establishes the project's presence in the Rust ecosystem:

FieldURLPurpose
homepagehttps://github.com/arceos-org/arceosLinks to parent ArceOS project
repositoryhttps://github.com/arceos-org/flatten_objectsSource code location
documentationhttps://docs.rs/flatten_objectsAuto-generated API docs

Sources: Cargo.toml(L8 - L10) 

Dependency Management

The crate maintains a minimal dependency footprint with only one external dependency, reflecting its design for resource-constrained environments.

External Dependencies

Dependency Configuration Analysis

flowchart TD
subgraph subGraph2["Standard Library"]
    CoreOnly["core only"]
    NoStdLib["no std library"]
    NoAlloc["no alloc"]
end
subgraph subGraph1["External Dependencies"]
    BitmapsCrate["bitmaps crate"]
    BitmapsVersion["version = 3.2"]
    BitmapsConfig["default-features = false"]
end
subgraph subGraph0["flatten_objects Crate"]
    MainCrate["flatten_objects"]
    NoStdEnv["no_std environment"]
end

BitmapsCrate --> BitmapsConfig
BitmapsCrate --> BitmapsVersion
MainCrate --> BitmapsCrate
MainCrate --> NoStdEnv
NoStdEnv --> CoreOnly
NoStdEnv --> NoAlloc
NoStdEnv --> NoStdLib

The single dependency is carefully configured:

bitmaps = { version = "3.2", default-features = false }

Key aspects of this dependency configuration:

  • Version constraint: 3.2 provides stable bitmap operations
  • Feature configuration: default-features = false ensures no_std compatibility
  • Minimal footprint: Only includes essential bitmap functionality

Sources: Cargo.toml(L15 - L16) 

No Standard Library Dependencies

The crate is designed for no_std environments, which means:

  • No implicit dependency on the Rust standard library
  • Only uses core library functionality
  • Compatible with embedded and kernel environments
  • Suitable for ArceOS integration

This configuration is reflected in the package categories: ["no-std", "data-structures"].

Sources: Cargo.toml(L12) 

Development Environment Configuration

Version Control Configuration

The .gitignore file defines which files and directories are excluded from version control:

Version Control Exclusions

flowchart TD
subgraph subGraph3["Tracked Files"]
    SourceCode["src/"]
    CargoToml["Cargo.toml"]
    ReadMe["README.md"]
end
subgraph subGraph2["Ignored Files"]
    DSStore[".DS_Store"]
    CargoLock["Cargo.lock"]
end
subgraph subGraph1["Ignored Directories"]
    Target["/target"]
    VSCode["/.vscode"]
end
subgraph subGraph0["Version Control"]
    GitRepo["Git Repository"]
    GitIgnore[".gitignore"]
end

GitIgnore --> CargoLock
GitIgnore --> DSStore
GitIgnore --> Target
GitIgnore --> VSCode
GitRepo --> CargoToml
GitRepo --> ReadMe
GitRepo --> SourceCode
Ignored ItemTypeReason
/targetDirectoryBuild artifacts and compiled output
/.vscodeDirectoryEditor-specific configuration
.DS_StoreFilemacOS system metadata
Cargo.lockFileDependency lock file (library crate)

The exclusion of Cargo.lock is significant because this is a library crate, not an application. Library crates typically don't commit their lock files to allow downstream users flexibility in dependency resolution.

Sources: .gitignore(L1 - L4) 

Editor Configuration

The .gitignore configuration shows consideration for multiple development environments:

  • VS Code: Excludes .vscode/ directory for editor settings
  • macOS: Excludes .DS_Store system files
  • Cross-platform: Standard Rust build artifact exclusions

This configuration supports a diverse development team working across different platforms and editors.

Sources: .gitignore(L2 - L3) 

Publication and Distribution Settings

Crates.io Configuration

The package is configured for publication to the official Rust package registry with appropriate metadata for discoverability:

keywords = ["arceos", "data-structures"]
categories = ["no-std", "data-structures"]

Distribution Categories and Keywords

flowchart TD
subgraph subGraph4["Target Audience"]
    EmbeddedDevs["Embedded Developers"]
    KernelDevs["Kernel Developers"]
    ArceOSUsers["ArceOS Users"]
end
subgraph subGraph3["Category Tags"]
    NoStd["no-std"]
    DataStructuresCategory["data-structures"]
end
subgraph subGraph2["Keyword Tags"]
    ArceOS["arceos"]
    DataStructures["data-structures"]
end
subgraph subGraph1["Package Classification"]
    Keywords["keywords"]
    Categories["categories"]
end
subgraph subGraph0["Crates.io Registry"]
    CratesIo["crates.io"]
    SearchIndex["Search Index"]
end

ArceOS --> ArceOSUsers
Categories --> DataStructuresCategory
Categories --> NoStd
CratesIo --> SearchIndex
DataStructures --> KernelDevs
Keywords --> ArceOS
Keywords --> DataStructures
NoStd --> EmbeddedDevs
SearchIndex --> Categories
SearchIndex --> Keywords

These classifications help users discover the crate when searching for:

  • ArceOS-related components
  • Data structure libraries
  • no_std compatible crates

Sources: Cargo.toml(L11 - L12) 

Documentation Configuration

The crate leverages automatic documentation generation through docs.rs:

  • Documentation URL: https://docs.rs/flatten_objects
  • Automatic builds on crate publication
  • API documentation generated from source code comments

This configuration ensures that comprehensive documentation is available to users without requiring separate hosting infrastructure.

Sources: Cargo.toml(L10) 

Overview

Relevant source files

This document provides an overview of the arm_gicv2 crate, a hardware abstraction layer for the ARM Generic Interrupt Controller version 2 (GICv2). The crate provides type-safe register definitions and basic operations for managing interrupts on ARM-based systems.

For detailed information about interrupt classification and ranges, see Interrupt System Architecture. For implementation details of the hardware interfaces, see Hardware Interface Implementation. For development guidance and build system information, see Development Guide.

Purpose and Scope

The arm_gicv2 crate serves as a low-level hardware abstraction layer for the ARM Generic Interrupt Controller version 2, enabling operating systems and embedded applications to manage interrupts in a type-safe manner. The crate provides register definitions, interrupt type classifications, and basic operations without imposing higher-level interrupt handling policies.

Sources: Cargo.toml(L1 - L16)  README.md(L1 - L10) 

Target Platforms and Use Cases

The crate targets ARM systems that implement the GICv2 specification, specifically:

PlatformArchitectureUse Cases
ARMv7-AApplication ProcessorsOperating systems, hypervisors
ARMv7-RReal-time ProcessorsReal-time systems, safety-critical applications

The crate is designed for no_std environments and supports multiple target architectures including aarch64-unknown-none-softfloat and riscv64gc-unknown-none-elf, making it suitable for bare-metal operating system development and embedded applications.

Sources: Cargo.toml(L11 - L12) 

Core Architecture

The following diagram illustrates the main components of the arm_gicv2 crate and their relationships to code entities:

flowchart TD
subgraph subGraph4["Hardware Target"]
    ARM_GICv2["ARM GICv2 ControllerMemory-mapped registers1024 interrupt capacity"]
end
subgraph subGraph3["External Dependencies"]
    tock_registers["tock-registers crateType-safe register access"]
end
subgraph subGraph2["Register Definitions"]
    GicDistributorRegs["GicDistributorRegsCTLR, ISENABLERICFGR, ITARGETSR"]
    GicCpuInterfaceRegs["GicCpuInterfaceRegsIAR, EOIRPMR, CTLR"]
end
subgraph subGraph1["Hardware Abstraction Layer"]
    gicv2["src/gic_v2.rs"]
    GicDistributor["GicDistributor struct"]
    GicCpuInterface["GicCpuInterface struct"]
end
subgraph subGraph0["Public API Layer"]
    lib["src/lib.rs"]
    Constants["SGI_RANGE, PPI_RANGESPI_RANGE, GIC_MAX_IRQ"]
    InterruptType["InterruptType enumtranslate_irq()"]
    TriggerMode["TriggerMode enum"]
end

GicCpuInterface --> GicCpuInterfaceRegs
GicCpuInterfaceRegs --> ARM_GICv2
GicCpuInterfaceRegs --> tock_registers
GicDistributor --> GicDistributorRegs
GicDistributorRegs --> ARM_GICv2
GicDistributorRegs --> tock_registers
InterruptType --> GicDistributor
TriggerMode --> GicDistributor
gicv2 --> GicCpuInterface
gicv2 --> GicDistributor
lib --> Constants
lib --> InterruptType
lib --> TriggerMode

Sources: Cargo.toml(L14 - L15)  high-level architecture diagrams

Key Features

The arm_gicv2 crate provides the following core features:

Interrupt Type System

  • SGI (Software Generated Interrupts): IDs 0-15 for inter-processor communication
  • PPI (Private Peripheral Interrupts): IDs 16-31 for CPU-specific peripherals
  • SPI (Shared Peripheral Interrupts): IDs 32-1019 for system-wide peripherals
  • ID Translation: The translate_irq() function maps logical interrupt IDs to physical GIC interrupt IDs

Hardware Interface Components

  • GicDistributor: System-wide interrupt configuration, routing, and priority management
  • GicCpuInterface: Per-CPU interrupt handling, acknowledgment, and completion signaling

Type-Safe Register Access

The crate leverages the tock-registers dependency to provide compile-time safety for register operations, preventing common errors in low-level hardware programming.

Sources: Cargo.toml(L6)  Cargo.toml(L14 - L15) 

Integration Context

The following diagram shows how the arm_gicv2 crate integrates into larger system contexts:

flowchart TD
subgraph subGraph2["Common Use Patterns"]
    IntMgmt["Interrupt ManagementEnable/disablePriority configCPU routing"]
    TaskSched["Task SchedulingPreemptive multitaskingLoad balancing"]
    IPC["Inter-Processor CommSGI handlingCore synchronization"]
end
subgraph subGraph1["arm_gicv2 Crate Interface"]
    PublicAPI["Public APIInterruptTypetranslate_irq()Constants"]
    HardwareAbs["Hardware AbstractionsGicDistributorGicCpuInterface"]
end
subgraph subGraph0["System Software"]
    OS["Operating SystemsBare-metal OSHypervisors"]
    RTOS["Real-time SystemsSafety-critical appsIoT devices"]
end

HardwareAbs --> IPC
HardwareAbs --> IntMgmt
HardwareAbs --> TaskSched
OS --> PublicAPI
PublicAPI --> HardwareAbs
RTOS --> PublicAPI

The crate is designed to be a foundational component in system software stacks, providing the necessary abstractions for higher-level interrupt management policies while maintaining direct access to hardware capabilities when needed.

Sources: Cargo.toml(L11 - L12)  README.md(L7 - L9) 

Interrupt System Architecture

Relevant source files

This document explains the interrupt classification and architectural design of the ARM GICv2 interrupt controller as modeled by the arm_gicv2 crate. It covers the three interrupt types (SGI, PPI, SPI), their ID ranges, trigger modes, and the translation mechanisms that map logical interrupt identifiers to physical GIC interrupt IDs.

For details about the hardware interface implementation that manages these interrupts, see Hardware Interface Implementation. For specific build and development information, see Development Guide.

GICv2 Interrupt Hierarchy

The ARM Generic Interrupt Controller version 2 supports up to 1024 interrupt sources, organized into three distinct categories based on their scope and routing capabilities. The arm_gicv2 crate models this architecture through a combination of range constants, enumeration types, and translation functions.

GICv2 Interrupt Address Space


Sources: src/lib.rs(L12 - L30) 

Interrupt Classification System

The crate defines three interrupt types through the InterruptType enum, each corresponding to different use cases and routing behaviors in the GICv2 architecture.

Interrupt Type Definitions

Interrupt TypeID RangeConstantPurpose
SGI (Software Generated)0-15SGI_RANGEInter-processor communication
PPI (Private Peripheral)16-31PPI_RANGESingle-processor specific interrupts
SPI (Shared Peripheral)32-1019SPI_RANGEMulti-processor routable interrupts

Interrupt Type Architecture


Sources: src/lib.rs(L48 - L63)  src/lib.rs(L12 - L27) 

Software Generated Interrupts (SGI)

SGIs occupy interrupt IDs 0-15 and are generated through software writes to the GICD_SGIR register. These interrupts enable communication between processor cores in multi-core systems.

  • Range: Defined by SGI_RANGE constant as 0..16
  • Generation: Software write to GIC distributor register
  • Routing: Can target specific CPUs or CPU groups
  • Use Cases: Inter-processor synchronization, cross-core notifications

Private Peripheral Interrupts (PPI)

PPIs use interrupt IDs 16-31 and are associated with peripherals private to individual processor cores, such as per-core timers and performance monitoring units.

  • Range: Defined by PPI_RANGE constant as 16..32
  • Scope: Private to individual CPU cores
  • Routing: Cannot be routed between cores
  • Use Cases: Local timers, CPU-specific peripherals

Shared Peripheral Interrupts (SPI)

SPIs occupy the largest address space (32-1019) and represent interrupts from shared system peripherals that can be routed to any available processor core.

  • Range: Defined by SPI_RANGE constant as 32..1020
  • Routing: Configurable to any CPU or CPU group
  • Distribution: Managed by GIC distributor
  • Use Cases: External device interrupts, system peripherals

Sources: src/lib.rs(L12 - L27) 

ID Translation Architecture

The translate_irq function provides a mapping mechanism between logical interrupt identifiers and physical GIC interrupt IDs, enabling type-safe interrupt management.

Translation Function Flow


Translation Examples

Input TypeLogical IDPhysical GIC IDCalculation
SGI55Direct mapping
PPI3193 + 16 (PPI_RANGE.start)
SPI100132100 + 32 (SPI_RANGE.start)

The function returns Option<usize>, with None indicating an invalid logical ID for the specified interrupt type.

Sources: src/lib.rs(L65 - L90) 

Trigger Mode System

The GICv2 controller supports two trigger modes for interrupt signals, modeled through the TriggerMode enum. These modes determine how the hardware interprets interrupt signal transitions.

Trigger Mode Definitions


Edge-Triggered Mode

Edge-triggered interrupts are asserted on detection of a rising edge and remain asserted until explicitly cleared by software, regardless of the ongoing signal state.

  • Enum Value: TriggerMode::Edge = 0
  • Detection: Rising edge of interrupt signal
  • Persistence: Remains asserted until software clears
  • Use Cases: Event-driven interrupts, completion notifications

Level-Sensitive Mode

Level-sensitive interrupts are asserted whenever the interrupt signal is at an active level and automatically deasserted when the signal becomes inactive.

  • Enum Value: TriggerMode::Level = 1
  • Detection: Active signal level
  • Persistence: Follows signal state
  • Use Cases: Status-driven interrupts, continuous monitoring

Sources: src/lib.rs(L32 - L46) 

Interrupt Types and Ranges

Relevant source files

This document details the three interrupt types supported by the ARM GICv2 controller and their corresponding interrupt ID ranges as implemented in the arm_gicv2 crate. It covers the classification system that divides the 1024-interrupt address space into Software-Generated Interrupts (SGI), Private Peripheral Interrupts (PPI), and Shared Peripheral Interrupts (SPI), along with the translation mechanisms between logical and physical interrupt identifiers.

For information about trigger modes and the translate_irq function implementation details, see Trigger Modes and Translation. For the hardware interface implementations that use these interrupt types, see Hardware Interface Implementation.

Interrupt Classification Overview

The ARM GICv2 specification defines a structured interrupt ID space of 1024 interrupts (0-1023) divided into three distinct types, each serving different purposes in the system architecture.

GICv2 Interrupt ID Space Allocation

flowchart TD
subgraph subGraph0["GIC_MAX_IRQ [1024 total interrupts]"]
    SGI["SGI_RANGEIDs 0-15Software GeneratedInter-processor communication"]
    PPI["PPI_RANGEIDs 16-31Private PeripheralSingle CPU specific"]
    SPI["SPI_RANGEIDs 32-1019Shared PeripheralMulti-CPU routing"]
    Reserved["ReservedIDs 1020-1023Implementation specific"]
end
InterruptType_SGI["InterruptType::SGI"]
InterruptType_PPI["InterruptType::PPI"]
InterruptType_SPI["InterruptType::SPI"]
translate_irq["translate_irq()"]

InterruptType_PPI --> translate_irq
InterruptType_SGI --> translate_irq
InterruptType_SPI --> translate_irq
PPI --> InterruptType_PPI
SGI --> InterruptType_SGI
SPI --> InterruptType_SPI

Sources: src/lib.rs(L12 - L30) 

Software-Generated Interrupts (SGI)

Software-Generated Interrupts occupy interrupt IDs 0-15 and are primarily used for inter-processor communication in multi-core systems. These interrupts are triggered by software writing to the GIC Distributor's GICD_SGIR register.

PropertyValue
Range ConstantSGI_RANGE
ID Range0-15 (16 interrupts)
PurposeInter-processor communication
Trigger MethodSoftware write to GICD_SGIR
TargetSpecific CPU cores

SGI Characteristics and Usage

flowchart TD
subgraph subGraph0["Common SGI Use Cases"]
    IPC["Inter-processorcommunication"]
    TaskMigration["Task migrationrequests"]
    CacheOps["Cache maintenanceoperations"]
    Scheduling["Schedulernotifications"]
end
CPU0["CPU Core 0"]
GICD_SGIR["GICD_SGIR RegisterSGI Generation"]
CPU1["CPU Core 1"]
CPU2["CPU Core 2"]
CPU3["CPU Core 3"]
SGI_0_15["SGI IDs 0-15SGI_RANGE"]
CPU0_Interface["CPU 0 Interface"]
CPU1_Interface["CPU 1 Interface"]
CPU2_Interface["CPU 2 Interface"]
CPU3_Interface["CPU 3 Interface"]

CPU0 --> GICD_SGIR
CPU1 --> GICD_SGIR
CPU2 --> GICD_SGIR
CPU3 --> GICD_SGIR
GICD_SGIR --> SGI_0_15

Sources: src/lib.rs(L12 - L16) 

Private Peripheral Interrupts (PPI)

Private Peripheral Interrupts use interrupt IDs 16-31 and are generated by peripherals that are private to each individual processor core. Each CPU core has its own set of PPI interrupt sources.

PropertyValue
Range ConstantPPI_RANGE
ID Range16-31 (16 interrupts)
PurposePrivate peripheral interrupts
ScopeSingle CPU core
ExamplesPrivate timers, PMU, virtual timer

PPI Architecture and Core Association

flowchart TD
subgraph subGraph2["PPI_RANGE [16-31]"]
    PPI_16["PPI ID 16"]
    PPI_17["PPI ID 17"]
    PPI_18["PPI ID 18"]
    PPI_Dots["..."]
    PPI_31["PPI ID 31"]
end
subgraph subGraph1["CPU Core 1"]
    Core1_Timer["Private Timer"]
    Core1_PMU["Performance Monitor"]
    Core1_VTimer["Virtual Timer"]
    Core1_Interface["CPU Interface 1"]
end
subgraph subGraph0["CPU Core 0"]
    Core0_Timer["Private Timer"]
    Core0_PMU["Performance Monitor"]
    Core0_VTimer["Virtual Timer"]
    Core0_Interface["CPU Interface 0"]
end

Core0_PMU --> PPI_17
Core0_Timer --> PPI_16
Core0_VTimer --> PPI_18
Core1_PMU --> PPI_17
Core1_Timer --> PPI_16
Core1_VTimer --> PPI_18

Sources: src/lib.rs(L18 - L21) 

Shared Peripheral Interrupts (SPI)

Shared Peripheral Interrupts occupy the largest portion of the interrupt space, using IDs 32-1019. These interrupts can be routed to any CPU core and are typically generated by system-wide peripherals.

PropertyValue
Range ConstantSPI_RANGE
ID Range32-1019 (988 interrupts)
PurposeShared peripheral interrupts
RoutingConfigurable to any CPU core
ExamplesUART, GPIO, DMA, network controllers

SPI Routing and Distribution

flowchart TD
subgraph subGraph3["CPU Cores"]
    CPU_0["CPU Core 0"]
    CPU_1["CPU Core 1"]
    CPU_2["CPU Core 2"]
    CPU_3["CPU Core 3"]
end
subgraph subGraph2["GicDistributor Routing"]
    ITARGETSR["ITARGETSR RegistersCPU target configuration"]
    ICFGR["ICFGR RegistersTrigger mode config"]
end
subgraph subGraph1["SPI_RANGE [32-1019]"]
    SPI_32["SPI ID 32"]
    SPI_33["SPI ID 33"]
    SPI_Dots["..."]
    SPI_1019["SPI ID 1019"]
end
subgraph subGraph0["Shared Peripherals"]
    UART["UART Controllers"]
    GPIO["GPIO Controllers"]
    DMA["DMA Controllers"]
    Network["Network Interfaces"]
    Storage["Storage Controllers"]
end

DMA --> SPI_Dots
GPIO --> SPI_33
ITARGETSR --> CPU_0
ITARGETSR --> CPU_1
ITARGETSR --> CPU_2
ITARGETSR --> CPU_3
Network --> SPI_Dots
SPI_Dots --> ITARGETSR
Storage --> SPI_1019
UART --> SPI_32

Sources: src/lib.rs(L23 - L27) 

Interrupt ID Translation

The translate_irq function converts logical interrupt IDs (relative to each interrupt type) into physical GIC interrupt IDs (absolute addressing within the 1024-interrupt space).

Translation Logic and Mapping

flowchart TD
subgraph subGraph2["Physical GIC IDs [Output]"]
    GIC_SGI["GIC ID 0-15"]
    GIC_PPI["GIC ID 16-31"]
    GIC_SPI["GIC ID 32-1019"]
end
subgraph subGraph1["translate_irq Function"]
    InterruptType_SGI_Match["InterruptType::SGIid < SGI_RANGE.end"]
    InterruptType_PPI_Match["InterruptType::PPIid + PPI_RANGE.start"]
    InterruptType_SPI_Match["InterruptType::SPIid + SPI_RANGE.start"]
end
subgraph subGraph0["Logical IDs [Input]"]
    SGI_Logical["SGI Logical ID0..15"]
    PPI_Logical["PPI Logical ID0..15"]
    SPI_Logical["SPI Logical ID0..987"]
end

InterruptType_PPI_Match --> GIC_PPI
InterruptType_SGI_Match --> GIC_SGI
InterruptType_SPI_Match --> GIC_SPI
PPI_Logical --> InterruptType_PPI_Match
SGI_Logical --> InterruptType_SGI_Match
SPI_Logical --> InterruptType_SPI_Match

Translation Examples

InputInterrupt TypeCalculationOutput
id=5InterruptType::SGIid(direct mapping)Some(5)
id=3InterruptType::PPIid + 16Some(19)
id=10InterruptType::SPIid + 32Some(42)
id=20InterruptType::SGIInvalid (≥16)None

Range Validation

The translation function includes bounds checking to ensure logical IDs are valid for their respective interrupt types:

  • SGI: Logical ID must be < SGI_RANGE.end (16)
  • PPI: Logical ID must be < PPI_RANGE.end - PPI_RANGE.start (16)
  • SPI: Logical ID must be < SPI_RANGE.end - SPI_RANGE.start (988)

Sources: src/lib.rs(L65 - L90) 

Implementation Constants

The interrupt ranges and limits are defined as compile-time constants in the crate's public API:

ConstantValuePurpose
SGI_RANGE0..16Software-generated interrupt range
PPI_RANGE16..32Private peripheral interrupt range
SPI_RANGE32..1020Shared peripheral interrupt range
GIC_MAX_IRQ1024Maximum interrupt capacity

These constants are used throughout the crate for bounds checking, validation, and hardware register configuration.

Sources: src/lib.rs(L12 - L30) 

Trigger Modes and Translation

Relevant source files

Purpose and Scope

This document explains the interrupt trigger mode configuration and interrupt ID translation mechanisms provided by the ARM GICv2 crate. It covers how the TriggerMode enum defines edge and level triggering behavior, and how the translate_irq function maps logical interrupt IDs to physical GIC interrupt IDs across different interrupt types.

For information about interrupt types and their ranges, see Interrupt Types and Ranges. For hardware implementation details, see Hardware Interface Implementation.

Trigger Modes

The GICv2 supports two fundamental interrupt trigger modes that determine how the interrupt controller responds to interrupt signals from peripheral devices.

Edge vs Level Triggering

flowchart TD
subgraph TriggerMode::Edge["TriggerMode::Edge"]
    subgraph subGraph1["TriggerMode::Level"]
        LevelSignal["Signal Level Active"]
        LevelBehavior["Interrupt asserted while activeDeasserted when level inactiveFollows signal state"]
        EdgeSignal["Rising Edge Detection"]
        EdgeBehavior["Interrupt asserted on edgeRemains asserted until clearedIndependent of signal state"]
    end
end

EdgeSignal --> EdgeBehavior
LevelSignal --> LevelBehavior

The TriggerMode enum defines two triggering behaviors:

ModeValueBehavior
Edge0Asserted on rising edge detection, remains asserted until cleared
Level1Asserted while signal level is active, deasserted when inactive

Edge-triggered interrupts are suitable for event-based peripherals where the interrupt indicates that something has happened, such as a timer expiration or a button press. The interrupt remains active until software explicitly clears it.

Level-sensitive interrupts are appropriate for status-based peripherals where the interrupt indicates an ongoing condition, such as a UART receive buffer containing data or a device error state.

Sources: src/lib.rs(L32 - L46) 

Interrupt ID Translation

The translate_irq function provides a mapping layer between logical interrupt IDs and physical GIC interrupt IDs. This abstraction allows software to work with interrupt IDs relative to each interrupt type rather than absolute GIC interrupt IDs.

Translation Mechanism

flowchart TD
subgraph subGraph2["Physical GIC Space"]
    PhysicalSGI["SGI: 0-15"]
    PhysicalPPI["PPI: 16-31"]
    PhysicalSPI["SPI: 32-1019"]
end
subgraph subGraph1["translate_irq Function"]
    TranslateFunc["translate_irq(id, int_type)"]
end
subgraph subGraph0["Logical Space"]
    LogicalSGI["SGI: 0-15"]
    LogicalPPI["PPI: 0-15"]
    LogicalSPI["SPI: 0-987"]
end

LogicalPPI --> TranslateFunc
LogicalSGI --> TranslateFunc
LogicalSPI --> TranslateFunc
TranslateFunc --> PhysicalPPI
TranslateFunc --> PhysicalSGI
TranslateFunc --> PhysicalSPI

The translation function maps logical IDs to physical GIC interrupt IDs based on interrupt type:

Interrupt TypeLogical ID RangePhysical ID RangeTranslation Formula
SGI0-150-15physical_id = logical_id
PPI0-1516-31physical_id = logical_id + 16
SPI0-98732-1019physical_id = logical_id + 32

Implementation Logic

The translate_irq function implements the following logic for each interrupt type:

SGI Translation: SGI interrupts maintain a direct 1:1 mapping between logical and physical IDs since they occupy the lowest interrupt ID range (0-15).

PPI Translation: PPI interrupts add an offset of 16 to map logical IDs 0-15 to physical IDs 16-31. The function validates that the logical ID is within the valid PPI range.

SPI Translation: SPI interrupts add an offset of 32 to map logical IDs 0-987 to physical IDs 32-1019. The function ensures the logical ID fits within the available SPI range.

Error Handling

The function returns Option<usize> to handle invalid interrupt IDs gracefully. It returns None when:

  • SGI logical ID ≥ 16
  • PPI logical ID ≥ 16
  • SPI logical ID ≥ 988
flowchart TD
Input["translate_irq(id, int_type)"]
SGICheck["int_type == SGI"]
PPICheck["int_type == PPI"]
SPICheck["int_type == SPI"]
SGIValid["id < 16?"]
PPIValid["id < 16?"]
SPIValid["id < 988?"]
SGIResult["Some(id)"]
PPIResult["Some(id + 16)"]
SPIResult["Some(id + 32)"]
ErrorResult["None"]

Input --> SGICheck
PPICheck --> PPIValid
PPICheck --> SPICheck
PPIValid --> ErrorResult
PPIValid --> PPIResult
SGICheck --> PPICheck
SGICheck --> SGIValid
SGIValid --> ErrorResult
SGIValid --> SGIResult
SPICheck --> SPIValid
SPIValid --> ErrorResult
SPIValid --> SPIResult

This validation ensures that only valid interrupt IDs are translated, preventing out-of-bounds access to GIC registers.

Sources: src/lib.rs(L65 - L90)  src/lib.rs(L12 - L30) 

Hardware Interface Implementation

Relevant source files

This document provides an overview of the two primary hardware abstraction components in the arm_gicv2 crate: GicDistributor and GicCpuInterface. These components provide type-safe, register-level access to ARM Generic Interrupt Controller v2 hardware through memory-mapped register interfaces.

For detailed interrupt classification and routing mechanisms, see Interrupt System Architecture. For implementation details of individual components, see GIC Distributor and GIC CPU Interface.

Core Hardware Abstraction Components

The arm_gicv2 crate implements hardware abstraction through two main structures that directly correspond to the physical GICv2 hardware blocks:

ComponentHardware BlockPrimary Responsibility
GicDistributorGIC Distributor (GICD)System-wide interrupt configuration and routing
GicCpuInterfaceGIC CPU Interface (GICC)Per-CPU interrupt handling and acknowledgment

Both components are defined in src/gic_v2.rs(L96 - L116)  and exported through the public API in src/lib.rs(L10) 

GicDistributor Structure

The GicDistributor manages system-wide interrupt configuration and provides the programming interface for:

  • Globally enabling interrupt forwarding
  • Configuring interrupt trigger modes (edge/level)
  • Setting interrupt target processors
  • Managing interrupt priority and grouping
  • Generating software interrupts (SGIs)

GicCpuInterface Structure

The GicCpuInterface handles per-CPU interrupt processing and provides the programming interface for:

  • Acknowledging pending interrupts
  • Signaling interrupt completion
  • Setting priority masks
  • Managing preemption policies

Sources: src/gic_v2.rs(L78 - L116)  src/lib.rs(L10) 

Hardware Abstraction Architecture

flowchart TD
subgraph subGraph3["Physical Hardware"]
    GICD["GIC Distributor Hardware"]
    GICC["GIC CPU Interface Hardware"]
end
subgraph subGraph2["Register Access Layer"]
    TockRegisters["tock_registers traits"]
    Readable["Readable"]
    Writeable["Writeable"]
    ReadOnly["ReadOnly"]
    WriteOnly["WriteOnly"]
end
subgraph subGraph1["Hardware Abstraction Layer"]
    DistRegs["GicDistributorRegs"]
    CpuRegs["GicCpuInterfaceRegs"]
    DistBase["NonNull"]
    CpuBase["NonNull"]
end
subgraph subGraph0["arm_gicv2 Public API"]
    LibAPI["lib.rs exports"]
    GicDist["GicDistributor"]
    GicCpu["GicCpuInterface"]
end

CpuBase --> TockRegisters
CpuRegs --> CpuBase
DistBase --> TockRegisters
DistRegs --> DistBase
GicCpu --> CpuRegs
GicDist --> DistRegs
LibAPI --> GicCpu
LibAPI --> GicDist
ReadOnly --> GICC
Readable --> GICD
TockRegisters --> ReadOnly
TockRegisters --> Readable
TockRegisters --> WriteOnly
TockRegisters --> Writeable
WriteOnly --> GICC
Writeable --> GICD

Sources: src/gic_v2.rs(L1 - L276)  src/lib.rs(L8 - L10) 

Register Interface Design

flowchart TD
subgraph subGraph2["Memory Layout"]
    DistBase["base: NonNull"]
    CpuBase["base: NonNull"]
end
subgraph subGraph1["GicCpuInterfaceRegs Structure"]
    CCTLR["CTLR: ReadWrite"]
    CPMR["PMR: ReadWrite"]
    CBPR["BPR: ReadWrite"]
    CIAR["IAR: ReadOnly"]
    CEOIR["EOIR: WriteOnly"]
    CRPR["RPR: ReadOnly"]
    CHPPIR["HPPIR: ReadOnly"]
    CDIR["DIR: WriteOnly"]
end
subgraph subGraph0["GicDistributorRegs Structure"]
    DCTLR["CTLR: ReadWrite"]
    DTYPER["TYPER: ReadOnly"]
    DISENABLER["ISENABLER: [ReadWrite; 0x20]"]
    DICENABLER["ICENABLER: [ReadWrite; 0x20]"]
    DIPRIORITYR["IPRIORITYR: [ReadWrite; 0x100]"]
    DITARGETSR["ITARGETSR: [ReadWrite; 0x100]"]
    DICFGR["ICFGR: [ReadWrite; 0x40]"]
    DSGIR["SGIR: WriteOnly"]
end

CpuBase --> CCTLR
CpuBase --> CEOIR
CpuBase --> CIAR
CpuBase --> CPMR
DistBase --> DCTLR
DistBase --> DICFGR
DistBase --> DIPRIORITYR
DistBase --> DISENABLER

The register structures are defined using the tock_registers::register_structs! macro, which provides compile-time memory layout validation and type-safe register access patterns.

Sources: src/gic_v2.rs(L12 - L76) 

Component Initialization and Lifecycle

Both hardware components follow a consistent initialization pattern:

PhaseGicDistributorGicCpuInterface
Constructionnew(base: *mut u8)new(base: *mut u8)
Initializationinit()- Disables all interrupts, configures SPIsinit()- Enables interface, unmasks priorities
RuntimeConfiguration and routing operationsInterrupt handling and acknowledgment

The initialization sequence ensures that:

  1. All interrupts are initially disabled
  2. SPI interrupts are configured as edge-triggered by default
  3. SPI targets are set to CPU 0 in multi-CPU systems
  4. Hardware interfaces are enabled for operation

Sources: src/gic_v2.rs(L124 - L131)  src/gic_v2.rs(L212 - L218)  src/gic_v2.rs(L180 - L209)  src/gic_v2.rs(L264 - L274) 

Thread Safety and Send/Sync Implementation

Both components implement Send and Sync traits through explicit unsafe impl blocks, enabling their use in multi-threaded environments:

unsafe impl Send for GicDistributor {}
unsafe impl Sync for GicDistributor {}
unsafe impl Send for GicCpuInterface {}
unsafe impl Sync for GicCpuInterface {}

This design acknowledges that while the underlying hardware registers require careful coordination, the abstraction layer itself can be safely shared across thread boundaries when properly synchronized at the application level.

Sources: src/gic_v2.rs(L118 - L122) 

GIC Distributor

Relevant source files

The GIC Distributor provides system-wide interrupt configuration and routing capabilities for the ARM GICv2 interrupt controller. This component manages interrupt prioritization, distribution to CPU interfaces, and global interrupt settings across all processors in the system.

For per-CPU interrupt handling and acknowledgment, see GIC CPU Interface. For background on interrupt types and ranges, see Interrupt Types and Ranges.

Register Structure and Memory Layout

The GIC Distributor is accessed through a memory-mapped register interface defined by the GicDistributorRegs structure. This provides type-safe access to all distributor control registers.

Distributor Register Layout


Sources: src/gic_v2.rs(L12 - L48) 

Core Implementation Structure

The GicDistributor struct provides the main interface for distributor operations, encapsulating the register base address and maximum interrupt count.

Distributor Component Architecture

flowchart TD
subgraph subGraph5["GicDistributor Implementation"]
    Struct["GicDistributorbase: NonNull<GicDistributorRegs>max_irqs: usize"]
    subgraph subGraph4["Register Access"]
        Regs["regs()→ &GicDistributorRegs"]
    end
    subgraph Initialization["Initialization"]
        Init["init()"]
    end
    subgraph subGraph2["Configuration Methods"]
        ConfigureInterrupt["configure_interrupt(vector: usize,tm: TriggerMode)"]
        SetEnable["set_enable(vector: usize,enable: bool)"]
    end
    subgraph subGraph1["Information Methods"]
        CpuNum["cpu_num()→ usize"]
        MaxIrqs["max_irqs()→ usize"]
    end
    subgraph subGraph0["Constructor Methods"]
        New["new(base: *mut u8)→ Self"]
    end
end

Regs --> ConfigureInterrupt
Regs --> CpuNum
Regs --> Init
Regs --> MaxIrqs
Regs --> SetEnable
Struct --> ConfigureInterrupt
Struct --> CpuNum
Struct --> Init
Struct --> MaxIrqs
Struct --> New
Struct --> Regs
Struct --> SetEnable

Sources: src/gic_v2.rs(L96 - L210) 

Initialization Process

The distributor initialization follows a specific sequence to establish a known state and configure default interrupt routing.

Initialization Sequence

StepOperationRegister(s)Purpose
1Disable all interruptsICENABLER[*]Clear existing enables
2Clear pending interruptsICPENDR[*]Reset pending state
3Set SPI targets to CPU 0ITARGETSR[*]Default routing
4Configure SPIs as edge-triggeredICFGR[*]Set trigger mode
5Enable distributorCTLRActivate GICD
flowchart TD
Start["init() called"]
ReadMaxIrqs["Read max_irqs from TYPER registerAssert ≤ GIC_MAX_IRQ"]
DisableAll["Disable all interruptsICENABLER[i] = 0xFFFFFFFFfor i in 0..max_irqs/32"]
ClearPending["Clear pending interruptsICPENDR[i] = 0xFFFFFFFFfor i in 0..max_irqs/32"]
CheckMultiCpu["cpu_num() > 1?"]
SetTargets["Set SPI targets to CPU 0ITARGETSR[i] = 0x01010101for i in SPI_RANGE"]
ConfigEdge["Configure SPIs as edge-triggeredconfigure_interrupt(i, TriggerMode::Edge)for i in SPI_RANGE"]
EnableGICD["Enable distributorCTLR = 1"]
End["Initialization complete"]

CheckMultiCpu --> ConfigEdge
CheckMultiCpu --> SetTargets
ClearPending --> CheckMultiCpu
ConfigEdge --> EnableGICD
DisableAll --> ClearPending
EnableGICD --> End
ReadMaxIrqs --> DisableAll
SetTargets --> ConfigEdge
Start --> ReadMaxIrqs

Sources: src/gic_v2.rs(L186 - L209) 

Interrupt Configuration

The distributor provides methods to configure interrupt properties including trigger modes and enable/disable states.

Trigger Mode Configuration

The configure_interrupt method sets the trigger mode for SPI interrupts using the ICFGR registers:

flowchart TD
subgraph subGraph0["configure_interrupt Method"]
    Input["vector: usizetm: TriggerMode"]
    Validate["Validate vector≥ SPI_RANGE.start< max_irqs"]
    CalcReg["Calculate register indexreg_idx = vector >> 4bit_shift = ((vector & 0xf) << 1) + 1"]
    ReadReg["Read ICFGR[reg_idx]"]
    ModifyBit["Modify trigger bitEdge: set bitLevel: clear bit"]
    WriteReg["Write ICFGR[reg_idx]"]
end

CalcReg --> ReadReg
Input --> Validate
ModifyBit --> WriteReg
ReadReg --> ModifyBit
Validate --> CalcReg

Enable/Disable Control

The set_enable method controls interrupt enable state using dedicated enable/disable registers:

OperationRegister UsedBit Effect
EnableISENABLER[reg]Set bit atvector % 32
DisableICENABLER[reg]Set bit atvector % 32

Where reg = vector / 32.

Sources: src/gic_v2.rs(L147 - L178) 

System Information Interface

The distributor provides methods to query system configuration determined by hardware implementation.

Hardware Detection Methods

flowchart TD
subgraph subGraph0["System Information"]
    TYPER_Reg["TYPER Register"]
    CpuNumCalc["cpu_num()((TYPER >> 5) & 0b111) + 1"]
    MaxIrqsCalc["max_irqs()((TYPER & 0b11111) + 1) * 32"]
    CpuCount["Number of CPU interfaces"]
    IrqCount["Maximum interrupt count"]
end

CpuNumCalc --> CpuCount
MaxIrqsCalc --> IrqCount
TYPER_Reg --> CpuNumCalc
TYPER_Reg --> MaxIrqsCalc

Sources: src/gic_v2.rs(L138 - L145) 

Thread Safety and Memory Access

The GicDistributor implements Send and Sync traits for safe concurrent access across threads, with register access performed through unsafe memory operations wrapped in safe interfaces.

The register access uses NonNull<GicDistributorRegs> to ensure the base pointer is always valid and uses the tock-registers crate for type-safe memory-mapped I/O operations.

Sources: src/gic_v2.rs(L5 - L135) 

GIC CPU Interface

Relevant source files

This document covers the GicCpuInterface implementation, which provides per-CPU interrupt handling functionality for ARM GICv2 controllers. The CPU interface handles interrupt acknowledgment, completion signaling, priority masking, and preemption control for individual processor cores.

For system-wide interrupt distribution and configuration, see GIC Distributor. For the overall interrupt classification system, see Interrupt System Architecture.

CPU Interface Register Structure

The GicCpuInterfaceRegs struct defines the memory-mapped register layout for the CPU interface, providing type-safe access to hardware registers that control per-CPU interrupt processing.

flowchart TD
subgraph subGraph1["Register Types"]
    RW["ReadWrite"]
    RO["ReadOnly"]
    WO["WriteOnly"]
end
subgraph subGraph0["GicCpuInterfaceRegs Structure"]
    CTLR["CTLR @ 0x0000CPU Interface Control"]
    PMR["PMR @ 0x0004Priority Mask Register"]
    BPR["BPR @ 0x0008Binary Point Register"]
    IAR["IAR @ 0x000cInterrupt Acknowledge"]
    EOIR["EOIR @ 0x0010End of Interrupt"]
    RPR["RPR @ 0x0014Running Priority"]
    HPPIR["HPPIR @ 0x0018Highest Priority Pending"]
    IIDR["IIDR @ 0x00fcInterface Identification"]
    DIR["DIR @ 0x1000Deactivate Interrupt"]
end

BPR --> RW
CTLR --> RW
DIR --> WO
EOIR --> WO
HPPIR --> RO
IAR --> RO
IIDR --> RO
PMR --> RW
RPR --> RO

Sources: src/gic_v2.rs(L50 - L76) 

Core Interface Implementation

The GicCpuInterface struct wraps the register structure and provides safe methods for interrupt handling operations.

ComponentTypePurpose
baseNonNullPointer to memory-mapped registers
Thread SafetySend + SyncSafe for multi-threaded access
flowchart TD
subgraph subGraph1["Register Operations"]
    IAR_read["IAR.get()"]
    EOIR_write["EOIR.set(iar)"]
    CTLR_enable["CTLR.set(1)"]
    PMR_unmask["PMR.set(0xff)"]
end
subgraph subGraph0["GicCpuInterface Methods"]
    new["new(base: *mut u8)"]
    regs["regs() -> &GicCpuInterfaceRegs"]
    iar["iar() -> u32"]
    eoi["eoi(iar: u32)"]
    handle_irq["handle_irq(handler: F)"]
    init["init()"]
end

eoi --> EOIR_write
handle_irq --> eoi
handle_irq --> iar
iar --> IAR_read
init --> CTLR_enable
init --> PMR_unmask
new --> regs

Sources: src/gic_v2.rs(L114 - L116)  src/gic_v2.rs(L214 - L222) 

Interrupt Acknowledgment and Completion

The CPU interface provides a two-phase interrupt handling protocol: acknowledgment via iar() and completion via eoi().

Interrupt Acknowledge Register (IAR)

The iar() method reads the GICC_IAR register to obtain the interrupt ID of the highest priority pending interrupt. This operation atomically acknowledges the interrupt and changes its state from pending to active.

#![allow(unused)]
fn main() {
pub fn iar(&self) -> u32 {
    self.regs().IAR.get()
}
}

Spurious Interrupt Handling: The method returns interrupt ID 1023 when no valid interrupt is pending, the distributor is disabled, or the CPU interface is disabled.

Sources: src/gic_v2.rs(L230 - L232) 

End of Interrupt (EOI)

The eoi() method writes to the GICC_EOIR register to signal completion of interrupt processing. The value written must match the value previously read from iar().

#![allow(unused)]
fn main() {
pub fn eoi(&self, iar: u32) {
    self.regs().EOIR.set(iar);
}
}

Sources: src/gic_v2.rs(L238 - L240) 

High-Level Interrupt Handling

The handle_irq() method provides a complete interrupt handling flow that combines acknowledgment, handler execution, and completion in a single operation.

flowchart TD
start["handle_irq() called"]
read_iar["Read IAR registeriar = self.iar()"]
extract_vector["Extract vector IDvector = iar & 0x3ff"]
check_spurious["vector < 1020?"]
call_handler["Call handler(vector)"]
write_eoir["Write EOIR registerself.eoi(iar)"]
spurious["Ignore spurious interrupt"]
end_flow["Return"]

call_handler --> write_eoir
check_spurious --> call_handler
check_spurious --> spurious
extract_vector --> check_spurious
read_iar --> extract_vector
spurious --> end_flow
start --> read_iar
write_eoir --> end_flow

The method masks the interrupt ID to 10 bits (iar & 0x3ff) to extract the vector number, as the upper bits of the IAR register contain additional status information.

Sources: src/gic_v2.rs(L250 - L262) 

CPU Interface Initialization

The init() method configures the CPU interface for operation by enabling interrupt delivery and setting priority masking.

flowchart TD
subgraph subGraph1["Register Effects"]
    ctrl_effect["Enables interrupt signalingto the processor"]
    pmr_effect["Allows interrupts atall priority levels"]
end
subgraph subGraph0["init() Operations"]
    enable_ctrl["Enable CPU InterfaceCTLR.set(1)"]
    unmask_prio["Unmask All PrioritiesPMR.set(0xff)"]
end

enable_ctrl --> ctrl_effect
unmask_prio --> pmr_effect

Initialization Requirements:

  • Must be called exactly once per CPU
  • Should be called after distributor initialization
  • Priority mask value 0xff represents the lowest priority threshold, allowing all interrupts

Sources: src/gic_v2.rs(L269 - L275) 

Thread Safety and Memory Management

The GicCpuInterface implements Send and Sync traits, enabling safe usage across thread boundaries in multi-core systems.

Safety AspectImplementation
Memory SafetyNonNullprevents null pointer dereference
Thread Safetyunsafe impl Send + Syncallows cross-thread usage
Register Accessconst fn regs()provides immutable reference to registers
Hardware SynchronizationHardware ensures atomic register operations

The unsafe implementations of Send and Sync are justified because:

  • Hardware registers are designed for concurrent access from multiple CPUs
  • Each CPU interface instance manages a separate set of memory-mapped registers
  • Register operations are atomic at the hardware level

Sources: src/gic_v2.rs(L121 - L122) 

Development Guide

Relevant source files

This page provides an overview of the development workflow, tools, and processes for contributing to the arm_gicv2 crate. It covers the essential information developers need to understand the build system, quality assurance processes, and deployment pipeline.

For detailed information about specific aspects of development, see the following sub-sections:

Development Overview

The arm_gicv2 crate follows modern Rust development practices with automated quality assurance and documentation deployment. The development workflow centers around a no_std compatible library that provides hardware abstraction for ARM Generic Interrupt Controller v2 across multiple target architectures.

Core Development Tools and Dependencies

The development ecosystem relies on several key components that work together to ensure code quality and compatibility:

Development Tool Chain

flowchart TD
subgraph subGraph3["Package Configuration"]
    PackageName["arm_gicv2"]
    Version["0.1.0"]
    License["GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
    Categories["embedded, no-std, hardware-support, os"]
end
subgraph subGraph2["Target Architectures"]
    Linux["x86_64-unknown-linux-gnu"]
    Bare["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    AArch64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Quality Tools"]
    Rustfmt["rustfmt"]
    Clippy["clippy"]
    Rustdoc["rustdoc"]
    Tests["cargo test"]
end
subgraph subGraph0["Core Tools"]
    Rust["rustc nightly"]
    Cargo["cargo build system"]
    TockRegs["tock-registers 0.8"]
end

Cargo --> AArch64
Cargo --> Bare
Cargo --> Clippy
Cargo --> Linux
Cargo --> RISCV
Cargo --> Rustdoc
Cargo --> Rustfmt
Cargo --> Tests
Categories --> Cargo
License --> Cargo
PackageName --> Cargo
Rust --> Cargo
TockRegs --> Cargo
Version --> Cargo

Sources: Cargo.toml(L1 - L16)  .github/workflows/ci.yml(L11 - L19) 

Automated Development Lifecycle

The project implements a comprehensive CI/CD pipeline that ensures code quality and deploys documentation automatically:

CI/CD Workflow

flowchart TD
subgraph subGraph4["Environment Variables"]
    DefaultBranch["default-branch"]
    RustDocFlags["RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs"]
end
subgraph Documentation["Documentation"]
    DocBuild["cargo doc --no-deps --all-features"]
    IndexGen["printf redirect to index.html"]
    GHPagesDeploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["Quality Checks"]
    VersionCheck["rustc --version --verbose"]
    FormatCheck["cargo fmt --all -- --check"]
    ClippyCheck["cargo clippy --target TARGET --all-features"]
    BuildCheck["cargo build --target TARGET --all-features"]
    UnitTest["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["CI Job Matrix"]
    Toolchain["rust-toolchain: nightly"]
    Target1["x86_64-unknown-linux-gnu"]
    Target2["x86_64-unknown-none"]
    Target3["riscv64gc-unknown-none-elf"]
    Target4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Trigger Events"]
    Push["git push"]
    PR["pull_request"]
end

BuildCheck --> UnitTest
ClippyCheck --> BuildCheck
DefaultBranch --> GHPagesDeploy
DocBuild --> IndexGen
FormatCheck --> ClippyCheck
IndexGen --> GHPagesDeploy
PR --> Toolchain
Push --> Toolchain
RustDocFlags --> DocBuild
Target1 --> DocBuild
Target1 --> VersionCheck
Toolchain --> Target1
Toolchain --> Target2
Toolchain --> Target3
Toolchain --> Target4
VersionCheck --> FormatCheck

Sources: .github/workflows/ci.yml(L1 - L56) 

Package Metadata and Configuration

The crate is configured as a library package with specific metadata that defines its purpose and compatibility:

ConfigurationValuePurpose
namearm_gicv2Crate identifier for Cargo registry
version0.1.0Semantic versioning for API compatibility
edition2021Rust language edition features
authorsYuekai Jia equation618@gmail.comPrimary maintainer contact
descriptionARM Generic Interrupt Controller version 2 (GICv2) register definitions and basic operationsCrate purpose summary
licenseGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0Multi-license compatibility
keywordsarceos, arm, aarch64, gic, interrupt-controllerDiscovery and categorization
categoriesembedded, no-std, hardware-support, osCargo registry classification

Sources: Cargo.toml(L1 - L12) 

Key Development Characteristics

No-std Compatibility

The crate is designed for no_std environments, making it suitable for bare-metal development, embedded systems, and operating system kernels. This is reflected in the target architecture support and category classifications.

Multi-target Support

Development and testing occur across four distinct target architectures:

  • x86_64-unknown-linux-gnu: Standard Linux development and testing
  • x86_64-unknown-none: Bare-metal x86_64 systems
  • riscv64gc-unknown-none-elf: RISC-V 64-bit bare-metal systems
  • aarch64-unknown-none-softfloat: ARM64 bare-metal with software floating point

Single Dependency

The crate maintains minimal external dependencies with only tock-registers = "0.8" providing type-safe register access abstractions. This design choice supports the embedded and bare-metal use cases where dependency minimization is critical.

Sources: Cargo.toml(L14 - L15)  .github/workflows/ci.yml(L12) 

Build System and Dependencies

Relevant source files

This page covers the build system configuration, dependency management, and target architecture support for the arm_gicv2 crate. It explains the Cargo package configuration, the tock-registers dependency choice, no_std compatibility requirements, and multi-target build support that enables the crate to work across embedded systems and operating system development environments.

For information about the CI/CD automation and testing infrastructure, see CI/CD Pipeline. For development environment setup and contribution workflows, see Development Environment.

Package Configuration

The arm_gicv2 crate is configured as a Rust library package that targets embedded systems and operating system development. The package metadata defines its purpose as providing ARM Generic Interrupt Controller version 2 register definitions and basic operations.

Package Metadata Structure

flowchart TD
subgraph subGraph2["Legal and Distribution"]
    license["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
    homepage["homepage: github.com/arceos-org/arceos"]
    repository["repository: github.com/arceos-org/arm_gicv2"]
    documentation["documentation: docs.rs/arm_gicv2"]
end
subgraph subGraph1["Package Description"]
    description["description: ARM GICv2 register definitions and operations"]
    keywords["keywords: arceos, arm, aarch64, gic, interrupt-controller"]
    categories["categories: embedded, no-std, hardware-support, os"]
end
subgraph subGraph0["Package Identity"]
    name["name: arm_gicv2"]
    version["version: 0.1.0"]
    edition["edition: 2021"]
    authors["authors: Yuekai Jia"]
end

categories --> documentation
description --> license
edition --> categories
keywords --> repository
name --> description
version --> keywords

Sources: Cargo.toml(L1 - L12) 

The package uses Rust edition 2021, which provides modern language features while maintaining compatibility with embedded and no_std environments. The triple licensing approach (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) ensures broad compatibility with different project licensing requirements.

Target Categories and Keywords

The crate is categorized for four primary use cases:

CategoryPurpose
embeddedIoT devices and microcontroller applications
no-stdEnvironments without standard library support
hardware-supportLow-level hardware abstraction and drivers
osOperating system kernel and hypervisor development

Sources: Cargo.toml(L12) 

Dependency Management

The crate has a minimal dependency footprint, using only the tock-registers crate for type-safe register access. This design choice prioritizes compatibility with resource-constrained environments while providing safe hardware register manipulation.

Core Dependency: tock-registers

flowchart TD
subgraph subGraph2["Hardware Registers"]
    hw_dist["Distributor Registers"]
    hw_cpu["CPU Interface Registers"]
end
subgraph subGraph1["tock-registers 0.8"]
    reg_types["Register Types"]
    read_write["ReadWrite"]
    read_only["ReadOnly"]
    write_only["WriteOnly"]
    local_reg["LocalRegisterCopy"]
end
subgraph subGraph0["arm_gicv2 Crate"]
    gicv2["arm_gicv2"]
    gic_dist["GicDistributor"]
    gic_cpu["GicCpuInterface"]
    dist_regs["GicDistributorRegs"]
    cpu_regs["GicCpuInterfaceRegs"]
end

cpu_regs --> local_reg
cpu_regs --> read_write
cpu_regs --> write_only
dist_regs --> read_only
dist_regs --> read_write
gic_cpu --> cpu_regs
gic_dist --> dist_regs
gicv2 --> gic_cpu
gicv2 --> gic_dist
local_reg --> hw_cpu
read_only --> hw_dist
read_write --> hw_cpu
read_write --> hw_dist
write_only --> hw_cpu

Sources: Cargo.toml(L15) 

The tock-registers dependency provides:

  • Type-safe register field access with compile-time guarantees
  • Memory-mapped register abstractions that prevent undefined behavior
  • Zero-cost abstractions that compile to direct memory operations
  • Support for read-only, write-only, and read-write register semantics

Dependency Version Strategy

The crate pins tock-registers to version 0.8, ensuring API stability while allowing patch-level updates. This approach balances security updates with build reproducibility across different development environments.

Sources: Cargo.toml(L14 - L15) 

Target Architecture Support

The build system supports multiple target architectures to enable deployment across different ARM-based systems and development environments. The no_std compatibility ensures the crate works in resource-constrained embedded environments.

Supported Target Architectures

flowchart TD
subgraph subGraph2["Use Cases"]
    development["Development Testing"]
    embedded["Embedded Systems"]
    os_dev["OS Development"]
    hypervisor["Hypervisor Development"]
end
subgraph subGraph1["Runtime Environments"]
    hosted["Hosted EnvironmentStandard library available"]
    bare_metal["Bare-metal EnvironmentNo standard library"]
    cross_arch["Cross-architecture TestingAlgorithm verification"]
end
subgraph subGraph0["Build Targets"]
    x86_linux["x86_64-unknown-linux-gnuDevelopment and testing"]
    x86_none["x86_64-unknown-noneBare-metal x86 systems"]
    riscv["riscv64gc-unknown-none-elfRISC-V bare-metal"]
    aarch64["aarch64-unknown-none-softfloatARM64 bare-metal"]
end

aarch64 --> bare_metal
bare_metal --> embedded
bare_metal --> hypervisor
bare_metal --> os_dev
development --> cross_arch
embedded --> cross_arch
hosted --> development
riscv --> bare_metal
x86_linux --> hosted
x86_none --> bare_metal

Sources: Referenced from high-level architecture diagrams

The multi-target support enables:

  • Development targets: Linux-based development and unit testing
  • Bare-metal targets: Direct hardware deployment without operating system
  • Cross-architecture validation: Algorithm correctness verification across platforms

no_std Compatibility Requirements

The crate maintains no_std compatibility through:

RequirementImplementation
Core library onlyNo standard library dependencies
Minimal allocationsStack-based data structures only
Hardware-direct accessMemory-mapped register operations
Deterministic behaviorNo dynamic memory allocation

This design ensures the crate works in interrupt handlers, kernel space, and resource-constrained embedded environments where the standard library is unavailable or inappropriate.

Build Configuration

The build system uses Cargo's default configuration with specific optimizations for embedded and systems programming use cases.

Build Process Flow

flowchart TD
subgraph Artifacts["Artifacts"]
    lib_crate["Library crate (.rlib)"]
    doc_artifacts["Documentation artifacts"]
    metadata["Crate metadata"]
end
subgraph subGraph2["Compilation Process"]
    target_select["Select target architecture"]
    no_std_build["Compile with no_std"]
    register_codegen["Generate register access code"]
    optimize["Apply target optimizations"]
end
subgraph subGraph1["Dependency Resolution"]
    tock_fetch["Fetch tock-registers 0.8"]
    dep_check["Verify no_std compatibility"]
    version_lock["Lock dependency versions"]
end
subgraph subGraph0["Source Organization"]
    cargo_toml["Cargo.tomlPackage configuration"]
    src_lib["src/lib.rsPublic API definitions"]
    src_gic["src/gic_v2.rsHardware implementation"]
end

cargo_toml --> tock_fetch
dep_check --> no_std_build
no_std_build --> lib_crate
optimize --> metadata
register_codegen --> doc_artifacts
src_gic --> version_lock
src_lib --> dep_check
target_select --> optimize
tock_fetch --> target_select
version_lock --> register_codegen

Sources: Cargo.toml(L1 - L16)  .gitignore(L1 - L4) 

Development Artifacts Management

The .gitignore configuration excludes build artifacts and development-specific files:

Ignored PathPurpose
/targetCargo build artifacts and intermediate files
/.vscodeVisual Studio Code workspace configuration
.DS_StoremacOS file system metadata
Cargo.lockDependency lock file (excluded for library crates)

Sources: .gitignore(L1 - L4) 

The exclusion of Cargo.lock follows Rust library conventions, allowing downstream projects to resolve their own dependency versions while maintaining compatibility with the specified version ranges.

CI/CD Pipeline

Relevant source files

This document describes the automated continuous integration and continuous deployment (CI/CD) pipeline for the arm_gicv2 crate. The pipeline is implemented using GitHub Actions and handles code quality assurance, multi-target builds, testing, and automated documentation deployment.

For information about the build system configuration and dependencies, see Build System and Dependencies. For development environment setup, see Development Environment.

Pipeline Overview

The CI/CD pipeline consists of two primary workflows that execute on every push and pull request, ensuring code quality and maintaining up-to-date documentation.

flowchart TD
subgraph subGraph3["Doc Job Outputs"]
    DocBuild["API documentation"]
    Deploy["GitHub Pages deployment"]
end
subgraph subGraph2["CI Job Outputs"]
    Build["Multi-target builds"]
    Tests["Unit tests"]
    Quality["Code quality checks"]
end
subgraph subGraph1["GitHub Actions Workflows"]
    CI["ci job"]
    DOC["doc job"]
end
subgraph subGraph0["Trigger Events"]
    Push["push"]
    PR["pull_request"]
end

CI --> Build
CI --> Quality
CI --> Tests
DOC --> Deploy
DOC --> DocBuild
PR --> CI
PR --> DOC
Push --> CI
Push --> DOC

CI/CD Pipeline Overview

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Job Configuration

The main ci job runs quality assurance checks and builds across multiple target architectures using a matrix strategy.

Matrix Strategy

The pipeline uses a fail-fast matrix configuration to test across multiple target platforms:

Target ArchitecturePurpose
x86_64-unknown-linux-gnuStandard Linux development and testing
x86_64-unknown-noneBare-metal x86_64 environments
riscv64gc-unknown-none-elfRISC-V embedded systems
aarch64-unknown-none-softfloatARM64 embedded systems
flowchart TD
subgraph subGraph2["CI Steps for Each Target"]
    Setup["actions/checkout@v4dtolnay/rust-toolchain@nightly"]
    Format["cargo fmt --all -- --check"]
    Lint["cargo clippy --target TARGET --all-features"]
    Build["cargo build --target TARGET --all-features"]
    Test["cargo test --target TARGET"]
end
subgraph subGraph1["Matrix Strategy"]
    Toolchain["nightly toolchain"]
    subgraph subGraph0["Target Architectures"]
        X86Linux["x86_64-unknown-linux-gnu"]
        X86None["x86_64-unknown-none"]
        RISCV["riscv64gc-unknown-none-elf"]
        ARM64["aarch64-unknown-none-softfloat"]
    end
end

ARM64 --> Setup
Build --> Test
Format --> Lint
Lint --> Build
RISCV --> Setup
Setup --> Format
Toolchain --> ARM64
Toolchain --> RISCV
Toolchain --> X86Linux
Toolchain --> X86None
X86Linux --> Setup
X86None --> Setup

CI Job Matrix and Execution Flow

Sources: .github/workflows/ci.yml(L6 - L30) 

Quality Assurance Steps

The CI pipeline enforces code quality through multiple automated checks:

  1. Code Formatting: Uses cargo fmt with the --check flag to ensure consistent code formatting
  2. Linting: Runs cargo clippy with all features enabled, specifically allowing the clippy::new_without_default lint
  3. Building: Compiles the crate for each target architecture with all features enabled
  4. Testing: Executes unit tests, but only for the x86_64-unknown-linux-gnu target
flowchart TD
subgraph subGraph1["Failure Conditions"]
    FormatFail["Format violations"]
    ClippyFail["Lint violations"]
    BuildFail["Compilation errors"]
    TestFail["Test failures"]
end
subgraph subGraph0["Quality Gates"]
    FormatCheck["cargo fmt --all -- --check"]
    ClippyCheck["cargo clippy --target TARGET --all-features-- -A clippy::new_without_default"]
    BuildCheck["cargo build --target TARGET --all-features"]
    TestCheck["cargo test --target TARGET(Linux only)"]
end

BuildCheck --> BuildFail
BuildCheck --> TestCheck
ClippyCheck --> BuildCheck
ClippyCheck --> ClippyFail
FormatCheck --> ClippyCheck
FormatCheck --> FormatFail
TestCheck --> TestFail

Quality Assurance Gate Sequence

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation Job

The doc job handles API documentation generation and deployment to GitHub Pages.

Documentation Build Process

The documentation build process includes strict documentation standards and automated deployment:

flowchart TD
subgraph subGraph2["Deployment Logic"]
    BranchCheck["Check if default branch"]
    Deploy["JamesIves/github-pages-deploy-action@v4"]
    GHPages["Deploy to gh-pages branch"]
end
subgraph subGraph1["Build Process"]
    DocBuild["cargo doc --no-deps --all-features"]
    IndexGen["Generate index.html redirect"]
    TreeExtract["Extract crate name from cargo tree"]
end
subgraph subGraph0["Documentation Environment"]
    DocEnv["RUSTDOCFLAGS:-D rustdoc::broken_intra_doc_links-D missing-docs"]
    Checkout["actions/checkout@v4"]
    Toolchain["dtolnay/rust-toolchain@nightly"]
end

BranchCheck --> Deploy
Checkout --> Toolchain
Deploy --> GHPages
DocBuild --> IndexGen
DocEnv --> Checkout
IndexGen --> TreeExtract
Toolchain --> DocBuild
TreeExtract --> BranchCheck

Documentation Build and Deployment Flow

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Standards

The pipeline enforces strict documentation requirements through RUSTDOCFLAGS:

  • Broken Links: -D rustdoc::broken_intra_doc_links treats broken documentation links as errors
  • Missing Documentation: -D missing-docs requires all public items to have documentation

Deployment Configuration

Documentation deployment occurs automatically under specific conditions:

  • Branch Condition: Only deploys when the push is to the default branch
  • Target Branch: Deploys to the gh-pages branch
  • Single Commit: Uses single-commit: true to maintain a clean deployment history
  • Source Folder: Deploys the target/doc directory containing generated documentation

Sources: .github/workflows/ci.yml(L38 - L55) 

Toolchain and Component Configuration

The pipeline uses the Rust nightly toolchain with specific components required for the complete CI/CD process:

ComponentPurpose
rust-srcSource code for cross-compilation
clippyLinting and code analysis
rustfmtCode formatting

The toolchain setup includes automatic target installation for all matrix targets, ensuring consistent build environments across all supported architectures.

Sources: .github/workflows/ci.yml(L15 - L19) 

Development Environment

Relevant source files

This document covers setting up and configuring a local development environment for contributing to the arm_gicv2 crate. It focuses on developer tooling, project organization, and recommended workflows for contributors.

For information about the build system configuration and dependency management, see Build System and Dependencies. For details about the automated CI/CD pipeline, see CI/CD Pipeline.

Local Development Setup

The arm_gicv2 crate requires specific toolchain configurations to support its target embedded systems. The development environment must accommodate multiple target architectures and maintain compatibility with no_std environments.

Required Toolchain Components

The project uses Rust nightly toolchain with specific components as defined in the CI configuration:

ComponentPurposeUsage Context
rust-srcSource code for cross-compilationRequired forno_stdtargets
clippyLinting and code analysisCode quality enforcement
rustfmtCode formattingStyle consistency

Development Workflow Diagram

flowchart TD
Dev["Developer"]
Setup["Local Setup"]
Toolchain["rustup toolchain install nightly"]
Components["rustup component add rust-src clippy rustfmt"]
Targets["rustup target add"]
Format["cargo fmt --check"]
Lint["cargo clippy"]
Build["cargo build --target"]
Test["cargo test (Linux only)"]
Commit["git commit"]
Push["git push"]
CI["GitHub Actions CI"]

Build --> Commit
Commit --> Push
Components --> Build
Components --> Format
Components --> Lint
Components --> Test
Dev --> Setup
Format --> Commit
Lint --> Commit
Push --> CI
Setup --> Components
Setup --> Targets
Setup --> Toolchain
Test --> Commit

Sources: .github/workflows/ci.yml(L15 - L19) 

Target Architecture Support

The development environment must support multiple target architectures for cross-compilation testing:

flowchart TD
subgraph subGraph1["Development Commands"]
    Check["cargo check --target"]
    Build["cargo build --target"]
    Clippy["cargo clippy --target"]
    Test["cargo test --target"]
end
subgraph subGraph0["Development Targets"]
    Linux["x86_64-unknown-linux-gnuTesting & Development"]
    Bare["x86_64-unknown-noneBare Metal x86"]
    RISC["riscv64gc-unknown-none-elfRISC-V Embedded"]
    ARM["aarch64-unknown-none-softfloatARM64 Embedded"]
end

ARM --> Build
ARM --> Check
ARM --> Clippy
Bare --> Build
Bare --> Check
Bare --> Clippy
Linux --> Build
Linux --> Check
Linux --> Clippy
Linux --> Test
RISC --> Build
RISC --> Check
RISC --> Clippy

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L25 - L30) 

Development Tools and Editor Configuration

Editor Support

The project includes VS Code in its gitignore patterns, indicating that some developers use VS Code but configuration is not standardized across the team.

File Organization and Ignored Paths

flowchart TD
subgraph subGraph2["Development Actions"]
    Src["src/Source code"]
    Cargo["Cargo.tomlProject manifest"]
    CI[".github/CI configuration"]
    Git[".gitignoreIgnore patterns"]
    Build["cargo build"]
    Editor["VS Code setup"]
    macOS["macOS development"]
    Install["cargo install"]
end
subgraph subGraph1["Ignored Files (.gitignore)"]
    Target["/targetBuild artifacts"]
    VSCode["/.vscodeEditor configuration"]
    DS[".DS_StoremacOS file metadata"]
    Lock["Cargo.lockDependency lockfile"]
end
subgraph subGraph0["Tracked Files"]
    Src["src/Source code"]
    Cargo["Cargo.tomlProject manifest"]
    CI[".github/CI configuration"]
    Git[".gitignoreIgnore patterns"]
    README["README.mdDocumentation"]
    Build["cargo build"]
    Editor["VS Code setup"]
    macOS["macOS development"]
    Install["cargo install"]
end

Build --> Target
Editor --> VSCode
Install --> Lock
macOS --> DS

Sources: .gitignore(L1 - L4) 

Ignored Development Artifacts

The .gitignore configuration reveals important aspects of the development environment:

PatternPurposeRationale
/targetRust build artifactsGenerated bycargo buildcommands
/.vscodeVS Code workspace settingsDeveloper-specific editor configuration
.DS_StoremacOS file system metadataPlatform-specific system files
Cargo.lockDependency version lockfileLibrary crate - consumers control versions

The exclusion of Cargo.lock is particularly significant as it indicates this is a library crate where downstream consumers should control dependency versions rather than the library itself.

Sources: .gitignore(L1 - L4) 

Quality Assurance Integration

Local Development Commands

The development environment should replicate CI checks locally before committing:

# Format check (matches CI)
cargo fmt --all -- --check

# Linting (matches CI with specific allowances) 
cargo clippy --target <target> --all-features -- -A clippy::new_without_default

# Build verification
cargo build --target <target> --all-features

# Unit testing (Linux target only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

Documentation Development

Local documentation building follows the same process as CI deployment:

# Build documentation with strict link checking
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features

Sources: .github/workflows/ci.yml(L23 - L30)  .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47) 

Contribution Workflow

Pre-commit Verification

Before submitting changes, developers should verify their code passes all CI checks locally:

Local Testing Workflow

flowchart TD
Change["Code Changes"]
Format["cargo fmt --all"]
FormatCheck["cargo fmt --all -- --check"]
Clippy["cargo clippy (all targets)"]
Build["cargo build (all targets)"]
Test["cargo test (Linux only)"]
Doc["cargo doc --no-deps"]
Commit["Ready for commit"]
Fix["Fix lint issues"]
Debug["Fix build errors"]
TestFix["Fix test failures"]
DocFix["Fix documentation"]

Build --> Debug
Build --> Test
Change --> Format
Clippy --> Build
Clippy --> Fix
Debug --> Build
Doc --> Commit
Doc --> DocFix
DocFix --> Doc
Fix --> Clippy
Format --> FormatCheck
FormatCheck --> Clippy
FormatCheck --> Format
Test --> Doc
Test --> TestFix
TestFix --> Test

Platform-Specific Considerations

Since unit tests only run on x86_64-unknown-linux-gnu, developers working on other platforms should ensure access to a Linux environment for complete testing.

The multi-target build verification ensures that changes maintain compatibility across all supported embedded platforms without requiring developers to have access to physical hardware.

Sources: .github/workflows/ci.yml(L28 - L30)  .github/workflows/ci.yml(L12) 

Overview

Relevant source files

Purpose and Scope

This document provides an overview of the axconfig-gen repository, a TOML-based configuration generation system designed for ArceOS. The system provides two primary interfaces: a command-line tool (axconfig-gen) for build-time configuration generation and procedural macros (axconfig-macros) for compile-time configuration embedding. The system transforms TOML configuration specifications into either TOML output files or Rust constant definitions with proper type annotations.

For detailed CLI usage patterns, see Command Line Interface. For comprehensive macro usage, see Macro Usage Patterns. For configuration format specifications, see TOML Configuration Format.

System Architecture

The repository implements a dual-interface configuration system organized as a Cargo workspace with two main crates that share core processing logic.

Component Architecture

flowchart TD
subgraph subGraph3["External Dependencies"]
    CLAP["clap (CLI parsing)"]
    TOML_EDIT["toml_edit (TOML manipulation)"]
    PROC_MACRO["proc-macro2, quote, syn"]
end
subgraph subGraph2["Cargo Workspace"]
    subgraph subGraph1["axconfig-macros Crate"]
        PARSE["parse_configs! macro"]
        INCLUDE["include_configs! macro"]
        MACLIB["Macro Implementation (lib.rs)"]
    end
    subgraph subGraph0["axconfig-gen Crate"]
        CLI["CLI Tool (main.rs)"]
        LIB["Library API (lib.rs)"]
        CONFIG["Config Structures"]
        VALUE["ConfigValue Types"]
        OUTPUT["Output Generation"]
    end
end

CLI --> CLAP
CLI --> CONFIG
CLI --> OUTPUT
CONFIG --> TOML_EDIT
INCLUDE --> LIB
LIB --> CONFIG
LIB --> VALUE
MACLIB --> LIB
MACLIB --> PROC_MACRO
PARSE --> LIB

Sources: README.md(L1 - L109)  [Cargo.toml workspace structure implied]

Core Processing Pipeline

flowchart TD
subgraph subGraph2["Output Generation"]
    TOML_OUT["TOML Output (.axconfig.toml)"]
    RUST_OUT["Rust Constants (pub const)"]
    COMPILE_TIME["Compile-time Embedding"]
end
subgraph subGraph1["Core Processing (axconfig-gen)"]
    PARSER["Config::from_toml()"]
    VALIDATOR["Config validation & merging"]
    TYPESYS["ConfigType inference"]
    FORMATTER["OutputFormat selection"]
end
subgraph subGraph0["Input Sources"]
    TOML_FILES["TOML Files (.toml)"]
    TOML_STRINGS["Inline TOML Strings"]
    TYPE_COMMENTS["Type Annotations (# comments)"]
end

FORMATTER --> COMPILE_TIME
FORMATTER --> RUST_OUT
FORMATTER --> TOML_OUT
PARSER --> VALIDATOR
TOML_FILES --> PARSER
TOML_STRINGS --> PARSER
TYPESYS --> FORMATTER
TYPE_COMMENTS --> TYPESYS
VALIDATOR --> TYPESYS

Sources: README.md(L39 - L65)  README.md(L69 - L98) 

Usage Modes

The system operates in two distinct modes, each targeting different use cases in the ArceOS build pipeline.

ModeInterfaceInput SourceOutput TargetUse Case
CLI Modeaxconfig-gencommandFile paths (...)File output (-o,-f)Build-time generation
Macro Modeparse_configs!,include_configs!Inline strings or file pathsToken streamCompile-time embedding

CLI Mode Operation

The CLI tool processes multiple TOML specification files and generates output files in either TOML or Rust format.

flowchart TD
subgraph Outputs["Outputs"]
    TOML_FILE[".axconfig.toml"]
    RUST_FILE["constants.rs"]
end
subgraph Processing["Processing"]
    MERGE["Config merging"]
    VALIDATE["Type validation"]
    GENERATE["Output generation"]
end
subgraph subGraph0["CLI Arguments"]
    SPEC["... (input files)"]
    OUTPUT["-o(output path)"]
    FORMAT["-f  (toml|rust)"]
    OLDCONFIG["-c  (merging)"]
end

FORMAT --> GENERATE
GENERATE --> RUST_FILE
GENERATE --> TOML_FILE
MERGE --> VALIDATE
OLDCONFIG --> MERGE
OUTPUT --> GENERATE
SPEC --> MERGE
VALIDATE --> GENERATE

Sources: README.md(L8 - L31) 

Macro Mode Operation

Procedural macros embed configuration processing directly into the compilation process, generating constants at compile time.

flowchart TD
subgraph subGraph2["Generated Constants"]
    GLOBAL_CONST["pub const GLOBAL_VAR"]
    MODULE_CONST["pub mod module_name"]
    TYPED_CONST["Typed constants"]
end
subgraph subGraph1["Compile-time Processing"]
    TOKEN_PARSE["TokenStream parsing"]
    CONFIG_PROC["Config processing"]
    CODE_GEN["Rust code generation"]
end
subgraph subGraph0["Macro Invocations"]
    PARSE_INLINE["parse_configs!(toml_string)"]
    INCLUDE_FILE["include_configs!(file_path)"]
    INCLUDE_ENV["include_configs!(path_env = VAR)"]
end

CODE_GEN --> GLOBAL_CONST
CODE_GEN --> MODULE_CONST
CODE_GEN --> TYPED_CONST
CONFIG_PROC --> CODE_GEN
INCLUDE_ENV --> TOKEN_PARSE
INCLUDE_FILE --> TOKEN_PARSE
PARSE_INLINE --> TOKEN_PARSE
TOKEN_PARSE --> CONFIG_PROC

Sources: README.md(L67 - L108) 

Type System and Output Generation

The system implements a sophisticated type inference and annotation system that enables generation of properly typed Rust constants.

Type Annotation System

Configuration values support type annotations through TOML comments, enabling precise Rust type generation:

  • bool - Boolean values
  • int - Signed integers (isize)
  • uint - Unsigned integers (usize)
  • str - String literals (&str)
  • [type] - Arrays of specified type
  • (type1, type2, ...) - Tuples with mixed types

Configuration Structure Mapping

flowchart TD
subgraph subGraph1["Rust Output Structure"]
    PUB_CONST["pub const GLOBAL_ITEMS"]
    PUB_MOD["pub mod section_name"]
    NESTED_CONST["pub const SECTION_ITEMS"]
    TYPED_VALUES["Properly typed values"]
end
subgraph subGraph0["TOML Structure"]
    GLOBAL_TABLE["Global Table (root level)"]
    NAMED_TABLES["Named Tables ([section])"]
    CONFIG_ITEMS["Key-Value Pairs"]
    TYPE_ANNOTATIONS["Unsupported markdown: heading"]
end

CONFIG_ITEMS --> NESTED_CONST
GLOBAL_TABLE --> PUB_CONST
NAMED_TABLES --> PUB_MOD
PUB_MOD --> NESTED_CONST
TYPE_ANNOTATIONS --> TYPED_VALUES

Sources: README.md(L35 - L36)  README.md(L42 - L64)  README.md(L89 - L97) 

Integration with ArceOS

The configuration system integrates into the ArceOS build process through multiple pathways, providing flexible configuration management for the operating system's components.

flowchart TD
subgraph subGraph3["ArceOS Build"]
    CARGO_BUILD["Cargo Build Process"]
    ARCEOS_KERNEL["ArceOS Kernel"]
end
subgraph subGraph2["Generated Artifacts"]
    AXCONFIG[".axconfig.toml"]
    RUST_CONSTS["Rust Constants"]
    EMBEDDED_CONFIG["Compile-time Config"]
end
subgraph subGraph1["Configuration Processing"]
    CLI_GEN["axconfig-gen CLI"]
    MACRO_PROC["axconfig-macros processing"]
end
subgraph Development["Development"]
    DEV_CONFIG["defconfig.toml"]
    BUILD_SCRIPTS["Build Scripts"]
    RUST_SOURCES["Rust Source Files"]
end

AXCONFIG --> CARGO_BUILD
BUILD_SCRIPTS --> CLI_GEN
CARGO_BUILD --> ARCEOS_KERNEL
CLI_GEN --> AXCONFIG
CLI_GEN --> RUST_CONSTS
DEV_CONFIG --> CLI_GEN
DEV_CONFIG --> MACRO_PROC
EMBEDDED_CONFIG --> CARGO_BUILD
MACRO_PROC --> EMBEDDED_CONFIG
RUST_CONSTS --> CARGO_BUILD
RUST_SOURCES --> MACRO_PROC

Sources: README.md(L27 - L33)  README.md(L102 - L108) 

This architecture enables ArceOS to maintain consistent configuration across build-time generation and compile-time embedding, supporting both static configuration files and dynamic configuration processing during compilation.

System Architecture

Relevant source files

This document explains the architectural design of the axconfig-gen configuration system, covering how the axconfig-gen and axconfig-macros packages work together within a unified Cargo workspace. It details the component relationships, data flow patterns, and integration points that enable both CLI-based and compile-time configuration processing for ArceOS.

For specific CLI tool usage, see Command Line Interface. For procedural macro implementation details, see Macro Implementation.

Workspace Organization

The axconfig-gen repository implements a dual-package architecture within a Cargo workspace that provides both standalone tooling and compile-time integration capabilities.

Workspace Structure

flowchart TD
subgraph subGraph2["External Dependencies"]
    TOML_EDIT["toml_edit = '0.22'"]
    CLAP["clap = '4'"]
    PROC_MACRO["proc-macro2, quote, syn"]
end
subgraph subGraph1["axconfig-macros Package"]
    AM_LIB["src/lib.rsparse_configs!, include_configs!"]
    AM_TESTS["tests/Integration tests"]
end
subgraph subGraph0["axconfig-gen Package"]
    AG_MAIN["src/main.rsCLI entry point"]
    AG_LIB["src/lib.rsPublic API exports"]
    AG_CONFIG["src/config.rsConfig, ConfigItem"]
    AG_VALUE["src/value.rsConfigValue"]
    AG_TYPE["src/ty.rsConfigType"]
    AG_OUTPUT["src/output.rsOutputFormat"]
end
WS["Cargo Workspaceresolver = '2'"]

AG_CONFIG --> AG_OUTPUT
AG_CONFIG --> AG_TYPE
AG_CONFIG --> AG_VALUE
AG_CONFIG --> TOML_EDIT
AG_LIB --> AG_CONFIG
AG_MAIN --> AG_CONFIG
AG_MAIN --> CLAP
AM_LIB --> AG_LIB
AM_LIB --> PROC_MACRO
AM_TESTS --> AM_LIB
WS --> AG_LIB
WS --> AG_MAIN
WS --> AM_LIB

Sources: Cargo.toml(L1 - L7)  axconfig-gen/Cargo.toml(L1 - L18)  axconfig-macros/Cargo.toml(L1 - L26) 

Component Architecture

The system implements a layered architecture where axconfig-macros depends on axconfig-gen for core functionality, enabling code reuse across both CLI and compile-time processing modes.

Core Processing Components

flowchart TD
subgraph subGraph3["Macro Integration"]
    PARSE_CONFIGS["parse_configs!TokenStream processing"]
    INCLUDE_CONFIGS["include_configs!File path resolution"]
    PROC_MACRO_API["proc_macro2::TokenStreamquote! macro usage"]
end
subgraph subGraph2["Processing Layer"]
    TOML_PARSE["toml_edit integrationDocument parsing"]
    TYPE_INFERENCE["Type inferencefrom values and comments"]
    VALIDATION["Value validationagainst types"]
    CODE_GEN["Rust code generationpub const definitions"]
end
subgraph subGraph1["Data Model Layer"]
    CONFIG["Configglobal: ConfigTabletables: BTreeMap"]
    CONFIG_ITEM["ConfigItemvalue: ConfigValuety: Optioncomment: Option"]
    CONFIG_VALUE["ConfigValueBool, Int, Str, Array, Table"]
    CONFIG_TYPE["ConfigTypeBool, Int, UInt, Str, Tuple, Array"]
end
subgraph subGraph0["Public API Layer"]
    CONFIG_API["Config::from_toml()Config::merge()Config::update()"]
    OUTPUT_API["Config::dump(OutputFormat)"]
    CLI_API["clap::ParserArgs struct"]
end

CLI_API --> CONFIG_API
CODE_GEN --> PROC_MACRO_API
CONFIG --> CODE_GEN
CONFIG --> CONFIG_ITEM
CONFIG --> TOML_PARSE
CONFIG_API --> CONFIG
CONFIG_ITEM --> CONFIG_TYPE
CONFIG_ITEM --> CONFIG_VALUE
CONFIG_TYPE --> VALIDATION
CONFIG_VALUE --> TYPE_INFERENCE
INCLUDE_CONFIGS --> CONFIG_API
OUTPUT_API --> CONFIG
PARSE_CONFIGS --> CONFIG_API

Sources: README.md(L39 - L65)  README.md(L69 - L98) 

Integration Patterns

The architecture supports two distinct integration patterns that share common core processing but serve different use cases in the ArceOS build pipeline.

Dual Processing Modes

Processing ModeEntry PointInput SourceOutput TargetUse Case
CLI Modeaxconfig-genbinaryFile argumentsGenerated filesBuild-time configuration
Macro Modeparse_configs!/include_configs!Inline TOML / File pathsToken streamsCompile-time constants

Data Flow Architecture

flowchart TD
subgraph subGraph3["Integration Points"]
    CLI_ARGS["clap::Args--spec, --output, --fmt"]
    MACRO_INVOKE["macro invocationparse_configs!()"]
    BUILD_SYSTEM["Cargo buildintegration"]
end
subgraph subGraph2["Output Generation"]
    TOML_OUT["OutputFormat::Tomlregenerated TOML"]
    RUST_OUT["OutputFormat::Rustpub const code"]
    TOKEN_STREAM["proc_macro2::TokenStreamcompile-time expansion"]
end
subgraph subGraph1["Core Processing"]
    PARSE["toml_edit::Documentparsing"]
    CONFIG_BUILD["Config::from_toml()structure building"]
    TYPE_PROC["ConfigType inferenceand validation"]
    MERGE["Config::merge()multiple sources"]
end
subgraph subGraph0["Input Sources"]
    TOML_FILES["TOML filesdefconfig.toml"]
    TOML_STRINGS["Inline TOMLstring literals"]
    ENV_VARS["Environment variablespath resolution"]
end

CLI_ARGS --> TOML_FILES
CONFIG_BUILD --> TYPE_PROC
ENV_VARS --> TOML_FILES
MACRO_INVOKE --> TOML_STRINGS
MERGE --> RUST_OUT
MERGE --> TOKEN_STREAM
MERGE --> TOML_OUT
PARSE --> CONFIG_BUILD
RUST_OUT --> BUILD_SYSTEM
TOKEN_STREAM --> BUILD_SYSTEM
TOML_FILES --> PARSE
TOML_OUT --> BUILD_SYSTEM
TOML_STRINGS --> PARSE
TYPE_PROC --> MERGE

Sources: README.md(L10 - L31)  README.md(L100 - L108) 

Processing Modes

The system architecture enables flexible configuration processing through two complementary approaches that share the same core data model and validation logic.

CLI Processing Pipeline

The CLI mode implements external file processing for build-time configuration generation:

  • Input: Multiple TOML specification files via command-line arguments
  • Processing: File-based merging using Config::merge() operations
  • Output: Generated .axconfig.toml or Rust constant files
  • Integration: Build script integration via file I/O

Macro Processing Pipeline

The macro mode implements compile-time configuration embedding:

  • Input: Inline TOML strings or file paths in macro invocations
  • Processing: Token stream manipulation using proc-macro2 infrastructure
  • Output: Rust constant definitions injected into the compilation AST
  • Integration: Direct compile-time constant availability

Shared Core Components

Both processing modes utilize the same underlying components:

  • Config struct for configuration representation
  • ConfigValue and ConfigType for type-safe value handling
  • toml_edit integration for TOML parsing and manipulation
  • Rust code generation logic for consistent output formatting

Sources: axconfig-gen/Cargo.toml(L15 - L17)  axconfig-macros/Cargo.toml(L18 - L22) 

Quick Start Guide

Relevant source files

This guide provides step-by-step instructions for immediately getting started with axconfig-gen. It covers the three primary usage patterns: CLI tool for file generation, library API for programmatic use, and procedural macros for compile-time configuration embedding.

For detailed CLI documentation, see Command Line Interface. For comprehensive macro usage patterns, see Macro Usage Patterns. For complete configuration format specification, see TOML Configuration Format.

Installation

Install axconfig-gen using Cargo:

cargo install axconfig-gen

For procedural macro support, add these dependencies to your Cargo.toml:

[dependencies]
axconfig-gen = "0.1"
axconfig-macros = "0.1"

CLI Quick Start

Basic File Generation

Create a configuration specification file myconfig.toml:

# Global configurations
arch = "x86_64"        # str
debug = true           # bool
version = 123          # uint

[kernel]
stack-size = 0x8000    # uint
task-limit = 64        # uint

Generate a processed configuration file:

axconfig-gen myconfig.toml -o .axconfig.toml -f toml

Generate Rust constants:

axconfig-gen myconfig.toml -o config.rs -f rust

CLI Workflow Diagram

flowchart TD
INPUT["TOML Filesmyconfig.tomldefconfig.toml"]
CLI["axconfig-gen CLI--output--fmt"]
TOML_OUT["Generated TOML.axconfig.toml"]
RUST_OUT["Generated Rustconfig.rs"]
BUILD["Build System"]

CLI --> RUST_OUT
CLI --> TOML_OUT
INPUT --> CLI
RUST_OUT --> BUILD
TOML_OUT --> BUILD

Sources: README.md(L8 - L34)  README.md(L55 - L65) 

Macro Quick Start

Inline Configuration Processing

Use parse_configs! for inline TOML processing:

use axconfig_macros::parse_configs;

parse_configs!(r#"
debug = true           # bool
port = 8080           # uint
name = "my-app"       # str

[database]
url = "localhost"     # str
pool-size = 10        # uint
"#);

// Generated constants are now available
println!("Debug mode: {}", DEBUG);
println!("Port: {}", PORT);
println!("DB URL: {}", database::URL);

File-Based Configuration Processing

Use include_configs! for external TOML files:

use axconfig_macros::include_configs;

// Include from file path
include_configs!("config/app.toml");

// Include using environment variable
include_configs!(path_env = "APP_CONFIG_PATH");

// Include with fallback
include_configs!(
    path_env = "APP_CONFIG_PATH", 
    fallback = "config/default.toml"
);

Macro Processing Flow

flowchart TD
INLINE["parse_configs!Inline TOML string"]
FILE["include_configs!External TOML file"]
ENV["Environment VariablesPath resolution"]
PARSE["Config::from_toml()"]
VALIDATE["Type validationConfigType system"]
GENERATE["Code generationpub const definitions"]
COMPILE["Compile-time constantsEmbedded in binary"]

ENV --> FILE
FILE --> PARSE
GENERATE --> COMPILE
INLINE --> PARSE
PARSE --> VALIDATE
VALIDATE --> GENERATE

Sources: README.md(L67 - L108)  README.md(L39 - L65) 

Understanding Configuration Types

Type Annotation System

Configuration values require type annotations in comments for Rust code generation:

Type AnnotationRust TypeExample
# boolbooldebug = true # bool
# intisizeoffset = -10 # int
# uintusizesize = 1024 # uint
# str&strname = "test" # str
# [uint]&[usize]ports = [80, 443] # [uint]
# (uint, str)(usize, &str)pair = [8080, "http"] # (uint, str)

Configuration Structure

flowchart TD
CONFIG["Config structGlobal + Named tables"]
GLOBAL["Global tableTop-level key-value pairs"]
TABLES["Named tables[section] groups"]
ITEMS["ConfigItemKey-value pairs"]
VALUES["ConfigValueTyped values"]
TYPES["ConfigTypeType annotations"]

CONFIG --> GLOBAL
CONFIG --> TABLES
GLOBAL --> ITEMS
ITEMS --> VALUES
TABLES --> ITEMS
VALUES --> TYPES

Sources: README.md(L35 - L36)  example-configs/defconfig.toml(L1 - L63) 

Common Usage Patterns

Development Workflow

  1. Create configuration specification with type annotations
  2. Test with CLI tool to verify output format
  3. Integrate with build system using CLI or macros
  4. Access constants in your Rust code

CLI Integration Example

# Generate configuration for build
axconfig-gen platform/x86_64.toml kernel/debug.toml -o .axconfig.toml

# Generate Rust constants
axconfig-gen .axconfig.toml -o src/config.rs -f rust

Macro Integration Example

// In your Rust project
axconfig_macros::include_configs!(
    path_env = "AXCONFIG_PATH",
    fallback = "configs/defconfig.toml"
);

// Use generated constants
fn main() {
    println!("Architecture: {}", ARCH);
    println!("SMP cores: {}", SMP);
    println!("Kernel stack size: {}", kernel::TASK_STACK_SIZE);
}

Output Format Comparison

Input TOMLGenerated TOMLGenerated Rust
debug = true # booldebug = truepub const DEBUG: bool = true;
[kernel]size = 64 # uint[kernel]size = 64pub mod kernel {pub const SIZE: usize = 64;}

Sources: README.md(L55 - L65)  README.md(L89 - L98) 

Next Steps

axconfig-gen Package

Relevant source files

This document covers the axconfig-gen package, which provides both a command-line tool and a Rust library for TOML-based configuration generation in the ArceOS ecosystem. The package serves as the core processing engine for configuration management, offering type-safe conversion from TOML specifications to both TOML and Rust code outputs.

For procedural macro interfaces that build on this package, see axconfig-macros Package. For specific CLI usage patterns, see Command Line Interface. For programmatic API details, see Library API.

Package Architecture

The axconfig-gen package operates as a dual-purpose tool, providing both standalone CLI functionality and a library API for integration into other Rust applications. The package implements a sophisticated configuration processing pipeline that handles TOML parsing, type inference, validation, and code generation.

Core Components

flowchart TD
subgraph subGraph3["External Dependencies"]
    clap["clapCLI parsing"]
    toml_edit["toml_editTOML manipulation"]
end
subgraph subGraph2["Processing Core"]
    toml_parsing["TOML Parsingtoml_edit integration"]
    type_inference["Type InferenceComment-based types"]
    validation["Validationmerge() and update()"]
    code_generation["Code Generationdump() method"]
end
subgraph subGraph1["Library API"]
    Config["ConfigMain configuration container"]
    ConfigItem["ConfigItemIndividual config entries"]
    ConfigValue["ConfigValueTyped values"]
    ConfigType["ConfigTypeType system"]
    OutputFormat["OutputFormatToml | Rust"]
end
subgraph subGraph0["CLI Layer"]
    Args["Args structClap parser"]
    main_rs["main.rsCLI entry point"]
end

Args --> main_rs
Config --> ConfigItem
Config --> code_generation
Config --> toml_parsing
Config --> validation
ConfigItem --> ConfigValue
ConfigValue --> ConfigType
ConfigValue --> type_inference
code_generation --> OutputFormat
main_rs --> Config
main_rs --> ConfigValue
main_rs --> OutputFormat
main_rs --> clap
toml_parsing --> toml_edit

Sources: axconfig-gen/src/main.rs(L1 - L175)  axconfig-gen/README.md(L1 - L69) 

Processing Pipeline

flowchart TD
subgraph subGraph2["Output Generation"]
    Config_dump["Config::dump()Generate output"]
    file_output["File Output--output path"]
    stdout_output["Standard OutputDefault behavior"]
end
subgraph subGraph1["Core Processing"]
    Config_new["Config::new()Initialize container"]
    Config_from_toml["Config::from_toml()Parse TOML specs"]
    Config_merge["Config::merge()Combine specifications"]
    Config_update["Config::update()Apply old config values"]
    config_modification["CLI Read/WriteIndividual item access"]
end
subgraph subGraph0["Input Sources"]
    spec_files["Specification FilesTOML format"]
    oldconfig["Old Config FileOptional TOML"]
    cli_args["CLI Arguments--read, --write flags"]
end

Config_dump --> file_output
Config_dump --> stdout_output
Config_from_toml --> Config_merge
Config_merge --> Config_update
Config_new --> Config_merge
Config_update --> config_modification
cli_args --> config_modification
config_modification --> Config_dump
oldconfig --> Config_update
spec_files --> Config_from_toml

Sources: axconfig-gen/src/main.rs(L76 - L174) 

Key Capabilities

Configuration Management

The package provides comprehensive configuration management through the Config struct, which serves as the central container for all configuration data. The configuration system supports:

FeatureDescriptionImplementation
Specification LoadingLoad multiple TOML specification filesConfig::from_toml()andConfig::merge()
Value UpdatesApply existing configuration valuesConfig::update()method
Individual AccessRead/write specific configuration itemsconfig_at()andconfig_at_mut()
Type SafetyEnforce types through comments and inferenceConfigTypeandConfigValue
Global and ScopedSupport both global and table-scoped itemsGLOBAL_TABLE_NAMEconstant

CLI Interface

The command-line interface, implemented in axconfig-gen/src/main.rs(L5 - L40)  provides extensive functionality for configuration management:

flowchart TD
subgraph subGraph2["Output Operations"]
    format_selection["--fmt toml|rustChoose output format"]
    file_output["--output pathWrite to file"]
    backup_creation["Auto backup.old extension"]
end
subgraph subGraph0["Input Operations"]
    spec_loading["--spec filesLoad specifications"]
    oldconfig_loading["--oldconfig fileLoad existing config"]
    item_writing["--write table.key=valueSet individual items"]
end
subgraph subGraph1["Query Operations"]
    item_reading["--read table.keyGet individual items"]
    verbose_mode["--verboseDebug information"]
end

file_output --> backup_creation
format_selection --> file_output
item_reading --> verbose_mode
item_writing --> format_selection
oldconfig_loading --> format_selection
spec_loading --> format_selection

Sources: axconfig-gen/src/main.rs(L5 - L40)  axconfig-gen/README.md(L8 - L22) 

Type System Integration

The package implements a sophisticated type system that bridges TOML configuration with Rust type safety:

Type CategoryTOML Comment SyntaxGenerated Rust Type
Primitives# bool,# int,# uint,# strbool,isize,usize,&str
Collections# [type]&[type]
Tuples# (type1, type2, ...)(type1, type2, ...)
InferredNo commentAutomatic from value

Library API

The package exposes a clean programmatic interface for integration into other Rust applications. The core workflow follows this pattern:

  1. Configuration Creation: Initialize with Config::new() or Config::from_toml()
  2. Specification Merging: Combine multiple sources with Config::merge()
  3. Value Management: Update configurations with Config::update()
  4. Output Generation: Generate code with Config::dump(OutputFormat)

Integration Points

ArceOS Build System

The package integrates with the ArceOS build system through multiple pathways:

flowchart TD
subgraph subGraph2["Generated Artifacts"]
    toml_configs["TOML Configurations.axconfig.toml files"]
    rust_constants["Rust Constantspub const definitions"]
    module_structure["Module Structurepub mod organization"]
end
subgraph subGraph1["axconfig-gen Processing"]
    cli_execution["CLI Executionaxconfig-gen binary"]
    library_usage["Library UsageProgrammatic API"]
    spec_processing["Specification ProcessingTOML merging"]
end
subgraph subGraph0["Build Time Integration"]
    build_scripts["Build Scriptsbuild.rs files"]
    cargo_workspace["Cargo WorkspaceMulti-crate builds"]
    env_resolution["Environment VariablesPath resolution"]
end

build_scripts --> cli_execution
cargo_workspace --> library_usage
cli_execution --> toml_configs
env_resolution --> spec_processing
library_usage --> rust_constants
module_structure --> cargo_workspace
rust_constants --> cargo_workspace
spec_processing --> module_structure
toml_configs --> cargo_workspace

Sources: axconfig-gen/src/main.rs(L87 - L95)  axconfig-gen/README.md(L24 - L28) 

External Tool Integration

The package supports integration with external configuration management tools through its file-based interface and backup mechanisms implemented in axconfig-gen/src/main.rs(L155 - L170)  The automatic backup system ensures configuration history preservation during updates.

Sources: axconfig-gen/src/main.rs(L155 - L170)  axconfig-gen/README.md(L34 - L62) 

Command Line Interface

Relevant source files

This document provides comprehensive documentation for the axconfig-gen command-line interface (CLI) tool. The CLI enables users to process TOML configuration files, merge specifications, update configurations, and generate output in various formats for the ArceOS operating system.

For information about using axconfig-gen as a Rust library, see Library API. For compile-time configuration processing using procedural macros, see axconfig-macros Package.

CLI Architecture Overview

The CLI tool is built around a structured argument parsing system that processes configuration specifications through a multi-stage pipeline.

CLI Argument Processing Architecture

flowchart TD
subgraph subGraph3["Output Generation"]
    CONFIG_DUMP["config.dump()"]
    FILE_WRITE["std::fs::write()"]
    STDOUT["println!()"]
end
subgraph subGraph2["Configuration Operations"]
    CONFIG_NEW["Config::new()"]
    CONFIG_FROM["Config::from_toml()"]
    CONFIG_MERGE["config.merge()"]
    CONFIG_UPDATE["config.update()"]
    CONFIG_AT["config.config_at()"]
    CONFIG_AT_MUT["config.config_at_mut()"]
end
subgraph subGraph1["Input Processing"]
    PARSE_READ["parse_config_read_arg()"]
    PARSE_WRITE["parse_config_write_arg()"]
    FILE_READ["std::fs::read_to_string()"]
end
subgraph clap::Parser["clap::Parser"]
    ARGS["Args struct"]
    SPEC["spec: Vec"]
    OLD["oldconfig: Option"]
    OUT["output: Option"]
    FMT["fmt: OutputFormat"]
    READ["read: Vec"]
    WRITE["write: Vec"]
    VERB["verbose: bool"]
end

ARGS --> FMT
ARGS --> OLD
ARGS --> OUT
ARGS --> READ
ARGS --> SPEC
ARGS --> VERB
ARGS --> WRITE
CONFIG_DUMP --> FILE_WRITE
CONFIG_DUMP --> STDOUT
CONFIG_FROM --> CONFIG_MERGE
CONFIG_FROM --> CONFIG_NEW
CONFIG_FROM --> CONFIG_UPDATE
FILE_READ --> CONFIG_FROM
FMT --> CONFIG_DUMP
OLD --> FILE_READ
OUT --> FILE_WRITE
PARSE_READ --> CONFIG_AT
PARSE_WRITE --> CONFIG_AT_MUT
READ --> PARSE_READ
SPEC --> FILE_READ
WRITE --> PARSE_WRITE

Sources: axconfig-gen/src/main.rs(L5 - L40)  axconfig-gen/src/main.rs(L76 - L174) 

Command Line Arguments

The CLI accepts multiple arguments that control different aspects of configuration processing:

ArgumentShortTypeRequiredDescription
...-VecYesPaths to configuration specification files
--oldconfig-cOptionNoPath to existing configuration file for updates
--output-oOptionNoOutput file path (stdout if not specified)
--fmt-fOutputFormatNoOutput format:tomlorrust(default:toml)
--read-rVecNoRead config items with formattable.key
--write-wVecNoWrite config items with formattable.key=value
--verbose-vboolNoEnable verbose debug output

Format Specification

The --fmt argument accepts two values processed by a PossibleValuesParser:

toml  - Generate TOML configuration output
rust  - Generate Rust constant definitions

Sources: axconfig-gen/src/main.rs(L7 - L40)  axconfig-gen/README.md(L8 - L22) 

Configuration Item Addressing

Configuration items use a dot-notation addressing system implemented in parsing functions:

Configuration Item Parsing Flow


Sources: axconfig-gen/src/main.rs(L42 - L62)  axconfig-gen/src/main.rs(L136 - L147) 

Usage Patterns

Basic Configuration Generation

Generate a configuration file from specifications:

axconfig-gen spec1.toml spec2.toml -o .axconfig.toml -f toml

This pattern loads multiple specification files, merges them using Config::merge(), and outputs the result.

Configuration Updates

Update an existing configuration with new specifications:

axconfig-gen defconfig.toml -c existing.toml -o updated.toml

The update process uses Config::update() which returns untouched and extra items for warning generation.

Runtime Configuration Queries

Read specific configuration values:

axconfig-gen defconfig.toml -r kernel.heap_size -r features.smp

Write configuration values:

axconfig-gen defconfig.toml -w kernel.heap_size=0x100000 -w features.smp=true

Rust Code Generation

Generate Rust constant definitions:

axconfig-gen defconfig.toml -f rust -o config.rs

Sources: axconfig-gen/README.md(L24 - L28)  axconfig-gen/src/main.rs(L87 - L95)  axconfig-gen/src/main.rs(L97 - L117) 

Configuration Processing Pipeline

The CLI implements a sequential processing pipeline that handles merging, updating, and querying operations:

Configuration Processing Flow

flowchart TD
subgraph subGraph5["Output Generation"]
    DUMP["config.dump(args.fmt)"]
    OUTPUT_CHECK["if args.output.is_some()"]
    BACKUP["create .old backup"]
    WRITE_FILE["std::fs::write(path, output)"]
    WRITE_STDOUT["println!(output)"]
end
subgraph subGraph4["Read Operations"]
    READ_LOOP["for arg in args.read"]
    PARSE_READ_ARG["parse_config_read_arg(arg)"]
    CONFIG_AT["config.config_at(table, key)"]
    PRINT_VALUE["println!(item.value().to_toml_value())"]
    EARLY_RETURN["return if read mode"]
end
subgraph subGraph3["Write Operations"]
    WRITE_LOOP["for arg in args.write"]
    PARSE_WRITE_ARG["parse_config_write_arg(arg)"]
    CONFIG_AT_MUT["config.config_at_mut(table, key)"]
    VALUE_UPDATE["item.value_mut().update(new_value)"]
end
subgraph subGraph2["Old Config Processing"]
    OLD_CHECK["if args.oldconfig.is_some()"]
    READ_OLD["std::fs::read_to_string(oldconfig)"]
    OLD_FROM_TOML["Config::from_toml(oldconfig_toml)"]
    UPDATE["config.update(oldconfig)"]
    WARN_UNTOUCHED["warn untouched items"]
    WARN_EXTRA["warn extra items"]
end
subgraph subGraph1["Specification Loading"]
    SPEC_LOOP["for spec in args.spec"]
    READ_SPEC["std::fs::read_to_string(spec)"]
    FROM_TOML["Config::from_toml(spec_toml)"]
    MERGE["config.merge(sub_config)"]
end
subgraph Initialization["Initialization"]
    CONFIG_NEW["Config::new()"]
end

BACKUP --> WRITE_FILE
CONFIG_AT --> PRINT_VALUE
CONFIG_AT_MUT --> VALUE_UPDATE
CONFIG_NEW --> SPEC_LOOP
DUMP --> OUTPUT_CHECK
EARLY_RETURN --> DUMP
FROM_TOML --> MERGE
MERGE --> OLD_CHECK
MERGE --> SPEC_LOOP
OLD_CHECK --> READ_OLD
OLD_CHECK --> WRITE_LOOP
OLD_FROM_TOML --> UPDATE
OUTPUT_CHECK --> BACKUP
OUTPUT_CHECK --> WRITE_STDOUT
PARSE_READ_ARG --> CONFIG_AT
PARSE_WRITE_ARG --> CONFIG_AT_MUT
PRINT_VALUE --> READ_LOOP
READ_LOOP --> EARLY_RETURN
READ_LOOP --> PARSE_READ_ARG
READ_OLD --> OLD_FROM_TOML
READ_SPEC --> FROM_TOML
SPEC_LOOP --> READ_SPEC
UPDATE --> WARN_EXTRA
UPDATE --> WARN_UNTOUCHED
VALUE_UPDATE --> WRITE_LOOP
WARN_EXTRA --> WRITE_LOOP
WRITE_LOOP --> DUMP
WRITE_LOOP --> PARSE_WRITE_ARG
WRITE_LOOP --> READ_LOOP

Sources: axconfig-gen/src/main.rs(L87 - L174) 

Error Handling and Debugging

The CLI implements comprehensive error handling with a custom unwrap! macro that provides clean error messages and exits gracefully:

Error Handling Mechanism

  • File reading errors display the problematic file path
  • TOML parsing errors show detailed syntax information
  • Config merging conflicts are reported with item names
  • Missing configuration items generate helpful error messages

Verbose Mode

When --verbose is enabled, the CLI outputs debug information for:

  • Configuration specification loading
  • Old configuration processing
  • Individual config item operations
  • Output generation decisions

The verbose output uses a local debug! macro that conditionally prints to stderr based on the args.verbose flag.

Sources: axconfig-gen/src/main.rs(L64 - L74)  axconfig-gen/src/main.rs(L79 - L85)  axconfig-gen/src/main.rs(L89 - L90) 

File Backup System

When writing to an existing output file, the CLI automatically creates backup files to prevent data loss:

  • Backup files use the .old extension (e.g., config.tomlconfig.old.toml)
  • Backups are only created if the new output differs from the existing file
  • The comparison prevents unnecessary writes when output is identical

This backup mechanism ensures safe configuration updates while avoiding redundant file operations.

Sources: axconfig-gen/src/main.rs(L156 - L169) 

Library API

Relevant source files

This document covers the programmatic interface for using axconfig-gen as a Rust library. The library API allows developers to programmatically parse TOML configurations, manipulate configuration data, and generate output in various formats within their own Rust applications. For information about using axconfig-gen as a command-line tool, see Command Line Interface. For details about the internal data structures, see Core Data Structures.

API Overview

The axconfig-gen library provides a high-level API centered around the Config type, which manages configuration data parsed from TOML files. The library handles TOML parsing, type inference, validation, and output generation through a clean programmatic interface.


Sources: axconfig-gen/src/lib.rs(L13 - L16)  axconfig-gen/src/config.rs(L9 - L18)  axconfig-gen/src/config.rs(L95 - L100) 

Basic Usage Pattern

The fundamental workflow for using the library API involves parsing TOML input, optionally manipulating the configuration, and generating output in the desired format.

flowchart TD
subgraph subGraph0["Optional Operations"]
    Merge["config.merge(other)"]
    Update["config.update(other)"]
    Access["config.config_at(table, key)"]
end
TomlInput["TOML String Input"]
ParseStep["Config::from_toml(toml_str)"]
ConfigObject["Config Object"]
OutputStep["config.dump(OutputFormat)"]
Result["Generated String Output"]

ConfigObject --> Access
ConfigObject --> Merge
ConfigObject --> OutputStep
ConfigObject --> Update
Merge --> ConfigObject
OutputStep --> Result
ParseStep --> ConfigObject
TomlInput --> ParseStep
Update --> ConfigObject

The library usage example from the README demonstrates this pattern:

OperationMethodPurpose
Parse TOMLConfig::from_toml()Convert TOML string to Config object
Generate Outputconfig.dump()Convert Config to TOML or Rust code
Access Dataconfig.config_at()Retrieve specific configuration items
Combine Configsconfig.merge()Merge multiple configurations
Update Valuesconfig.update()Update existing configuration values

Sources: axconfig-gen/README.md(L36 - L62)  axconfig-gen/src/config.rs(L200 - L236)  axconfig-gen/src/config.rs(L238 - L265) 

Configuration Management

The Config type provides methods for managing configuration data at both the table and item level. The configuration is organized into a global table and named tables, following TOML's structure.

Table Operations

MethodReturn TypeDescription
global_table()&BTreeMap<String, ConfigItem>Access global configuration items
table_at(name)Option<&BTreeMap<String, ConfigItem>>Access named table by name
table_at_mut(name)Option<&mut BTreeMap<String, ConfigItem>>Mutable access to named table
table_comments_at(name)Option<&str>Get comments for a table

Item Access

MethodReturn TypeDescription
config_at(table, key)Option<&ConfigItem>Access specific configuration item
config_at_mut(table, key)Option<&mut ConfigItem>Mutable access to configuration item
iter()Iterator<Item = &ConfigItem>Iterate over all configuration items
table_iter()Iterator<Item = (&str, &ConfigTable, &str)>Iterate over all tables

Sources: axconfig-gen/src/config.rs(L135 - L196) 

Configuration Manipulation

The library provides two primary methods for combining and updating configurations: merge() and update(). These operations support different use cases for configuration management.

Merge Operation

The merge() method combines two configurations, requiring that no duplicate keys exist:


Update Operation

The update() method overwrites existing values and returns information about keys that were not matched:


Sources: axconfig-gen/src/config.rs(L267 - L284)  axconfig-gen/src/config.rs(L286 - L322) 

Output Generation

The library supports generating output in multiple formats through the OutputFormat enum and dump() method family.

Output Formats

FormatMethodDescription
TOMLdump_toml()Generate TOML configuration file
Rustdump_rs()Generate Rust constant definitions
Genericdump(OutputFormat)Generate output in specified format

Output Generation Process


The output generation process iterates through all tables and items, formatting them according to the specified output format. For TOML output, it preserves comments and structure. For Rust output, it generates pub const definitions with appropriate types.

Sources: axconfig-gen/src/config.rs(L238 - L265)  axconfig-gen/src/lib.rs(L14) 

Error Handling

The library uses a custom error type ConfigErr with a specialized result type ConfigResult<T>. All fallible operations return this result type for consistent error handling.

Error Types

VariantDescriptionCommon Causes
Parse(TomlError)TOML parsing failureInvalid TOML syntax
InvalidValueInvalid configuration valueUnsupported value types
InvalidTypeInvalid type annotationMalformed type comments
ValueTypeMismatchValue doesn't match specified typeType annotation conflicts
Other(String)General errorsDuplicate keys, reserved names

Error Handling Pattern


The error type implements both Display and Debug traits for easy error reporting, and automatically converts from TomlError for seamless integration with the underlying TOML parsing library.

Sources: axconfig-gen/src/lib.rs(L18 - L57) 

Core Data Structures

Relevant source files

This page provides an in-depth examination of the three fundamental data structures that form the backbone of the axconfig-gen configuration system: Config, ConfigItem, and ConfigValue. These structures work together to represent, validate, and manipulate TOML-based configurations throughout the parsing, processing, and output generation pipeline.

For information about the type system and how types are inferred and validated, see Type System. For details on how these structures are used in output generation, see Output Generation.

Structure Relationships

The core data structures form a hierarchical relationship where Config serves as the top-level container, ConfigItem represents individual configuration entries, and ConfigValue wraps the actual values with type information.


Sources: axconfig-gen/src/config.rs(L1 - L331)  axconfig-gen/src/value.rs(L1 - L289) 

Config Structure

The Config structure serves as the primary container for all configuration data, organizing items into a global table and named tables with associated comments.

Internal Organization

flowchart TD
subgraph NamedTableItems["Named Table Items"]
    GlobalItem2["ConfigItem { table_name: '$GLOBAL', key: 'SMP', ... }"]
    PlatformTable["'platform' table"]
    PlatformItem1["ConfigItem { table_name: 'platform', key: 'FAMILY', ... }"]
    PlatformItem2["ConfigItem { table_name: 'platform', key: 'ARCH', ... }"]
end
subgraph Config["Config Structure"]
    GlobalItem1["ConfigItem { table_name: '$GLOBAL', key: 'LOG_LEVEL', ... }"]
    GlobalTable["global: ConfigTable(BTreeMap<String, ConfigItem>)"]
    NamedTables["tables: BTreeMap<String, ConfigTable>"]
    TableComments["table_comments: BTreeMap<String, String>"]
end
subgraph GlobalItems["Global Configuration Items"]
    GlobalItem1["ConfigItem { table_name: '$GLOBAL', key: 'LOG_LEVEL', ... }"]
    GlobalItem2["ConfigItem { table_name: '$GLOBAL', key: 'SMP', ... }"]
    NamedTables["tables: BTreeMap<String, ConfigTable>"]
end

NamedTables --> PlatformTable
PlatformTable --> PlatformItem1
PlatformTable --> PlatformItem2

The Config structure maintains a clear separation between global configuration items and those organized within named tables. The constant GLOBAL_TABLE_NAME is set to "$GLOBAL" to distinguish global items from named table items.

Sources: axconfig-gen/src/config.rs(L91 - L113)  axconfig-gen/src/config.rs(L103 - L104) 

Key Methods and Operations

MethodPurposeReturn Type
new()Creates empty config instanceConfig
from_toml(toml: &str)Parses TOML string into configConfigResult
merge(other: &Config)Merges configurations, fails on duplicatesConfigResult<()>
update(other: &Config)Updates values, reports untouched/extra itemsConfigResult<(Vec, Vec)>
dump(fmt: OutputFormat)Generates output in specified formatConfigResult

The merge operation at axconfig-gen/src/config.rs(L268 - L284)  strictly prevents duplicate keys, while update at axconfig-gen/src/config.rs(L292 - L322)  allows value updates and reports which items were not updated.

Sources: axconfig-gen/src/config.rs(L106 - L322) 

ConfigItem Structure

The ConfigItem structure represents individual configuration entries, encapsulating the hierarchical context (table name), the key identifier, the typed value, and associated documentation comments.

Data Flow and Construction

flowchart TD
TOMLTable["TOML Table with Key-Value Pairs"]
TOMLValue["TOML Value with Decorations"]
NewMethod["ConfigItem::new(table_name, table, key, value)"]
NewGlobalMethod["ConfigItem::new_global(table, key, value)"]
CommentExtraction["Comment Extractionprefix_comments() + suffix_comments()"]
TypeInference["Type Inference from CommentsConfigType::new() if suffix exists"]
ValueWrapping["ConfigValue Creationfrom_raw_value_type() or from_raw_value()"]
ConfigItemInstance["ConfigItem {table_name: String,key: String,value: ConfigValue,comments: String}"]

CommentExtraction --> TypeInference
NewGlobalMethod --> ConfigItemInstance
NewMethod --> ConfigItemInstance
TOMLTable --> NewGlobalMethod
TOMLTable --> NewMethod
TOMLValue --> CommentExtraction
TypeInference --> ValueWrapping
ValueWrapping --> ConfigItemInstance

The construction process extracts type annotations from trailing comments (suffix comments starting with #) and preserves documentation from prefix comments for later output generation.

Sources: axconfig-gen/src/config.rs(L20 - L51)  axconfig-gen/src/config.rs(L324 - L330) 

Naming and Identification

The item_name() method at axconfig-gen/src/config.rs(L57 - L63)  provides a unique identifier for each configuration item:

  • Global items: Returns just the key (e.g., "SMP")
  • Named table items: Returns "table.key" format (e.g., "platform.ARCH")

This naming scheme enables unambiguous reference to any configuration item within the system.

Sources: axconfig-gen/src/config.rs(L53 - L89) 

ConfigValue Structure

The ConfigValue structure wraps TOML values with optional type information, providing the foundation for type validation and code generation throughout the system.

Value and Type Relationship


The dual representation allows for both dynamically inferred types (when no annotation is provided) and explicitly declared types (from TOML comment annotations).

Sources: axconfig-gen/src/value.rs(L7 - L13)  axconfig-gen/src/value.rs(L82 - L90) 

Construction Patterns

ConstructorPurposeType Information
new(s: &str)Parse TOML string, infer typeNo explicit type
new_with_type(s: &str, ty: &str)Parse with explicit type validationExplicit type provided
from_raw_value(value: &Value)Wrap existing TOML valueNo explicit type
from_raw_value_type(value: &Value, ty: ConfigType)Wrap with type validationExplicit type provided

The validation logic at axconfig-gen/src/value.rs(L142 - L175)  ensures that values match their declared types, with special handling for numeric strings that can represent multiple types.

Sources: axconfig-gen/src/value.rs(L14 - L50) 

Value Update Semantics

The update method at axconfig-gen/src/value.rs(L57 - L80)  implements type-safe value updates with the following rules:

  1. Both have explicit types: Types must match exactly
  2. Current has type, new doesn't: New value must be compatible with current type
  3. Current has no type, new has type: Current value must be compatible with new type
  4. Neither has explicit type: No type constraint, update proceeds

This flexible approach allows for gradual type refinement while maintaining type safety where types are explicitly declared.

Sources: axconfig-gen/src/value.rs(L57 - L80) 

Integration with TOML Processing

The core data structures integrate seamlessly with the toml_edit crate to preserve formatting, comments, and metadata during parsing and generation.

TOML Parsing Pipeline

flowchart TD
TOMLInput["TOML Input String"]
DocumentMut["toml_edit::DocumentMut"]
TableIteration["Iterate over top-level items"]
ValueItems["Item::Value → Global ConfigItem"]
TableItems["Item::Table → Named table ConfigItems"]
CommentPreservation["Preserve prefix/suffix commentsfrom toml_edit::Decor"]
TypeExtraction["Extract type annotationsfrom suffix comments"]
ConfigStructure["Populated Config Structure"]

CommentPreservation --> TypeExtraction
DocumentMut --> TableIteration
TOMLInput --> DocumentMut
TableItems --> CommentPreservation
TableIteration --> TableItems
TableIteration --> ValueItems
TypeExtraction --> ConfigStructure
ValueItems --> CommentPreservation

The parsing implementation at axconfig-gen/src/config.rs(L200 - L236)  carefully handles the distinction between global values, named tables, and validates that object arrays ([[table]] syntax) are not supported in the current implementation.

Sources: axconfig-gen/src/config.rs(L200 - L236)  axconfig-gen/src/config.rs(L324 - L330) 

Type System

Relevant source files

Purpose and Scope

The Type System is the core component responsible for type inference, validation, and code generation in the axconfig-gen library. It provides mechanisms to parse type annotations from strings, infer types from TOML values, validate type compatibility, and generate appropriate Rust type declarations for configuration values.

For information about how types are used in configuration data structures, see Core Data Structures. For details on how the type system integrates with output generation, see Output Generation.

ConfigType Enumeration

The foundation of the type system is the ConfigType enum, which represents all supported configuration value types in the system.

flowchart TD
ConfigType["ConfigType enum"]
Bool["Bool: boolean values"]
Int["Int: signed integers"]
Uint["Uint: unsigned integers"]
String["String: string values"]
Tuple["Tuple(Vec<ConfigType>): fixed-length heterogeneous sequences"]
Array["Array(Box<ConfigType>): variable-length homogeneous sequences"]
Unknown["Unknown: type to be inferred"]
TupleExample["Example: (int, str, bool)"]
ArrayExample["Example: [uint] or [[str]]"]

Array --> ArrayExample
ConfigType --> Array
ConfigType --> Bool
ConfigType --> Int
ConfigType --> String
ConfigType --> Tuple
ConfigType --> Uint
ConfigType --> Unknown
Tuple --> TupleExample

Supported Type Categories

Type CategoryConfigType VariantRust EquivalentTOML Representation
BooleanBoolbooltrue,false
Signed IntegerIntisize-123,0x80
Unsigned IntegerUintusize123,0xff
StringString&str"hello"
TupleTuple(Vec)(T1, T2, ...)[val1, val2, ...]
ArrayArray(Box)&[T][val1, val2, ...]
UnknownUnknownN/AUsed for inference

Sources: axconfig-gen/src/ty.rs(L4 - L22) 

Type Parsing and String Representation

The type system supports parsing type specifications from string format, enabling type annotations in TOML comments and explicit type declarations.

flowchart TD
TypeString["Type String Input"]
Parser["ConfigType::new()"]
BasicTypes["Basic Typesbool, int, uint, str"]
TupleParser["Tuple Parser(type1, type2, ...)"]
ArrayParser["Array Parser[element_type]"]
TupleSplitter["split_tuple_items()Handle nested parentheses"]
ElementType["Recursive parsingfor element type"]
ConfigTypeResult["ConfigType instance"]
DisplayFormat["Display traitHuman-readable output"]
RustType["to_rust_type()Rust code generation"]

ArrayParser --> ElementType
BasicTypes --> ConfigTypeResult
ConfigTypeResult --> DisplayFormat
ConfigTypeResult --> RustType
ElementType --> ConfigTypeResult
Parser --> ArrayParser
Parser --> BasicTypes
Parser --> TupleParser
TupleParser --> TupleSplitter
TupleSplitter --> ConfigTypeResult
TypeString --> Parser

Type String Examples

Input StringParsed TypeRust Output
"bool"ConfigType::Bool"bool"
"[uint]"ConfigType::Array(Box::new(ConfigType::Uint))"&[usize]"
"(int, str)"ConfigType::Tuple(vec![ConfigType::Int, ConfigType::String])"(isize, &str)"
"[[bool]]"ConfigType::Array(Box::new(ConfigType::Array(...)))"&[&[bool]]"

Sources: axconfig-gen/src/ty.rs(L24 - L61)  axconfig-gen/src/ty.rs(L83 - L111)  axconfig-gen/src/ty.rs(L113 - L134) 

Type Inference System

The type inference system automatically determines types from TOML values, supporting both simple and complex nested structures.

flowchart TD
TOMLValue["TOML Value"]
BooleanValue["Boolean Value"]
IntegerValue["Integer Value"]
StringValue["String Value"]
ArrayValue["Array Value"]
BoolType["ConfigType::Bool"]
IntegerCheck["Check sign"]
NegativeInt["< 0: ConfigType::Int"]
PositiveInt["Unsupported markdown: blockquote"]
NumericString["is_num() check"]
StringAsUint["Numeric: ConfigType::Uint"]
StringAsStr["Non-numeric: ConfigType::String"]
InferElements["Infer element types"]
AllSame["All elements same type?"]
ArrayResult["Yes: ConfigType::Array"]
TupleResult["No: ConfigType::Tuple"]
UnknownResult["Empty/Unknown: ConfigType::Unknown"]

AllSame --> ArrayResult
AllSame --> TupleResult
AllSame --> UnknownResult
ArrayValue --> InferElements
BooleanValue --> BoolType
InferElements --> AllSame
IntegerCheck --> NegativeInt
IntegerCheck --> PositiveInt
IntegerValue --> IntegerCheck
NumericString --> StringAsStr
NumericString --> StringAsUint
StringValue --> NumericString
TOMLValue --> ArrayValue
TOMLValue --> BooleanValue
TOMLValue --> IntegerValue
TOMLValue --> StringValue

Inference Rules

  • Integers: Negative values infer as Int, non-negative as Uint
  • Strings: Numeric strings (hex, binary, octal, decimal) infer as Uint, others as String
  • Arrays: Homogeneous arrays become Array, heterogeneous become Tuple
  • Nested Arrays: Recursively infer element types

Sources: axconfig-gen/src/value.rs(L177 - L224)  axconfig-gen/src/value.rs(L114 - L125) 

Type Validation and Matching

The type system provides comprehensive validation to ensure values conform to expected types, with special handling for string-encoded numbers and flexible integer types.

flowchart TD
ValueTypeMatch["value_type_matches()"]
BoolMatch["Boolean vs Bool"]
IntMatch["Integer vs Int/Uint"]
StringMatch["String value analysis"]
ArrayMatch["Array structure validation"]
StringNumeric["is_num() check"]
StringAsNumber["Numeric stringmatches Int/Uint/String"]
StringLiteral["Non-numeric stringmatches String only"]
TupleValidation["Array vs TupleLength and element validation"]
ArrayValidation["Array vs ArrayElement type validation"]
Elementwise["Check each elementagainst corresponding type"]
Homogeneous["Check all elementsagainst element type"]

ArrayMatch --> ArrayValidation
ArrayMatch --> TupleValidation
ArrayValidation --> Homogeneous
StringMatch --> StringNumeric
StringNumeric --> StringAsNumber
StringNumeric --> StringLiteral
TupleValidation --> Elementwise
ValueTypeMatch --> ArrayMatch
ValueTypeMatch --> BoolMatch
ValueTypeMatch --> IntMatch
ValueTypeMatch --> StringMatch

Type Compatibility Matrix

TOML ValueBoolIntUintStringNotes
true/falseExact match only
123Integers match both Int/Uint
-123Negative still matches Uint
"123"Numeric strings are flexible
"abc"Non-numeric strings

Sources: axconfig-gen/src/value.rs(L142 - L175)  axconfig-gen/src/value.rs(L58 - L80) 

Integration with ConfigValue

The type system integrates closely with ConfigValue to provide type-safe configuration value management.

flowchart TD
ConfigValue["ConfigValue struct"]
Value["value: toml_edit::Value"]
OptionalType["ty: Option<ConfigType>"]
ExplicitType["Explicit typefrom constructor"]
InferredType["Inferred typefrom value analysis"]
NoType["No typepure value storage"]
TypeMethods["Type-related methods"]
InferredTypeMethod["inferred_type()Get computed type"]
TypeMatchesMethod["type_matches()Validate against type"]
UpdateMethod["update()Type-safe value updates"]
ToRustMethod["to_rust_value()Generate Rust code"]

ConfigValue --> OptionalType
ConfigValue --> TypeMethods
ConfigValue --> Value
OptionalType --> ExplicitType
OptionalType --> InferredType
OptionalType --> NoType
TypeMethods --> InferredTypeMethod
TypeMethods --> ToRustMethod
TypeMethods --> TypeMatchesMethod
TypeMethods --> UpdateMethod

ConfigValue Type Operations

  • Construction: Values can be created with or without explicit types
  • Inference: Types can be computed from TOML values automatically
  • Validation: Updates are validated against existing or specified types
  • Code Generation: Type information drives Rust code output format

Sources: axconfig-gen/src/value.rs(L7 - L12)  axconfig-gen/src/value.rs(L52 - L103) 

Rust Code Generation

The type system drives generation of appropriate Rust type declarations and value literals for compile-time configuration constants.

flowchart TD
TypeToRust["Type → Rust Mapping"]
BasicMapping["Basic Type Mapping"]
ComplexMapping["Complex Type Mapping"]
BoolToRust["Bool → bool"]
IntToRust["Int → isize"]
UintToRust["Uint → usize"]
StringToRust["String → &str"]
TupleToRust["Tuple → (T1, T2, ...)"]
ArrayToRust["Array → &[T]"]
TupleElements["Recursive element mapping"]
ArrayElement["Element type mapping"]
ValueToRust["Value → Rust Code"]
LiteralMapping["Literal Value Mapping"]
StructureMapping["Structure Mapping"]
BoolLiteral["true/false"]
NumericLiteral["Integer literals"]
StringLiteral["String literals"]
TupleStructure["(val1, val2, ...)"]
ArrayStructure["&[val1, val2, ...]"]

ArrayToRust --> ArrayElement
BasicMapping --> BoolToRust
BasicMapping --> IntToRust
BasicMapping --> StringToRust
BasicMapping --> UintToRust
ComplexMapping --> ArrayToRust
ComplexMapping --> TupleToRust
LiteralMapping --> BoolLiteral
LiteralMapping --> NumericLiteral
LiteralMapping --> StringLiteral
StructureMapping --> ArrayStructure
StructureMapping --> TupleStructure
TupleToRust --> TupleElements
TypeToRust --> BasicMapping
TypeToRust --> ComplexMapping
ValueToRust --> LiteralMapping
ValueToRust --> StructureMapping

Rust Generation Examples

ConfigTypeRust TypeExample ValueGenerated Rust
Boolbooltruetrue
Uintusize"0xff"0xff
Array(String)&[&str]["a", "b"]&["a", "b"]
Tuple([Uint, String])(usize, &str)[123, "test"](123, "test")

Sources: axconfig-gen/src/ty.rs(L62 - L81)  axconfig-gen/src/value.rs(L243 - L288) 

Output Generation

Relevant source files

This section covers the output generation system in axconfig-gen, which converts parsed configuration structures into formatted output strings. The system supports generating both TOML files and Rust source code from configuration data, handling proper formatting, type annotations, and structural organization.

For information about the core data structures being formatted, see Core Data Structures. For details about the type system that drives code generation, see Type System.

Purpose and Scope

The output generation system serves as the final stage of the configuration processing pipeline, transforming internal Config, ConfigItem, and ConfigValue structures into human-readable and machine-consumable formats. It handles:

  • Format Selection: Supporting TOML and Rust code output formats
  • Structural Organization: Managing tables, modules, and hierarchical output
  • Type-Aware Generation: Converting values according to their configured types
  • Formatting and Indentation: Ensuring readable, properly formatted output
  • Comment Preservation: Maintaining documentation and type annotations

Output Format Architecture

The system centers around the OutputFormat enum and Output writer, which coordinate the transformation process.

Output Format Selection

flowchart TD
subgraph subGraph2["Generation Methods"]
    TABLE_BEGIN["table_begin()"]
    TABLE_END["table_end()"]
    WRITE_ITEM["write_item()"]
    PRINT_LINES["print_lines()"]
end
subgraph subGraph1["Writer System"]
    OUTPUT["Output"]
    WRITER_STATE["writer statefmt: OutputFormatindent: usizeresult: String"]
end
subgraph subGraph0["Format Selection"]
    OF["OutputFormat"]
    TOML_FMT["OutputFormat::Toml"]
    RUST_FMT["OutputFormat::Rust"]
end
TOML_OUT["[table]key = value # type"]
RUST_OUT["pub mod table {pub const KEY: Type = value;}"]

OF --> OUTPUT
OF --> RUST_FMT
OF --> TOML_FMT
OUTPUT --> PRINT_LINES
OUTPUT --> TABLE_BEGIN
OUTPUT --> TABLE_END
OUTPUT --> WRITER_STATE
OUTPUT --> WRITE_ITEM
RUST_FMT --> RUST_OUT
TOML_FMT --> TOML_OUT

Sources: axconfig-gen/src/output.rs(L3 - L32)  axconfig-gen/src/output.rs(L34 - L48) 

Value Conversion Pipeline

The conversion from internal values to output strings follows a type-aware transformation process.

Value to Output Conversion Flow


Sources: axconfig-gen/src/value.rs(L92 - L102)  axconfig-gen/src/value.rs(L226 - L241)  axconfig-gen/src/value.rs(L243 - L288) 

Output Writer Implementation

The Output struct manages the formatting state and provides methods for building structured output.

Core Writer Methods

MethodPurposeTOML BehaviorRust Behavior
table_begin()Start a configuration sectionWrites[table-name]headerCreatespub mod table_name {
table_end()End a configuration sectionNo action neededWrites closing}
write_item()Output a key-value pairkey = value # typepub const KEY: Type = value;
print_lines()Handle multi-line commentsPreserves#commentsConverts to///doc comments

Sources: axconfig-gen/src/output.rs(L71 - L93)  axconfig-gen/src/output.rs(L95 - L136) 

Formatting and Indentation

The system maintains proper indentation for nested structures:

flowchart TD
subgraph subGraph1["Rust Module Nesting"]
    MOD_BEGIN["table_begin() → indent += 4"]
    MOD_END["table_end() → indent -= 4"]
    CONST_WRITE["write_item() uses current indent"]
end
subgraph subGraph0["Indentation Management"]
    INDENT_STATE["indent: usize"]
    PRINTLN_FMT["println_fmt()"]
    SPACES["format!('{:indent$}', '')"]
end

CONST_WRITE --> INDENT_STATE
INDENT_STATE --> PRINTLN_FMT
MOD_BEGIN --> INDENT_STATE
MOD_END --> INDENT_STATE
PRINTLN_FMT --> SPACES

Sources: axconfig-gen/src/output.rs(L54 - L60)  axconfig-gen/src/output.rs(L82 - L84)  axconfig-gen/src/output.rs(L89 - L92) 

Type-Specific Value Generation

Different value types require specialized conversion logic for both TOML and Rust output formats.

Primitive Types

Value TypeTOML OutputRust Output
Booleantrue,falsetrue,false
Integer42,0xdead_beef42,0xdead_beef
String"hello""hello"
Numeric String"0xff"0xff(converted to number)

Sources: axconfig-gen/src/value.rs(L228 - L230)  axconfig-gen/src/value.rs(L245 - L255) 

Collection Types

Array Conversion Logic

flowchart TD
subgraph subGraph2["Rust Output"]
    SIMPLE_RUST["&[elem1, elem2, elem3]"]
    NESTED_RUST["&[elem1,elem2,]"]
end
subgraph subGraph1["TOML Output"]
    SIMPLE_TOML["[elem1, elem2, elem3]"]
    NESTED_TOML["[elem1,elem2]"]
end
subgraph subGraph0["Array Processing"]
    ARRAY_VAL["Value::Array"]
    CHECK_NESTED["contains nested arrays?"]
    ELEMENTS["convert each element"]
end

ARRAY_VAL --> CHECK_NESTED
CHECK_NESTED --> ELEMENTS
CHECK_NESTED --> NESTED_RUST
CHECK_NESTED --> NESTED_TOML
CHECK_NESTED --> SIMPLE_RUST
CHECK_NESTED --> SIMPLE_TOML
ELEMENTS --> NESTED_RUST
ELEMENTS --> NESTED_TOML
ELEMENTS --> SIMPLE_RUST
ELEMENTS --> SIMPLE_TOML

Sources: axconfig-gen/src/value.rs(L231 - L240)  axconfig-gen/src/value.rs(L267 - L285) 

Tuple vs Array Distinction

The system distinguishes between homogeneous arrays and heterogeneous tuples:

ConfigurationTypeTOML OutputRust Output
[1, 2, 3][uint][1, 2, 3]&[1, 2, 3]
[1, "a", true](uint, str, bool)[1, "a", true](1, "a", true)

Sources: axconfig-gen/src/value.rs(L256 - L265)  axconfig-gen/src/value.rs(L267 - L285) 

Name Transformation

The system applies consistent naming conventions when converting between formats:

Identifier Transformation Rules

flowchart TD
subgraph subGraph0["TOML to Rust Naming"]
    TOML_KEY["TOML key: 'some-config-key'"]
    MOD_TRANSFORM["mod_name(): replace '-' with '_'"]
    CONST_TRANSFORM["const_name(): UPPERCASE + replace '-' with '_'"]
    RUST_MOD["Rust module: 'some_config_key'"]
    RUST_CONST["Rust constant: 'SOME_CONFIG_KEY'"]
end

CONST_TRANSFORM --> RUST_CONST
MOD_TRANSFORM --> RUST_MOD
TOML_KEY --> CONST_TRANSFORM
TOML_KEY --> MOD_TRANSFORM

Sources: axconfig-gen/src/output.rs(L138 - L144) 

Error Handling in Output Generation

The system provides comprehensive error handling for output generation scenarios:

Error ConditionWhen It OccursError Type
Unknown type for keyType inference fails and no explicit typeConfigErr::Other
Value type mismatchValue doesn't match expected typeConfigErr::ValueTypeMismatch
Array length mismatchTuple and array length differConfigErr::ValueTypeMismatch

Sources: axconfig-gen/src/output.rs(L120 - L125)  axconfig-gen/src/value.rs(L257 - L259) 

The output generation system provides a robust foundation for transforming configuration data into multiple target formats while preserving type information and maintaining readable formatting conventions.

axconfig-macros Package

Relevant source files

This document covers the axconfig-macros procedural macro crate, which provides compile-time TOML configuration processing for the ArceOS ecosystem. The package transforms TOML configuration data into Rust constant definitions during compilation, enabling zero-cost configuration access at runtime.

For information about the CLI tool and library API that provides the core processing functionality, see axconfig-gen Package. For practical usage examples and TOML format specifications, see Configuration Examples.

Overview

The axconfig-macros package provides two procedural macros that convert TOML configuration data into Rust constants at compile time:

  • parse_configs! - Processes inline TOML strings
  • include_configs! - Reads and processes external TOML files

Both macros leverage the core processing functionality from axconfig-gen to parse TOML, infer types, and generate Rust code, but operate during compilation rather than as a separate build step.

Sources: axconfig-macros/README.md(L1 - L49)  axconfig-macros/src/lib.rs(L1 - L143) 

Macro Processing Architecture

flowchart TD
subgraph subGraph3["File System"]
    TOML_FILES["TOML Config Files"]
    ENV_VARS["Environment VariablesCARGO_MANIFEST_DIR, AX_CONFIG_PATH"]
    MANIFEST_DIR["Project RootPath resolution"]
end
subgraph subGraph2["Rust Compilation"]
    TOKEN_STREAM["TokenStreamGenerated code"]
    COMPILER_ERROR["compiler_errorError handling"]
    QUOTE_MACRO["quote!Code generation"]
end
subgraph subGraph1["Core Integration"]
    CONFIG_FROM_TOML["Config::from_tomlTOML parsing"]
    CONFIG_DUMP["Config::dumpOutputFormat::Rust"]
    OUTPUT_FORMAT["OutputFormat::RustCode generation"]
end
subgraph subGraph0["Compile-Time Processing"]
    PARSE_MACRO["parse_configs!Inline TOML"]
    INCLUDE_MACRO["include_configs!File-based TOML"]
    ARGS_PARSER["IncludeConfigsArgsArgument parsing"]
end

ARGS_PARSER --> ENV_VARS
ARGS_PARSER --> TOML_FILES
COMPILER_ERROR --> TOKEN_STREAM
CONFIG_DUMP --> OUTPUT_FORMAT
CONFIG_FROM_TOML --> CONFIG_DUMP
INCLUDE_MACRO --> ARGS_PARSER
INCLUDE_MACRO --> MANIFEST_DIR
OUTPUT_FORMAT --> TOKEN_STREAM
PARSE_MACRO --> CONFIG_FROM_TOML
TOKEN_STREAM --> QUOTE_MACRO
TOML_FILES --> CONFIG_FROM_TOML

This diagram shows how the procedural macros integrate with the axconfig-gen core processing pipeline and the Rust compilation system.

Sources: axconfig-macros/src/lib.rs(L22 - L41)  axconfig-macros/src/lib.rs(L58 - L87)  axconfig-macros/src/lib.rs(L89 - L143) 

Macro Usage Patterns

parse_configs! Macro

The parse_configs! macro processes inline TOML strings and generates corresponding Rust constants:

Input TypeGenerated OutputExample
Global constantspub const NAME: TYPE = VALUE;pub const ARE_YOU_OK: bool = true;
Table sectionspub mod table_name { ... }pub mod hello { ... }
Typed valuesType-annotated constantspub const VALUE: isize = 456;

The macro supports type annotations through TOML comments and automatic type inference from values.

Sources: axconfig-macros/README.md(L7 - L23)  axconfig-macros/src/lib.rs(L16 - L41) 

include_configs! Macro

The include_configs! macro provides three path resolution strategies:


Sources: axconfig-macros/src/lib.rs(L58 - L87)  axconfig-macros/src/lib.rs(L76 - L77)  axconfig-macros/src/lib.rs(L83 - L86) 

Implementation Details

Argument Parsing System

The IncludeConfigsArgs enum handles the three different invocation patterns for include_configs!:

flowchart TD
subgraph subGraph2["Error Handling"]
    MISSING_ENV["Missing path_env parameter"]
    DUPLICATE_PARAM["Duplicate parameter error"]
    UNEXPECTED_PARAM["Unexpected parameter error"]
end
subgraph subGraph1["Parse Implementation"]
    PEEK_LITSTR["input.peek(LitStr)Check for direct path"]
    PARSE_IDENT["Ident parsingpath_env, fallback"]
    PARSE_EQUALS["Token![=] parsing"]
    PARSE_STRING["LitStr parsing"]
    DUPLICATE_CHECK["Duplicate parameter detection"]
end
subgraph subGraph0["IncludeConfigsArgs Variants"]
    PATH_VARIANT["Path(LitStr)Direct file path"]
    ENV_VARIANT["PathEnv(LitStr)Environment variable only"]
    FALLBACK_VARIANT["PathEnvFallback(LitStr, LitStr)Environment + fallback"]
end

DUPLICATE_CHECK --> DUPLICATE_PARAM
DUPLICATE_CHECK --> ENV_VARIANT
DUPLICATE_CHECK --> FALLBACK_VARIANT
PARSE_EQUALS --> PARSE_STRING
PARSE_IDENT --> PARSE_EQUALS
PARSE_IDENT --> UNEXPECTED_PARAM
PARSE_STRING --> DUPLICATE_CHECK
PARSE_STRING --> MISSING_ENV
PEEK_LITSTR --> PATH_VARIANT

Sources: axconfig-macros/src/lib.rs(L89 - L143)  axconfig-macros/src/lib.rs(L95 - L142) 

Error Handling and Compilation Integration

The macro implementation includes comprehensive error handling that integrates with the Rust compiler's diagnostic system:

Error TypeFunctionGenerated Output
TOML parsing errorscompiler_errorCompile-time error with span information
File read failurescompiler_errorError message with file path context
Environment variable errorscompiler_errorMissing environment variable details
Token parsing errorsLexErrorhandlingRust lexer error propagation

Sources: axconfig-macros/src/lib.rs(L12 - L14)  axconfig-macros/src/lib.rs(L39 - L40)  axconfig-macros/src/lib.rs(L63 - L67)  axconfig-macros/src/lib.rs(L79 - L81) 

Integration with axconfig-gen Core

flowchart TD
subgraph subGraph2["Generated Output"]
    RUST_CONSTANTS["pub const declarations"]
    MODULE_STRUCTURE["pub mod organization"]
    TYPE_ANNOTATIONS["Inferred and explicit types"]
end
subgraph subGraph1["axconfig-gen Integration"]
    CONFIG_STRUCT["Config::from_tomlCore parsing logic"]
    OUTPUT_FORMAT["OutputFormat::RustCode generation"]
    TYPE_SYSTEM["Type inferenceConfigType, ConfigValue"]
end
subgraph subGraph0["axconfig-macros Layer"]
    PROC_MACRO["Procedural Macroproc_macro::TokenStream"]
    SYNTAX_PARSING["Syntax Parsingsyn, quote, proc_macro2"]
    PATH_RESOLUTION["Path ResolutionEnvironment variables, file system"]
end

CONFIG_STRUCT --> TYPE_SYSTEM
OUTPUT_FORMAT --> MODULE_STRUCTURE
OUTPUT_FORMAT --> RUST_CONSTANTS
OUTPUT_FORMAT --> TYPE_ANNOTATIONS
PATH_RESOLUTION --> CONFIG_STRUCT
PROC_MACRO --> SYNTAX_PARSING
SYNTAX_PARSING --> PATH_RESOLUTION
TYPE_SYSTEM --> OUTPUT_FORMAT

The macros act as a procedural macro frontend to the core axconfig-gen processing pipeline, handling the compile-time integration and file system operations while delegating the actual TOML processing and code generation to the shared library.

Sources: axconfig-macros/src/lib.rs(L10)  axconfig-macros/src/lib.rs(L34 - L35) 

Type System Integration

The macros inherit the full type system capabilities from axconfig-gen, including:

  • Automatic type inference from TOML values
  • Explicit type annotations via TOML comments
  • Complex type support for tuples, arrays, and nested structures
  • Rust-native type mapping for generated constants

The generated code maintains type safety and zero-cost abstractions by producing compile-time constants rather than runtime configuration lookups.

Sources: axconfig-macros/README.md(L25 - L38)  axconfig-macros/src/lib.rs(L34 - L35) 

Macro Usage Patterns

Relevant source files

This document provides practical guidance for using the procedural macros provided by the axconfig-macros crate. It covers the two primary macros (parse_configs! and include_configs!), their various invocation patterns, and best practices for integrating TOML configuration processing into Rust code at compile time.

For implementation details of how these macros work internally, see Macro Implementation. For TOML format specifications and examples, see TOML Configuration Format.

Basic Macro Invocation Patterns

The axconfig-macros crate provides two fundamental macros for compile-time TOML processing: parse_configs! for inline TOML content and include_configs! for external file processing.

flowchart TD
subgraph subGraph2["Generated Output"]
    CONSTANTS["pub const declarations"]
    MODULES["pub mod declarations"]
    TYPES["Type annotations"]
end
subgraph subGraph1["Input Sources"]
    TOML_STRING["Inline TOML String"]
    TOML_FILE["External TOML File"]
    ENV_PATH["Environment Variable Path"]
    FALLBACK["Fallback File Path"]
end
subgraph subGraph0["Macro Invocation Types"]
    INLINE["parse_configs!"]
    INCLUDE["include_configs!"]
end

CONSTANTS --> MODULES
CONSTANTS --> TYPES
ENV_PATH --> CONSTANTS
FALLBACK --> CONSTANTS
INCLUDE --> ENV_PATH
INCLUDE --> FALLBACK
INCLUDE --> TOML_FILE
INLINE --> TOML_STRING
TOML_FILE --> CONSTANTS
TOML_STRING --> CONSTANTS

Macro Processing Flow

Inline TOML Processing

The parse_configs! macro processes TOML content directly embedded in Rust source code. This pattern is useful for small, static configurations that don't require external file management.

Basic usage pattern as shown in axconfig-macros/README.md(L8 - L16) :

axconfig_macros::parse_configs!(r#"
are-you-ok = true
one-two-three = 123

[hello]
"one-two-three" = "456"     # int
array = [1, 2, 3]           # [uint]
tuple = [1, "abc", 3]
"#);

This generates compile-time constants that can be accessed immediately after the macro invocation, as demonstrated in axconfig-macros/README.md(L18 - L22) 

Sources: axconfig-macros/README.md:8-22

External File Processing

The include_configs! macro reads TOML configuration from external files at compile time. This pattern is preferred for larger configurations or when configuration files are shared across multiple projects.


File Path Resolution Strategies

Sources: axconfig-macros/README.md:40-48

File Inclusion Patterns

Direct File Path

The simplest file inclusion pattern specifies a direct path relative to CARGO_MANIFEST_DIR:

axconfig_macros::include_configs!("path/to/config.toml");

This pattern is demonstrated in axconfig-macros/tests/example_config.rs(L5)  where the test loads configuration from a relative path.

Environment Variable Path Resolution

For flexible deployment scenarios, the macro can resolve file paths from environment variables:

axconfig_macros::include_configs!(path_env = "AX_CONFIG_PATH");

This pattern allows the configuration file location to be determined at build time through environment variables, enabling different configurations for different build environments.

Environment Variable with Fallback

The most robust pattern combines environment variable resolution with a fallback path:

axconfig_macros::include_configs!(
    path_env = "AX_CONFIG_PATH", 
    fallback = "path/to/defconfig.toml"
);

This ensures the build succeeds even when the environment variable is not set, defaulting to a known configuration file.

Sources: axconfig-macros/README.md:42-47, axconfig-macros/tests/example_config.rs:5

Type Annotation Patterns

Explicit Type Specification

Type annotations are specified using TOML comments following configuration values. The type system supports several categories:

Type CategorySyntaxExample
Boolean# boolenabled = true # bool
Signed Integer# intoffset = -10 # int
Unsigned Integer# uintsize = 1024 # uint
String# strname = "test" # str
Array# [type]values = [1, 2, 3] # [uint]
Tuple# (type1, type2, ...)pair = [1, "abc"] # (uint, str)

As shown in axconfig-macros/README.md(L13 - L15)  type annotations directly influence the generated Rust code types.

Type Inference

When no explicit type annotation is provided, the macro attempts to infer types from TOML values:

flowchart TD
subgraph subGraph1["Generated Types"]
    BOOL_TYPE["bool"]
    UINT_TYPE["usize"]
    STR_TYPE["&str"]
    ARRAY_TYPE["&[usize]"]
    TUPLE_TYPE["(T1, T2, ...)"]
end
subgraph subGraph0["Type Inference Rules"]
    TOML_VALUE["TOML Value"]
    BOOL_CHECK["Boolean Value?"]
    INT_CHECK["Integer Value?"]
    STR_CHECK["String Value?"]
    ARRAY_CHECK["Array Value?"]
end

ARRAY_CHECK --> ARRAY_TYPE
ARRAY_CHECK --> TUPLE_TYPE
BOOL_CHECK --> BOOL_TYPE
BOOL_CHECK --> INT_CHECK
INT_CHECK --> STR_CHECK
INT_CHECK --> UINT_TYPE
STR_CHECK --> ARRAY_CHECK
STR_CHECK --> STR_TYPE
TOML_VALUE --> BOOL_CHECK

Type Inference Decision Tree

Sources: axconfig-macros/README.md:25

Generated Code Structure

Constant Generation

The macros generate pub const declarations for top-level configuration items and pub mod declarations for TOML tables, as shown in axconfig-macros/README.md(L29 - L38) :

flowchart TD
subgraph subGraph1["Generated Rust Code"]
    PUB_CONST["pub const ITEM_NAME"]
    PUB_MOD["pub mod table_name"]
    MOD_CONST["pub const ITEM_NAME (in module)"]
end
subgraph subGraph0["TOML Structure"]
    ROOT_ITEMS["Root-level items"]
    TABLES["[table] sections"]
    NESTED_ITEMS["Table items"]
end

NESTED_ITEMS --> MOD_CONST
PUB_MOD --> MOD_CONST
ROOT_ITEMS --> PUB_CONST
TABLES --> PUB_MOD

TOML to Rust Code Mapping

Identifier Transformation

TOML keys are transformed to valid Rust identifiers following these rules:

  • Hyphens are converted to underscores
  • Snake_case is preserved
  • Quoted keys are handled appropriately
  • Case conversion follows Rust naming conventions

As demonstrated in axconfig-macros/README.md(L18 - L20)  the key "one-two-three" becomes the identifier ONE_TWO_THREE.

Sources: axconfig-macros/README.md:29-38, axconfig-macros/README.md:18-20

Integration Patterns

Module-scoped Configuration

A common pattern is to define configuration within a dedicated module to avoid namespace pollution:

mod config {
    include_configs!("../example-configs/defconfig.toml");
}

This pattern is used in axconfig-macros/tests/example_config.rs(L4 - L6)  and allows accessing configuration through qualified paths like config::ARCH.

Conditional Compilation

The macros can be used with conditional compilation for different build configurations:

#[cfg(feature = "nightly")]
mod config2 {
    parse_configs!(include_str!("../../example-configs/defconfig.toml"));
}

This pattern, shown in axconfig-macros/tests/example_config.rs(L8 - L11)  demonstrates combining parse_configs! with include_str! for feature-gated builds.

Testing Integration

Configuration modules can be compared for testing purposes, as demonstrated in the comprehensive comparison macro in axconfig-macros/tests/example_config.rs(L17 - L75)  which validates that different macro invocation methods produce identical results.

Sources: axconfig-macros/tests/example_config.rs:4-11, axconfig-macros/tests/example_config.rs:17-75

Macro Implementation

Relevant source files

This document covers the technical implementation details of the procedural macros in the axconfig-macros crate, including their integration with the core axconfig-gen library. It explains how the macros process TOML configurations at compile time and generate Rust code using the proc-macro infrastructure.

For information about how to use these macros in practice, see Macro Usage Patterns. For details about the underlying configuration processing logic, see Library API.

Procedural Macro Architecture

The axconfig-macros crate implements two procedural macros that provide compile-time TOML configuration processing by leveraging the core functionality from axconfig-gen. The architecture separates parsing and code generation concerns from the macro expansion logic.

flowchart TD
subgraph subGraph3["File System Operations"]
    ENV_VAR_RESOLUTION["Environment VariableResolution"]
    FILE_READING["std::fs::read_to_stringTOML file loading"]
    PATH_RESOLUTION["CARGO_MANIFEST_DIRPath resolution"]
end
subgraph subGraph2["Proc-Macro Infrastructure"]
    TOKEN_STREAM["TokenStreamInput parsing"]
    SYN_PARSE["syn::parseSyntax tree parsing"]
    QUOTE_GEN["quote!Code generation"]
    COMPILE_ERROR["Error::to_compile_errorCompilation errors"]
end
subgraph subGraph1["Core Processing (axconfig-gen)"]
    CONFIG_FROM_TOML["Config::from_tomlTOML parsing"]
    CONFIG_DUMP["Config::dumpOutputFormat::Rust"]
    RUST_CODE_GEN["Rust Code Generationpub const, pub mod"]
end
subgraph subGraph0["Compile-Time Processing"]
    USER_CODE["User Rust Codeparse_configs! / include_configs!"]
    MACRO_INVOKE["Macro InvocationTokenStream input"]
    MACRO_IMPL["Macro Implementationlib.rs"]
end

COMPILE_ERROR --> USER_CODE
CONFIG_DUMP --> RUST_CODE_GEN
CONFIG_FROM_TOML --> CONFIG_DUMP
ENV_VAR_RESOLUTION --> PATH_RESOLUTION
FILE_READING --> CONFIG_FROM_TOML
MACRO_IMPL --> COMPILE_ERROR
MACRO_IMPL --> ENV_VAR_RESOLUTION
MACRO_IMPL --> FILE_READING
MACRO_IMPL --> TOKEN_STREAM
MACRO_INVOKE --> MACRO_IMPL
QUOTE_GEN --> USER_CODE
RUST_CODE_GEN --> QUOTE_GEN
SYN_PARSE --> CONFIG_FROM_TOML
TOKEN_STREAM --> SYN_PARSE
USER_CODE --> MACRO_INVOKE

Sources: axconfig-macros/src/lib.rs(L1 - L144) 

Macro Implementation Details

parse_configs! Macro

The parse_configs! macro processes inline TOML strings and expands them into Rust code at compile time. It handles both regular compilation and nightly compiler features for enhanced expression expansion.

flowchart TD
INPUT_TOKENS["TokenStreamTOML string literal"]
NIGHTLY_EXPAND["proc_macro::expand_exprNightly feature"]
PARSE_LITSTR["parse_macro_input!as LitStr"]
EXTRACT_VALUE["LitStr::valueExtract TOML content"]
CONFIG_PARSE["Config::from_tomlParse TOML structure"]
CONFIG_DUMP["Config::dumpOutputFormat::Rust"]
TOKEN_PARSE["code.parseString to TokenStream"]
ERROR_HANDLING["compiler_errorLexError handling"]
FINAL_TOKENS["TokenStreamGenerated Rust code"]

CONFIG_DUMP --> ERROR_HANDLING
CONFIG_DUMP --> TOKEN_PARSE
CONFIG_PARSE --> CONFIG_DUMP
CONFIG_PARSE --> ERROR_HANDLING
ERROR_HANDLING --> FINAL_TOKENS
EXTRACT_VALUE --> CONFIG_PARSE
INPUT_TOKENS --> NIGHTLY_EXPAND
INPUT_TOKENS --> PARSE_LITSTR
NIGHTLY_EXPAND --> PARSE_LITSTR
PARSE_LITSTR --> EXTRACT_VALUE
TOKEN_PARSE --> ERROR_HANDLING
TOKEN_PARSE --> FINAL_TOKENS

The implementation includes conditional compilation for nightly features:

FeatureFunctionalityImplementation
NightlyEnhanced expression expansionconfig_toml.expand_expr()
StableStandard literal parsingparse_macro_input!(config_toml as LitStr)
Error HandlingCompilation error generationcompiler_error()function

Sources: axconfig-macros/src/lib.rs(L22 - L41) 

include_configs! Macro

The include_configs! macro supports three different path specification methods and handles file system operations with comprehensive error handling.

flowchart TD
subgraph subGraph2["File Processing"]
    FILE_READ["std::fs::read_to_stringTOML file reading"]
    PARSE_CONFIGS_CALL["parse_configs!Recursive macro call"]
    QUOTE_MACRO["quote!Token generation"]
end
subgraph subGraph1["Path Resolution"]
    ENV_VAR_LOOKUP["std::env::varEnvironment variable lookup"]
    FALLBACK_LOGIC["Fallback pathhandling"]
    CARGO_MANIFEST["CARGO_MANIFEST_DIRRoot directory resolution"]
    PATH_JOIN["std::path::Path::joinFull path construction"]
end
subgraph subGraph0["Argument Parsing"]
    ARGS_INPUT["TokenStreamMacro arguments"]
    PARSE_ARGS["IncludeConfigsArgs::parseCustom argument parser"]
    PATH_DIRECT["Path variantDirect file path"]
    PATH_ENV["PathEnv variantEnvironment variable"]
    PATH_ENV_FALLBACK["PathEnvFallback variantEnv var + fallback"]
end

ARGS_INPUT --> PARSE_ARGS
CARGO_MANIFEST --> PATH_JOIN
ENV_VAR_LOOKUP --> FALLBACK_LOGIC
FALLBACK_LOGIC --> CARGO_MANIFEST
FILE_READ --> PARSE_CONFIGS_CALL
PARSE_ARGS --> PATH_DIRECT
PARSE_ARGS --> PATH_ENV
PARSE_ARGS --> PATH_ENV_FALLBACK
PARSE_CONFIGS_CALL --> QUOTE_MACRO
PATH_DIRECT --> CARGO_MANIFEST
PATH_ENV --> ENV_VAR_LOOKUP
PATH_ENV_FALLBACK --> ENV_VAR_LOOKUP
PATH_JOIN --> FILE_READ

Sources: axconfig-macros/src/lib.rs(L58 - L87) 

Argument Parsing Implementation

The IncludeConfigsArgs enum and its Parse implementation handle the complex argument parsing for the include_configs! macro with proper error reporting.

stateDiagram-v2
[*] --> CheckFirstToken
CheckFirstToken --> DirectPath : LitStr
CheckFirstToken --> ParseParameters : Ident
DirectPath --> [*] : Path(LitStr)
ParseParameters --> ReadIdent
ReadIdent --> CheckEquals : ident
CheckEquals --> ReadString : =
ReadString --> ProcessParameter : LitStr
ProcessParameter --> SetPathEnv : "path_env"
ProcessParameter --> SetFallback : "fallback"
ProcessParameter --> Error : unknown parameter
SetPathEnv --> CheckComma
SetFallback --> CheckComma
CheckComma --> ReadIdent : more tokens
CheckComma --> ValidateResult : end of input
ValidateResult --> PathEnvOnly : env only
ValidateResult --> PathEnvWithFallback : env + fallback
ValidateResult --> Error : invalid combination
PathEnvOnly --> [*] : PathEnv(LitStr)
PathEnvWithFallback --> [*] : PathEnvFallback(LitStr, LitStr)
Error --> [*] : ParseError

The parsing logic handles these parameter combinations:

SyntaxEnum VariantBehavior
"path/to/file.toml"Path(LitStr)Direct file path
path_env = "ENV_VAR"PathEnv(LitStr)Environment variable only
path_env = "ENV_VAR", fallback = "default.toml"PathEnvFallback(LitStr, LitStr)Environment variable with fallback

Sources: axconfig-macros/src/lib.rs(L89 - L143) 

Error Handling and Compilation

The macro implementation includes comprehensive error handling that generates meaningful compilation errors for various failure scenarios.

Compiler Error Generation

The compiler_error function provides a centralized mechanism for generating compilation errors that integrate properly with the Rust compiler's diagnostic system.

flowchart TD
subgraph subGraph2["Compiler Integration"]
    RUST_COMPILER["Rust CompilerError reporting"]
    BUILD_FAILURE["Build FailureClear error messages"]
end
subgraph subGraph1["Error Processing"]
    COMPILER_ERROR_FN["compiler_errorError::new_spanned"]
    TO_COMPILE_ERROR["Error::to_compile_errorTokenStream generation"]
    ERROR_SPAN["Span informationSource location"]
end
subgraph subGraph0["Error Sources"]
    TOML_PARSE_ERROR["TOML Parse ErrorConfig::from_toml"]
    FILE_READ_ERROR["File Read Errorstd::fs::read_to_string"]
    ENV_VAR_ERROR["Environment Variable Errorstd::env::var"]
    LEX_ERROR["Lexical ErrorTokenStream parsing"]
    SYNTAX_ERROR["Syntax ErrorArgument parsing"]
end

COMPILER_ERROR_FN --> ERROR_SPAN
COMPILER_ERROR_FN --> TO_COMPILE_ERROR
ENV_VAR_ERROR --> COMPILER_ERROR_FN
FILE_READ_ERROR --> COMPILER_ERROR_FN
LEX_ERROR --> COMPILER_ERROR_FN
RUST_COMPILER --> BUILD_FAILURE
SYNTAX_ERROR --> COMPILER_ERROR_FN
TOML_PARSE_ERROR --> COMPILER_ERROR_FN
TO_COMPILE_ERROR --> RUST_COMPILER

Sources: axconfig-macros/src/lib.rs(L12 - L14)  axconfig-macros/src/lib.rs(L36 - L40)  axconfig-macros/src/lib.rs(L63 - L67)  axconfig-macros/src/lib.rs(L79 - L81) 

Integration with Build System

The macros integrate with Cargo's build system through environment variable resolution and dependency tracking that ensures proper rebuilds when configuration files change.

Build-Time Path Resolution

The implementation uses CARGO_MANIFEST_DIR to resolve relative paths consistently across different build environments:

Environment VariablePurposeUsage
CARGO_MANIFEST_DIRProject root directoryPath resolution base
User-defined env varsConfig file pathsDynamic path specification

Dependency Tracking

The file-based operations in include_configs! automatically create implicit dependencies that Cargo uses for rebuild detection:

flowchart TD
subgraph subGraph2["Build Outputs"]
    COMPILED_BINARY["Compiled binarywith embedded config"]
    BUILD_CACHE["Build cacheDependency information"]
end
subgraph subGraph1["Build Process"]
    MACRO_EXPANSION["Macro expansionFile read operation"]
    CARGO_TRACKING["Cargo dependency trackingImplicit file dependency"]
    REBUILD_DETECTION["Rebuild detectionFile modification time"]
end
subgraph subGraph0["Source Files"]
    RUST_SOURCE["Rust source filewith include_configs!"]
    TOML_CONFIG["TOML config filereferenced by macro"]
end

CARGO_TRACKING --> BUILD_CACHE
CARGO_TRACKING --> REBUILD_DETECTION
MACRO_EXPANSION --> CARGO_TRACKING
REBUILD_DETECTION --> COMPILED_BINARY
RUST_SOURCE --> MACRO_EXPANSION
TOML_CONFIG --> MACRO_EXPANSION
TOML_CONFIG --> REBUILD_DETECTION

Sources: axconfig-macros/src/lib.rs(L76 - L77)  axconfig-macros/tests/example_config.rs(L4 - L5) 

Testing Integration

The macro implementation includes comprehensive testing that validates both the generated code and the macro expansion process itself.

The test structure demonstrates proper integration between the macros and expected outputs:

flowchart TD
subgraph subGraph2["Test Execution"]
    MOD_CMP_MACRO["mod_cmp! macroModule comparison"]
    ASSERT_STATEMENTS["assert_eq! callsValue validation"]
    TEST_FUNCTIONS["test functionsinclude and parse tests"]
end
subgraph subGraph1["Test Data"]
    DEFCONFIG_TOML["defconfig.tomlTest input"]
    OUTPUT_RS["output.rsExpected generated code"]
    INCLUDE_STR["include_str!String inclusion"]
end
subgraph subGraph0["Test Configuration"]
    TEST_FILE["example_config.rsTest definitions"]
    CONFIG_MODULE["config moduleinclude_configs! usage"]
    CONFIG2_MODULE["config2 moduleparse_configs! usage"]
    EXPECTED_MODULE["config_expect moduleExpected output"]
end

ASSERT_STATEMENTS --> CONFIG2_MODULE
ASSERT_STATEMENTS --> CONFIG_MODULE
ASSERT_STATEMENTS --> EXPECTED_MODULE
CONFIG2_MODULE --> INCLUDE_STR
CONFIG_MODULE --> DEFCONFIG_TOML
EXPECTED_MODULE --> OUTPUT_RS
INCLUDE_STR --> DEFCONFIG_TOML
MOD_CMP_MACRO --> ASSERT_STATEMENTS
TEST_FUNCTIONS --> MOD_CMP_MACRO

Sources: axconfig-macros/tests/example_config.rs(L1 - L87) 

Configuration Examples

Relevant source files

This page demonstrates how the axconfig-gen system transforms TOML configuration files into various output formats. It provides concrete examples showing input configurations and their corresponding generated outputs in both TOML and Rust formats. For information about the TOML input format specification, see TOML Configuration Format. For detailed output format documentation, see Generated Output Examples.

Complete Configuration Example

The following example demonstrates a complete ArceOS configuration transformation, showing how a defconfig TOML file is processed into both normalized TOML and Rust code outputs.

Input Configuration Structure


Sources: example-configs/defconfig.toml(L1 - L63) 

Transformation Pipeline

flowchart TD
subgraph subGraph2["Output Generation"]
    TOML_GEN["TOML Generator"]
    RUST_GEN["Rust Code Generator"]
    TOML_OUT["output.toml"]
    RUST_OUT["output.rs"]
end
subgraph subGraph1["Core Processing"]
    CONFIG_STRUCT["Config Structure"]
    GLOBAL_TABLE["Global ConfigItem entries"]
    NAMED_TABLES["Named table sections"]
    VALIDATION["Type Validation"]
end
subgraph subGraph0["Input Processing"]
    TOML_IN["defconfig.toml"]
    PARSE["TOML Parser"]
    TYPE_INFER["Type Inference from Comments"]
end

CONFIG_STRUCT --> GLOBAL_TABLE
CONFIG_STRUCT --> NAMED_TABLES
GLOBAL_TABLE --> VALIDATION
NAMED_TABLES --> VALIDATION
PARSE --> TYPE_INFER
RUST_GEN --> RUST_OUT
TOML_GEN --> TOML_OUT
TOML_IN --> PARSE
TYPE_INFER --> CONFIG_STRUCT
VALIDATION --> RUST_GEN
VALIDATION --> TOML_GEN

Sources: example-configs/defconfig.toml(L1 - L63)  example-configs/output.toml(L1 - L63)  example-configs/output.rs(L1 - L66) 

Data Type Transformations

The configuration system supports multiple data types with automatic conversion between TOML values and Rust types:

TOML TypeExample InputType AnnotationRust Output
Stringarch = "x86_64"# strpub const ARCH: &str = "x86_64";
Integersmp = 1# uintpub const SMP: usize = 1;
Hex Integerphys-memory-size = 0x800_0000# uintpub const PHYS_MEMORY_SIZE: usize = 0x800_0000;
String as Integerkernel-base-vaddr = "0xffff_ff80_0020_0000"# uintpub const KERNEL_BASE_VADDR: usize = 0xffff_ff80_0020_0000;
Array of Arraysmmio-regions = [["0xb000_0000", "0x1000_0000"]]# [(uint, uint)]pub const MMIO_REGIONS: &[(usize, usize)] = &[(0xb000_0000, 0x1000_0000)];
Empty Arrayvirtio-mmio-regions = []# [(uint, uint)]pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[];

Sources: example-configs/defconfig.toml(L2 - L56)  example-configs/output.rs(L2 - L27) 

Structure Mapping Between Formats

The system maps TOML structure to Rust module hierarchy:

flowchart TD
subgraph subGraph1["Rust Module Structure"]
    RUST_ROOT["Root constants"]
    RUST_KERNEL["pub mod kernel"]
    RUST_PLATFORM["pub mod platform"]
    RUST_DEVICES["pub mod devices"]
end
subgraph subGraph0["TOML Structure"]
    TOML_ROOT["Root level items"]
    TOML_KERNEL["[kernel] section"]
    TOML_PLATFORM["[platform] section"]
    TOML_DEVICES["[devices] section"]
end
subgraph subGraph2["Naming Conventions"]
    KEBAB["kebab-case"]
    SNAKE["SCREAMING_SNAKE_CASE"]
end

KEBAB --> SNAKE
TOML_DEVICES --> RUST_DEVICES
TOML_KERNEL --> RUST_KERNEL
TOML_PLATFORM --> RUST_PLATFORM
TOML_ROOT --> RUST_ROOT

Sources: example-configs/defconfig.toml(L11 - L46)  example-configs/output.rs(L11 - L65) 

Naming Convention Examples

TOML NameRust Constant
task-stack-sizeTASK_STACK_SIZE
phys-memory-basePHYS_MEMORY_BASE
kernel-base-vaddrKERNEL_BASE_VADDR
mmio-regionsMMIO_REGIONS
pci-ecam-basePCI_ECAM_BASE

Sources: example-configs/defconfig.toml(L13 - L58)  example-configs/output.rs(L13 - L57) 

Comment and Documentation Preservation

The system preserves comments from TOML files as Rust documentation:

TOML Comments

# Architecture identifier.
arch = "x86_64"             # str

# Stack size of each task.
task-stack-size = 0     # uint

# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
    ["0xb000_0000", "0x1000_0000"], # PCI config space
    ["0xfe00_0000", "0xc0_0000"],   # PCI devices
]                           # [(uint, uint)]

Generated Rust Documentation

/// Architecture identifier.
pub const ARCH: &str = "x86_64";

/// Stack size of each task.
pub const TASK_STACK_SIZE: usize = 0;

/// MMIO regions with format (`base_paddr`, `size`).
pub const MMIO_REGIONS: &[(usize, usize)] = &[
    (0xb000_0000, 0x1000_0000),
    (0xfe00_0000, 0xc0_0000),
    // ... more entries
];

Sources: example-configs/defconfig.toml(L1 - L54)  example-configs/output.rs(L1 - L35) 

Array Processing Examples

The system handles complex array structures, converting TOML arrays to Rust slices:

Input Array Format

mmio-regions = [
    ["0xb000_0000", "0x1000_0000"], # PCI config space
    ["0xfe00_0000", "0xc0_0000"],   # PCI devices
    ["0xfec0_0000", "0x1000"],      # IO APIC
    ["0xfed0_0000", "0x1000"],      # HPET
    ["0xfee0_0000", "0x1000"],      # Local APIC
]                           # [(uint, uint)]

Generated Rust Array

pub const MMIO_REGIONS: &[(usize, usize)] = &[
    (0xb000_0000, 0x1000_0000),
    (0xfe00_0000, 0xc0_0000),
    (0xfec0_0000, 0x1000),
    (0xfed0_0000, 0x1000),
    (0xfee0_0000, 0x1000),
];

Sources: example-configs/defconfig.toml(L48 - L54)  example-configs/output.rs(L13 - L19) 

Output Format Comparison

Both output formats maintain the same logical structure but serve different purposes:

AspectTOML OutputRust Output
PurposeConfiguration interchangeCompile-time constants
StructureFlat sections with key-value pairsNested modules with constants
TypesTOML native types with annotationsStrongly-typed Rust constants
UsageRuntime configuration loadingDirect code integration
CommentsPreserved as TOML commentsConverted to doc comments

The TOML output in example-configs/output.toml(L1 - L63)  maintains the original structure while normalizing formatting. The Rust output in example-configs/output.rs(L1 - L66)  provides type-safe compile-time access to the same configuration data.

Sources: example-configs/output.toml(L1 - L63)  example-configs/output.rs(L1 - L66) 

TOML Configuration Format

Relevant source files

This document specifies the TOML input format used by axconfig-gen for configuration processing. It covers the structure, type annotation system, supported data types, and ArceOS-specific conventions for writing configuration files.

For information about how configurations are processed into output formats, see Generated Output Examples. For details about the CLI tool that processes these TOML files, see Command Line Interface.

Basic TOML Structure

The axconfig-gen system processes TOML files organized into a two-level hierarchy: a global table for top-level configuration items and named tables for grouped configurations.

flowchart TD
subgraph subGraph2["ConfigItem Internal Structure"]
    ITEM["ConfigItem"]
    VALUES["values: HashMap"]
    COMMENTS["comments: HashMap"]
end
subgraph subGraph1["Mapping to Config Structure"]
    CONFIG["Config"]
    GLOBAL_ITEMS["global: ConfigItem"]
    TABLE_ITEMS["tables: HashMap"]
end
subgraph subGraph0["TOML File Structure"]
    FILE["TOML Configuration File"]
    GLOBAL["Global Table(top-level keys)"]
    NAMED["Named Tables([section] headers)"]
end

CONFIG --> GLOBAL_ITEMS
CONFIG --> TABLE_ITEMS
FILE --> GLOBAL
FILE --> NAMED
GLOBAL --> CONFIG
GLOBAL_ITEMS --> ITEM
ITEM --> COMMENTS
ITEM --> VALUES
NAMED --> CONFIG
TABLE_ITEMS --> ITEM

TOML Structure to Internal Representation Mapping

The parser maps TOML sections directly to the internal Config structure, where global keys become part of the global ConfigItem and each [section] becomes a named table entry.

Sources: README.md(L42 - L65)  example-configs/defconfig.toml(L1 - L63) 

Type Annotation System

Type information is specified through inline comments immediately following configuration values. This annotation system enables precise Rust code generation with correct type definitions.

flowchart TD
subgraph subGraph2["Value Integration"]
    VALUE_PARSE["Value parsing"]
    CONFIG_VALUE["ConfigValue"]
    TYPE_ASSIGN["Type assignment"]
    FINAL["ConfigValue with ConfigType"]
end
subgraph subGraph1["Type Processing"]
    TYPE_STR["Type String:'uint', 'bool', '[int]', etc."]
    TYPE_PARSE["ConfigType::from_str()"]
    TYPE_OBJ["ConfigType enum variant"]
end
subgraph subGraph0["TOML Parsing Flow"]
    INPUT["TOML Line:key = value  # type_annotation"]
    PARSE["toml_edit parsing"]
    EXTRACT["Comment Extraction"]
end

CONFIG_VALUE --> TYPE_ASSIGN
EXTRACT --> TYPE_STR
INPUT --> PARSE
PARSE --> EXTRACT
PARSE --> VALUE_PARSE
TYPE_ASSIGN --> FINAL
TYPE_OBJ --> TYPE_ASSIGN
TYPE_PARSE --> TYPE_OBJ
TYPE_STR --> TYPE_PARSE
VALUE_PARSE --> CONFIG_VALUE

Type Annotation Processing Pipeline

The system extracts type annotations from comments and converts them to ConfigType instances for type-safe code generation.

Type Annotation Syntax

AnnotationConfigTypeRust Output TypeExample
# boolConfigType::Boolboolenabled = true # bool
# intConfigType::Intisizeoffset = -10 # int
# uintConfigType::Uintusizesize = 1024 # uint
# strConfigType::Str&strname = "test" # str
# [uint]ConfigType::Array(Uint)&[usize]ports = [80, 443] # [uint]
# (uint, str)ConfigType::Tuple([Uint, Str])(usize, &str)pair = [1, "a"] # (uint, str)

Sources: README.md(L35 - L36)  README.md(L47 - L48)  example-configs/defconfig.toml(L2 - L3) 

Supported Data Types

The type system supports primitive types, collections, and nested structures to accommodate complex ArceOS configuration requirements.

flowchart TD
subgraph subGraph3["ConfigType Mapping"]
    CONFIG_BOOL["ConfigType::Bool"]
    CONFIG_INT["ConfigType::Int"]
    CONFIG_UINT["ConfigType::Uint"]
    CONFIG_STR["ConfigType::Str"]
    CONFIG_ARRAY["ConfigType::Array(inner)"]
    CONFIG_TUPLE["ConfigType::Tuple(Vec)"]
end
subgraph subGraph2["Value Representations"]
    DECIMAL["Decimal: 1024"]
    HEX_INT["Hex Integer: 0x1000"]
    HEX_STR["Hex String: '0xffff_ff80_0000_0000'"]
    ARRAY_VAL["Array: [1, 2, 3]"]
    TUPLE_VAL["Tuple: [value1, value2]"]
end
subgraph subGraph1["Collection Types"]
    ARRAY["[type]homogeneous arrays"]
    TUPLE["(type1, type2, ...)heterogeneous tuples"]
end
subgraph subGraph0["Primitive Types"]
    BOOL["booltrue/false values"]
    INT["intsigned integers"]
    UINT["uintunsigned integers"]
    STR["strstring values"]
end

ARRAY --> CONFIG_ARRAY
ARRAY_VAL --> ARRAY
BOOL --> CONFIG_BOOL
DECIMAL --> UINT
HEX_INT --> UINT
HEX_STR --> UINT
INT --> CONFIG_INT
STR --> CONFIG_STR
TUPLE --> CONFIG_TUPLE
TUPLE_VAL --> TUPLE
UINT --> CONFIG_UINT

ConfigType System and Value Mappings

Complex Type Examples

Array Types: Support homogeneous collections with type-safe element access:

mmio-regions = [
    ["0xb000_0000", "0x1000_0000"],
    ["0xfe00_0000", "0xc0_0000"]
]  # [(uint, uint)]

Tuple Types: Enable heterogeneous data grouping:

endpoint = ["192.168.1.1", 8080, true]  # (str, uint, bool)

Sources: example-configs/defconfig.toml(L48 - L54)  README.md(L47 - L49) 

ArceOS Configuration Conventions

ArceOS configurations follow established patterns for organizing system, platform, and device specifications into logical groupings.

Standard Configuration Sections

flowchart TD
subgraph subGraph4["Devices Section Details"]
    MMIO["mmio-regions: [(uint, uint)]"]
    VIRTIO["virtio-mmio-regions: [(uint, uint)]"]
    PCI["pci-ecam-base: uint"]
end
subgraph subGraph3["Platform Section Details"]
    PHYS_BASE["phys-memory-base: uint"]
    PHYS_SIZE["phys-memory-size: uint"]
    KERN_BASE["kernel-base-paddr: uint"]
    VIRT_MAP["phys-virt-offset: uint"]
end
subgraph subGraph2["Kernel Section Details"]
    STACK["task-stack-size: uint"]
    TICKS["ticks-per-sec: uint"]
end
subgraph subGraph1["Root Level Details"]
    ARCH["arch: strArchitecture identifier"]
    PLAT["plat: strPlatform identifier"]
    SMP["smp: uintCPU count"]
end
subgraph subGraph0["ArceOS Configuration Structure"]
    ROOT["Root Levelarch, plat, smp"]
    KERNEL["[kernel] SectionRuntime parameters"]
    PLATFORM["[platform] SectionMemory layout & hardware"]
    DEVICES["[devices] SectionHardware specifications"]
end

DEVICES --> MMIO
DEVICES --> PCI
DEVICES --> VIRTIO
KERNEL --> STACK
KERNEL --> TICKS
PLATFORM --> KERN_BASE
PLATFORM --> PHYS_BASE
PLATFORM --> PHYS_SIZE
PLATFORM --> VIRT_MAP
ROOT --> ARCH
ROOT --> PLAT
ROOT --> SMP

ArceOS Standard Configuration Organization

Memory Address Conventions

ArceOS uses specific patterns for memory address specification:

Address TypeFormatExamplePurpose
Physical addressesHex integers0x20_0000Hardware memory locations
Virtual addressesHex strings"0xffff_ff80_0020_0000"Kernel virtual memory
Memory regionsTuple arrays[["0xb000_0000", "0x1000_0000"]]MMIO ranges
Size specificationsHex integers0x800_0000Memory region sizes

Sources: example-configs/defconfig.toml(L22 - L39)  example-configs/defconfig.toml(L48 - L62) 

Key Naming and Value Formats

The configuration system supports flexible key naming and multiple value representation formats to accommodate diverse configuration needs.

Key Naming Conventions

# Standard kebab-case keys
task-stack-size = 0x1000        # uint

# Quoted keys for special characters
"one-two-three" = 456           # int

# Mixed naming in different contexts
kernel-base-paddr = 0x20_0000   # uint
"phys-memory-base" = 0          # uint

Value Format Support

Value TypeTOML RepresentationInternal ProcessingOutput
Decimal integers1024Direct parsing1024
Hex integers0x1000Hex parsing4096
Hex strings"0xffff_ff80"String + type hint0xffff_ff80_usize
Underscore separators0x800_0000Ignored in parsing0x8000000
String literals"x86_64"String preservation"x86_64"
Boolean valuestrue/falseDirect mappingtrue/false

Type Inference Rules

When no explicit type annotation is provided:

flowchart TD
VALUE["TOML Value"]
CHECK_BOOL["Is boolean?"]
CHECK_INT["Is integer?"]
CHECK_STR["Is string?"]
CHECK_ARRAY["Is array?"]
INFER_BOOL["ConfigType::Bool"]
INFER_UINT["ConfigType::Uint"]
INFER_STR["ConfigType::Str"]
INFER_ARRAY["ConfigType::Array(infer element type)"]

CHECK_ARRAY --> INFER_ARRAY
CHECK_BOOL --> CHECK_INT
CHECK_BOOL --> INFER_BOOL
CHECK_INT --> CHECK_STR
CHECK_INT --> INFER_UINT
CHECK_STR --> CHECK_ARRAY
CHECK_STR --> INFER_STR
VALUE --> CHECK_BOOL

Type Inference Decision Tree

The system defaults to unsigned integers for numeric values and attempts to infer array element types recursively.

Sources: README.md(L35 - L36)  example-configs/defconfig.toml(L1 - L7)  example-configs/defconfig.toml(L28 - L32) 

Generated Output Examples

Relevant source files

This page demonstrates the output generation capabilities of axconfig-gen by showing concrete examples of how TOML input configurations are transformed into both cleaned TOML and Rust code outputs. The examples illustrate type mappings, naming conventions, structure preservation, and comment handling across different output formats.

For information about the input TOML configuration format and type annotation system, see TOML Configuration Format. For details on the underlying output generation implementation, see Output Generation.

Transformation Process Overview

The axconfig-gen system processes input TOML configurations through a structured pipeline that preserves semantic meaning while adapting to different output format requirements.

Transformation Pipeline

flowchart TD
subgraph subGraph0["Format-Specific Processing"]
    TOML_CLEAN["TOML CleanupFormatting & Validation"]
    RUST_GEN["Rust Code GenerationConstants & Modules"]
end
INPUT["Input TOML(defconfig.toml)"]
PARSE["TOML Parsing(toml_edit)"]
STRUCT["Config Structure(Config, ConfigItem)"]
TYPE["Type Processing(ConfigType, ConfigValue)"]
TOML_OUT["Generated TOML(output.toml)"]
RUST_OUT["Generated Rust(output.rs)"]

INPUT --> PARSE
PARSE --> STRUCT
RUST_GEN --> RUST_OUT
STRUCT --> TYPE
TOML_CLEAN --> TOML_OUT
TYPE --> RUST_GEN
TYPE --> RUST_OUT
TYPE --> TOML_CLEAN
TYPE --> TOML_OUT

Sources: example-configs/defconfig.toml(L1 - L63)  example-configs/output.toml(L1 - L63)  example-configs/output.rs(L1 - L66) 

Global Configuration Items

Global configuration items defined at the root level of the TOML are transformed into top-level constants in Rust output while being preserved with consistent formatting in TOML output.

Input TOML Configuration:

# Architecture identifier.
arch = "x86_64"             # str
# Platform identifier.
plat = "x86_64-qemu-q35"    # str
# Number of CPUs.
smp = 1                     # uint

Generated TOML Output:

# Architecture identifier.
arch = "x86_64" # str
# Platform identifier.
plat = "x86_64-qemu-q35" # str
# Number of CPUs.
smp = 1 # uint

Generated Rust Output:

/// Architecture identifier.
pub const ARCH: &str = "x86_64";
/// Platform identifier.
pub const PLAT: &str = "x86_64-qemu-q35";
/// Number of CPUs.
pub const SMP: usize = 1;

Transformations Applied:

AspectInputTOML OutputRust Output
NamingarcharchARCH
Type"x86_64"(str)"x86_64" # str&str
Comments# Architecture identifier.Preserved/// Architecture identifier.
FormattingVariable spacingConsistent spacingRust doc comments

Sources: example-configs/defconfig.toml(L1 - L6)  example-configs/output.toml(L1 - L6)  example-configs/output.rs(L1 - L6) 

Sectioned Configuration

TOML table sections are transformed into Rust modules, maintaining the hierarchical organization while adapting naming conventions for each output format.

Input TOML Configuration:

[kernel]
# Stack size of each task.
task-stack-size = 0     # uint
# Number of timer ticks per second (Hz).
ticks-per-sec = 0       # uint

Generated TOML Output:

[kernel]
# Stack size of each task.
task-stack-size = 0 # uint
# Number of timer ticks per second (Hz).
ticks-per-sec = 0 # uint

Generated Rust Output:

///
/// Kernel configs
///
pub mod kernel {
    /// Stack size of each task.
    pub const TASK_STACK_SIZE: usize = 0;
    /// Number of timer ticks per second (Hz).
    pub const TICKS_PER_SEC: usize = 0;
}

Structure Mapping:

flowchart TD
TOML_TABLE["[kernel]TOML Table"]
RUST_MOD["pub mod kernelRust Module"]
TOML_ITEM1["task-stack-size = 0"]
TOML_ITEM2["ticks-per-sec = 0"]
RUST_CONST1["pub const TASK_STACK_SIZE: usize = 0"]
RUST_CONST2["pub const TICKS_PER_SEC: usize = 0"]

RUST_MOD --> RUST_CONST1
RUST_MOD --> RUST_CONST2
TOML_ITEM1 --> RUST_CONST1
TOML_ITEM2 --> RUST_CONST2
TOML_TABLE --> TOML_ITEM1
TOML_TABLE --> TOML_ITEM2

Sources: example-configs/defconfig.toml(L11 - L16)  example-configs/output.toml(L32 - L37)  example-configs/output.rs(L33 - L39) 

Complex Data Types

Array and tuple types demonstrate sophisticated type mapping between TOML array syntax and Rust slice references, including proper handling of nested structures.

Input TOML Configuration:

[devices]
# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
    ["0xb000_0000", "0x1000_0000"], # PCI config space
    ["0xfe00_0000", "0xc0_0000"],   # PCI devices
    ["0xfec0_0000", "0x1000"],      # IO APIC
]                           # [(uint, uint)]

Generated TOML Output:

[devices]
# MMIO regions with format (`base_paddr`, `size`).
mmio-regions = [
    ["0xb000_0000", "0x1000_0000"],
    ["0xfe00_0000", "0xc0_0000"],
    ["0xfec0_0000", "0x1000"],
] # [(uint, uint)]

Generated Rust Output:

pub mod devices {
    /// MMIO regions with format (`base_paddr`, `size`).
    pub const MMIO_REGIONS: &[(usize, usize)] = &[
        (0xb000_0000, 0x1000_0000),
        (0xfe00_0000, 0xc0_0000),
        (0xfec0_0000, 0x1000),
    ];
}

Type System Mappings:

flowchart TD
subgraph subGraph1["Rust Types"]
    RUST_SLICE["Slice Reference&[(usize, usize)]"]
    RUST_TUPLE["Tuple Literals(0xb000_0000, 0x1000_0000)"]
    RUST_TYPE["Rust Typeusize"]
end
subgraph subGraph0["TOML Types"]
    TOML_ARR["Array of Arrays[[str, str], ...]"]
    TOML_STR["String Literals'0xb000_0000'"]
    TOML_TYPE["Type Annotation# [(uint, uint)]"]
end

TOML_ARR --> RUST_SLICE
TOML_STR --> RUST_TUPLE
TOML_TYPE --> RUST_TYPE

Sources: example-configs/defconfig.toml(L48 - L54)  example-configs/output.toml(L13 - L19)  example-configs/output.rs(L13 - L19) 

Type System and Naming Conventions

The transformation process applies consistent rules for type mapping and identifier naming across output formats.

Type Mapping Rules:

TOML Type AnnotationTOML ValueRust TypeRust Value
# str"x86_64"&str"x86_64"
# uint1usize1
# uint"0xffff_ff80_0000_0000"usize0xffff_ff80_0000_0000
# [(uint, uint)][["0x1000", "0x2000"]]&[(usize, usize)]&[(0x1000, 0x2000)]

Naming Convention Transformations:

flowchart TD
subgraph subGraph2["Rust Output"]
    SNAKE["SCREAMING_SNAKE_CASETASK_STACK_SIZE"]
    MODULE["snake_case modulesection_name"]
end
subgraph subGraph1["TOML Output"]
    KEBAB_OUT["kebab-casetask-stack-size"]
    SECTION_OUT["[section-name]"]
end
subgraph subGraph0["Input Identifiers"]
    KEBAB["kebab-casetask-stack-size"]
    SECTION["[section-name]"]
end

KEBAB --> KEBAB_OUT
KEBAB --> SNAKE
SECTION --> MODULE
SECTION --> SECTION_OUT

Hexadecimal Value Processing:

Large hexadecimal values in TOML strings are parsed and converted to native Rust integer literals:

  • Input: kernel-base-vaddr = "0xffff_ff80_0020_0000"
  • Output: pub const KERNEL_BASE_VADDR: usize = 0xffff_ff80_0020_0000;

Sources: example-configs/defconfig.toml(L29)  example-configs/output.rs(L52)  example-configs/defconfig.toml(L32)  example-configs/output.rs(L62) 

Comment and Documentation Preservation

Comments from the input TOML are preserved and transformed appropriately for each output format, maintaining documentation context across transformations.

Comment Transformation Rules:

Input LocationTOML OutputRust Output
Line commentsPreserved as-isConverted to///doc comments
Section headersPreserved with formattingConverted to module doc comments
Type annotationsPreserved inlineEmbedded in type signatures

The generated outputs maintain full traceability to the original configuration intent while adapting to the idioms and conventions of their respective formats.

Sources: example-configs/defconfig.toml(L1 - L63)  example-configs/output.toml(L1 - L63)  example-configs/output.rs(L1 - L66) 

Development Guide

Relevant source files

This document provides essential information for developers contributing to the axconfig-gen project. It covers the build system, dependency management, testing procedures, and continuous integration processes. This guide is intended for contributors who need to understand the project structure, build the codebase locally, run tests, and submit changes.

For usage instructions of the CLI tool and library API, see axconfig-gen Package. For procedural macro usage, see axconfig-macros Package. For detailed build system information, see Build System and Dependencies. For testing procedures, see Testing. For CI/CD pipeline details, see Continuous Integration.

Project Structure Overview

The axconfig-gen repository is organized as a Cargo workspace containing two complementary packages that provide different interfaces to the same core configuration processing functionality.

Workspace Architecture

flowchart TD
subgraph subGraph3["Build Artifacts"]
    BIN["axconfig-gen binary"]
    CRATE["axconfig-gen crate"]
    PROC_CRATE["axconfig-macros crate"]
end
subgraph subGraph2["axconfig-macros Package"]
    MAC_LIB["src/lib.rsparse_configs!, include_configs!"]
    TESTS["tests/integration tests"]
end
subgraph subGraph1["axconfig-gen Package"]
    CLI["src/main.rsCLI entry point"]
    LIB["src/lib.rslibrary API"]
    CONFIG["src/config.rsConfig, ConfigItem"]
    VALUE["src/value.rsConfigValue"]
    TY["src/ty.rsConfigType"]
    OUTPUT["src/output.rsoutput generation"]
end
subgraph subGraph0["Cargo Workspace"]
    WS["Cargo.tomlworkspace root"]
    AG["axconfig-gen/CLI tool & library"]
    AM["axconfig-macros/procedural macros"]
end

AG --> CLI
AG --> CONFIG
AG --> LIB
AG --> OUTPUT
AG --> TY
AG --> VALUE
AM --> AG
AM --> MAC_LIB
AM --> TESTS
CLI --> BIN
LIB --> CRATE
MAC_LIB --> PROC_CRATE
WS --> AG
WS --> AM

Workspace Configuration: The project uses a Cargo workspace with resolver = "2" for improved dependency resolution. Both packages share common metadata including version, authors, and licensing information.

Sources: Cargo.toml(L1 - L18) 

Dependency Management

flowchart TD
subgraph subGraph2["Transitive Dependencies"]
    INDEXMAP["indexmap 2.9.0"]
    TOML_DATETIME["toml_datetime 0.6.11"]
    WINNOW["winnow 0.7.11"]
    CLAP_BUILDER["clap_builder 4.5.40"]
    CLAP_DERIVE["clap_derive 4.5.40"]
end
subgraph subGraph1["axconfig-macros Dependencies"]
    AM_PKG["axconfig-macros"]
    PROC_MACRO2["proc-macro2 1.0.95procedural macro support"]
    QUOTE["quote 1.0.40code generation"]
    SYN["syn 2.0.102Rust parsing"]
end
subgraph subGraph0["axconfig-gen Dependencies"]
    AG_PKG["axconfig-gen"]
    CLAP["clap 4.5.40CLI argument parsing"]
    TOML_EDIT["toml_edit 0.22.27TOML manipulation"]
end

AG_PKG --> CLAP
AG_PKG --> TOML_EDIT
AM_PKG --> AG_PKG
AM_PKG --> PROC_MACRO2
AM_PKG --> QUOTE
AM_PKG --> SYN
CLAP --> CLAP_BUILDER
CLAP --> CLAP_DERIVE
TOML_EDIT --> INDEXMAP
TOML_EDIT --> TOML_DATETIME
TOML_EDIT --> WINNOW

Key Dependencies:

  • clap: Provides command-line argument parsing with derive macros
  • toml_edit: Enables TOML parsing and manipulation while preserving formatting
  • proc-macro2, quote, syn: Standard procedural macro toolkit for code generation

Sources: Cargo.lock(L56 - L71) 

Development Workflow

Building the Project

The project can be built using standard Cargo commands from the workspace root:

# Build both packages
cargo build

# Build with optimizations
cargo build --release

# Build specific package
cargo build -p axconfig-gen
cargo build -p axconfig-macros

Development Environment Setup

flowchart TD
subgraph subGraph2["Package Development"]
    CLI_DEV["axconfig-gen CLI testing"]
    LIB_DEV["library API development"]
    MACRO_DEV["procedural macro development"]
end
subgraph subGraph1["Build Process"]
    BUILD["cargo build"]
    TEST["cargo test"]
    CLIPPY["cargo clippy"]
    FMT["cargo fmt"]
end
subgraph subGraph0["Local Development"]
    CLONE["git clone repository"]
    RUSTUP["Rust toolchain 1.76+"]
    DEPS["cargo fetch dependencies"]
end

BUILD --> CLI_DEV
BUILD --> LIB_DEV
BUILD --> MACRO_DEV
BUILD --> TEST
CLIPPY --> FMT
CLONE --> RUSTUP
DEPS --> BUILD
RUSTUP --> DEPS
TEST --> CLIPPY

Minimum Rust Version: The project requires Rust 1.76 or later as specified in the workspace configuration.

Sources: Cargo.toml(L17) 

Package-Specific Development

axconfig-gen Development

The axconfig-gen package contains both the CLI tool and the core library functionality. Development typically involves:

axconfig-macros Development

The axconfig-macros package focuses on procedural macro implementation:

Code Organization Patterns

The codebase follows several key organizational patterns:

  1. Separation of Concerns: CLI tool, library API, and procedural macros are clearly separated
  2. Shared Core Logic: Both packages use the same core configuration processing logic
  3. Type-Driven Design: Strong type system with ConfigType, ConfigValue, and Config abstractions
  4. Dual Output Modes: Support for both file-based generation and compile-time code generation

Common Development Tasks

Adding New Configuration Types

When extending the type system, developers typically need to:

  1. Extend ConfigType enum in axconfig-gen/src/ty.rs
  2. Update ConfigValue handling in axconfig-gen/src/value.rs
  3. Modify output generation in axconfig-gen/src/output.rs
  4. Add corresponding tests for both CLI and macro interfaces

Extending Output Formats

New output formats require:

  1. Extending the output generation system in axconfig-gen/src/output.rs
  2. Adding CLI flags in axconfig-gen/src/main.rs
  3. Updating both file-based and macro-based code generation paths

Cross-Package Coordination

Since axconfig-macros depends on axconfig-gen, changes to the core library API require careful coordination:

  1. Make API changes in axconfig-gen first
  2. Update axconfig-macros to use new API
  3. Ensure backward compatibility or coordinate breaking changes
  4. Test both packages together to verify integration

Sources: Cargo.lock(L56 - L71)  Cargo.toml(L1 - L18) 

Build System and Dependencies

Relevant source files

This page documents the Cargo workspace structure, dependency management, and local build configuration for the axconfig-gen repository. It covers the multi-crate workspace organization, external dependency requirements, and the build process for both CLI tools and procedural macros.

For information about testing procedures, see Testing. For details about continuous integration workflows, see Continuous Integration.

Workspace Structure

The axconfig-gen repository is organized as a Cargo workspace containing two primary crates that work together to provide configuration processing capabilities.

Workspace Configuration

The workspace is defined in the root Cargo.toml(L1 - L18)  with resolver = "2" enabling the newer dependency resolver. This configuration establishes shared metadata across all workspace members including version 0.2.1, Rust edition 2021, and a minimum Rust version requirement of 1.76.

flowchart TD
subgraph subGraph2["Cargo Workspace"]
    ROOT["Cargo.tomlresolver = '2'"]
    subgraph subGraph1["Shared Metadata"]
        VER["version = '0.2.1'"]
        ED["edition = '2021'"]
        RUST["rust-version = '1.76'"]
        LIC["GPL-3.0 OR Apache-2.0 OR MulanPSL-2.0"]
    end
    subgraph subGraph0["Workspace Members"]
        AXGEN["axconfig-genCLI tool & library"]
        AXMAC["axconfig-macrosProcedural macros"]
    end
end

ROOT --> AXGEN
ROOT --> AXMAC
ROOT --> ED
ROOT --> LIC
ROOT --> RUST
ROOT --> VER

Workspace Member Dependencies

The workspace defines an explicit dependency relationship where axconfig-macros depends on axconfig-gen, allowing the procedural macros to reuse the core configuration processing logic.

Sources: Cargo.toml(L1 - L18)  Cargo.lock(L56 - L71) 

Dependency Architecture

The project maintains a clean separation between CLI/library functionality and macro functionality through its dependency structure.

Core Dependencies by Crate

CrateDirect DependenciesPurpose
axconfig-genclap,toml_editCLI argument parsing and TOML manipulation
axconfig-macrosaxconfig-gen,proc-macro2,quote,synProcedural macro infrastructure and core logic reuse

External Dependency Graph

flowchart TD
subgraph subGraph3["axconfig-macros Dependencies"]
    AXMAC["axconfig-macrosv0.2.1"]
    PROC_MACRO2["proc-macro2v1.0.95"]
    QUOTE["quotev1.0.40"]
    SYN["synv2.0.102"]
end
subgraph subGraph2["axconfig-gen Dependencies"]
    AXGEN["axconfig-genv0.2.1"]
    CLAP["clapv4.5.40"]
    TOML_EDIT["toml_editv0.22.27"]
    subgraph subGraph1["toml_edit Dependencies"]
        INDEXMAP["indexmap"]
        TOML_DATETIME["toml_datetime"]
        WINNOW["winnow"]
    end
    subgraph subGraph0["clap Dependencies"]
        CLAP_BUILDER["clap_builder"]
        CLAP_DERIVE["clap_derive"]
        ANSTREAM["anstream"]
    end
end

AXGEN --> CLAP
AXGEN --> TOML_EDIT
AXMAC --> AXGEN
AXMAC --> PROC_MACRO2
AXMAC --> QUOTE
AXMAC --> SYN
CLAP --> CLAP_BUILDER
CLAP --> CLAP_DERIVE
TOML_EDIT --> INDEXMAP
TOML_EDIT --> TOML_DATETIME
TOML_EDIT --> WINNOW

Key Dependency Roles

  • clap (v4.5.40): Provides command-line argument parsing with derive macros for the CLI interface
  • toml_edit (v0.22.27): Enables TOML document parsing and manipulation while preserving formatting
  • proc-macro2 (v1.0.95): Low-level procedural macro token stream manipulation
  • quote (v1.0.40): Template-based Rust code generation for macro expansion
  • syn (v2.0.102): Rust syntax tree parsing for macro input processing

Sources: Cargo.lock(L56 - L61)  Cargo.lock(L64 - L71)  Cargo.lock(L74 - L81)  Cargo.lock(L207 - L216) 

Build Process and Requirements

Environment Requirements

The project requires Rust 1.76 or later as specified in Cargo.toml(L17)  This minimum version ensures compatibility with the procedural macro features and dependency requirements used throughout the codebase.

Build Commands

CommandPurposeOutput
cargo buildBuild all workspace membersTarget binaries and libraries
cargo build --bin axconfig-genBuild only CLI tooltarget/debug/axconfig-gen
cargo build --releaseOptimized buildRelease binaries intarget/release/
cargo install --path axconfig-genInstall CLI globallySystem-wideaxconfig-gencommand

Build Flow

flowchart TD
subgraph subGraph3["Build Process"]
    START["cargo build"]
    subgraph subGraph2["Output Generation"]
        BIN["target/debug/axconfig-genexecutable"]
        LIB["libaxconfig_gen.rliblibrary"]
        PROC["libaxconfig_macros.soproc-macro"]
    end
    subgraph subGraph1["Workspace Compilation"]
        AXGEN_BUILD["Compile axconfig-genCLI + library"]
        AXMAC_BUILD["Compile axconfig-macrosproc-macros"]
    end
    subgraph subGraph0["Dependency Resolution"]
        LOCK["Cargo.lockdependency versions"]
        FETCH["Download & compileexternal dependencies"]
    end
end

AXGEN_BUILD --> AXMAC_BUILD
AXGEN_BUILD --> BIN
AXGEN_BUILD --> LIB
AXMAC_BUILD --> PROC
FETCH --> AXGEN_BUILD
LOCK --> FETCH
START --> LOCK

Cross-Crate Compilation Order

The build system automatically handles the dependency order, compiling axconfig-gen first since axconfig-macros depends on it. This ensures the library interface is available during procedural macro compilation.

Sources: Cargo.toml(L4 - L7)  Cargo.lock(L56 - L71) 

Development Dependencies

Transitive Dependency Analysis

The Cargo.lock(L1 - L317)  reveals a total of 31 crates in the complete dependency tree. Key transitive dependencies include:

  • Windows Support: windows-sys and related platform-specific crates for cross-platform CLI functionality
  • String Processing: unicode-ident, memchr, utf8parse for robust text handling
  • Data Structures: hashbrown, indexmap for efficient key-value storage in TOML processing

Licensing Compatibility

The workspace uses a tri-license approach: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0 as specified in Cargo.toml(L13)  All external dependencies maintain compatible licensing terms, ensuring legal compliance for distribution.

Version Management

The workspace employs pinned versions in Cargo.lock(L1 - L317)  to ensure reproducible builds across different environments. Major dependency versions are:

  • CLI ecosystem: clap 4.x series with stable API
  • TOML processing: toml_edit 0.22.x with advanced editing capabilities
  • Macro ecosystem: proc-macro2, quote, syn 1.x/2.x series with mature APIs

Sources: Cargo.toml(L9 - L17)  Cargo.lock(L1 - L317) 

Testing

Relevant source files

This page covers the testing infrastructure and test organization for the axconfig-gen project. It includes unit tests for core functionality, integration tests for end-to-end validation, and macro-specific tests for procedural macro behavior.

For information about the build system setup, see Build System and Dependencies. For details about the continuous integration pipeline, see Continuous Integration.

Test Architecture Overview

The testing strategy is organized into three main categories: unit tests for individual components, integration tests for complete workflows, and macro-specific tests for procedural macro functionality.

flowchart TD
subgraph subGraph2["Test Data Sources"]
    EXAMPLE_CONFIGS["Example Configurationsexample-configs/defconfig.toml"]
    EXPECTED_OUTPUT["Expected Outputsexample-configs/output.rs"]
    INLINE_DATA["Inline Test DataHardcoded in test functions"]
end
subgraph subGraph1["Test Targets"]
    TYPE_SYS["Type SystemConfigType, ConfigValue"]
    VALUE_SYS["Value ProcessingTOML parsing, validation"]
    OUTPUT_SYS["Output GenerationRust/TOML formatting"]
    MACRO_SYS["Macro Functionalityinclude_configs!, parse_configs!"]
end
subgraph subGraph0["Test Categories"]
    UNIT["Unit Testsaxconfig-gen/src/tests.rs"]
    INTEGRATION["Integration TestsExample-based validation"]
    MACRO["Macro Testsaxconfig-macros/tests/"]
end

EXAMPLE_CONFIGS --> INTEGRATION
EXAMPLE_CONFIGS --> MACRO
EXPECTED_OUTPUT --> INTEGRATION
EXPECTED_OUTPUT --> MACRO
INLINE_DATA --> UNIT
INTEGRATION --> OUTPUT_SYS
INTEGRATION --> VALUE_SYS
MACRO --> MACRO_SYS
UNIT --> OUTPUT_SYS
UNIT --> TYPE_SYS
UNIT --> VALUE_SYS

Sources: axconfig-gen/src/tests.rs(L1 - L192)  axconfig-macros/tests/example_config.rs(L1 - L87) 

Unit Test Structure

The unit tests in axconfig-gen cover individual components of the configuration system, focusing on type inference, value validation, and code generation.

Type System Testing

The type inference and validation system is tested through the test_type_infer and test_type_match functions, which verify that TOML values are correctly mapped to Rust types.


Sources: axconfig-gen/src/tests.rs(L23 - L59)  axconfig-gen/src/tests.rs(L61 - L115) 

Code Generation Testing

The test_to_rust function validates that TOML values are correctly converted to Rust code with proper formatting and indentation.

Test CaseInput TOMLExpected Rust OutputPurpose
Nested Arrays[[(uint, (str, str), uint)]]&[&[(usize, (&str, &str), usize)]]Complex nesting validation
Mixed Arrays[[(uint, [str], uint)]]&[&[(usize, &[&str], usize)]]Variable-length arrays
IndentationMulti-level arraysProperly indented codeFormatting correctness

Sources: axconfig-gen/src/tests.rs(L137 - L180) 

Integration Testing

Integration tests verify the complete pipeline from TOML input to generated output, ensuring that all components work together correctly.

sequenceDiagram
    participant integration_test as "integration_test()"
    participant FileSystem as "File System"
    participant Configfrom_toml as "Config::from_toml()"
    participant Configdump as "Config::dump()"

    integration_test ->> FileSystem: Read defconfig.toml
    integration_test ->> FileSystem: Read expected output.toml
    integration_test ->> FileSystem: Read expected output.rs
    integration_test ->> Configfrom_toml: Parse TOML specification
    Configfrom_toml ->> Configdump: Generate TOML output
    Configfrom_toml ->> Configdump: Generate Rust output
    integration_test ->> integration_test: Assert outputs match expected

The integration test reads example configuration files and compares the generated outputs against reference files to ensure consistency across the entire processing pipeline.

Sources: axconfig-gen/src/tests.rs(L182 - L191) 

Macro Testing Framework

The macro tests in axconfig-macros verify that procedural macros generate the same output as the CLI tool, ensuring consistency between compile-time and build-time processing.

Test Structure

flowchart TD
subgraph subGraph2["Test Configuration"]
    DEFCONFIG["../example-configs/defconfig.tomlInput configuration"]
    OUTPUT_RS["../../example-configs/output.rsExpected constants"]
end
subgraph subGraph1["Comparison Framework"]
    MOD_CMP["mod_cmp! macroField-by-field comparison"]
    ASSERT_EQ["assert_eq! callsValue verification"]
end
subgraph subGraph0["Macro Test Modules"]
    CONFIG_MOD["config moduleinclude_configs! macro"]
    CONFIG2_MOD["config2 moduleparse_configs! macro"]
    EXPECT_MOD["config_expect moduleExpected output include"]
end

CONFIG2_MOD --> MOD_CMP
CONFIG_MOD --> MOD_CMP
DEFCONFIG --> CONFIG2_MOD
DEFCONFIG --> CONFIG_MOD
EXPECT_MOD --> MOD_CMP
MOD_CMP --> ASSERT_EQ
OUTPUT_RS --> EXPECT_MOD

Sources: axconfig-macros/tests/example_config.rs(L4 - L15) 

Comparison Strategy

The mod_cmp! macro systematically compares all generated constants across different modules to ensure consistency:

CategoryConstants TestedPurpose
GlobalARCH,PLAT,SMPCore system configuration
DevicesMMIO_REGIONS,PCI_,VIRTIO_Hardware abstraction
KernelTASK_STACK_SIZE,TICKS_PER_SECRuntime parameters
PlatformKERNEL_,PHYS_Memory layout

Sources: axconfig-macros/tests/example_config.rs(L17 - L75) 

Running Tests

Test Execution Commands

# Run all tests in the workspace
cargo test

# Run unit tests only (axconfig-gen)
cargo test -p axconfig-gen

# Run macro tests only (axconfig-macros)
cargo test -p axconfig-macros

# Run specific test functions
cargo test test_type_infer
cargo test integration_test

Feature-Specific Testing

The macro tests include conditional compilation for nightly features:

#![allow(unused)]
fn main() {
#[cfg(feature = "nightly")]
#[test]
fn test_parse_configs() {
    mod_cmp!(config2, config_expect);
}
}

Sources: axconfig-macros/tests/example_config.rs(L82 - L86) 

Test Data Management

Example Configuration Files

The test suite relies on shared example configuration files located in the example-configs directory:

  • defconfig.toml - Input TOML configuration
  • output.toml - Expected TOML output
  • output.rs - Expected Rust code output

These files serve as the source of truth for both integration tests and macro tests, ensuring consistency across test categories.

Sources: axconfig-macros/tests/example_config.rs(L5)  axconfig-gen/src/tests.rs(L184 - L186) 

Adding New Tests

Unit Test Guidelines

When adding unit tests to axconfig-gen/src/tests.rs follow these patterns:

  1. Use the check_type_infer() helper for type inference tests
  2. Use the assert_err! macro for error condition validation
  3. Include both positive and negative test cases
  4. Test edge cases like empty arrays and complex nesting

Integration Test Additions

For new integration tests:

  1. Add test configuration files to example-configs/
  2. Update the integration_test() function to include new scenarios
  3. Ensure both TOML and Rust output validation

Macro Test Extensions

When extending macro tests:

  1. Update the mod_cmp! macro to include new configuration fields
  2. Add conditional compilation for feature-specific functionality
  3. Maintain consistency with the expected output format

Sources: axconfig-gen/src/tests.rs(L14 - L21)  axconfig-macros/tests/example_config.rs(L17 - L75) 

Continuous Integration

Relevant source files

This document covers the automated continuous integration and deployment pipeline for the axconfig-gen repository. The CI system ensures code quality, runs comprehensive tests, and automatically deploys documentation. For information about manual testing procedures, see Testing. For details about the build system configuration, see Build System and Dependencies.

Pipeline Overview

The CI system is implemented using GitHub Actions with a workflow defined in .github/workflows/ci.yml(L1 - L53)  The pipeline consists of two primary jobs that run on every push and pull request event.

CI Workflow Structure

flowchart TD
subgraph Outputs["Outputs"]
    QUALITY["Code Quality Validation"]
    ARTIFACTS["Build Artifacts"]
    DOCS["Published Documentation"]
    PAGES["GitHub Pages Deployment"]
end
subgraph subGraph3["GitHub Actions Workflow"]
    WORKFLOW["ci.yml workflow"]
    subgraph subGraph2["doc job"]
        CHECKOUT_DOC["actions/checkout@v4"]
        RUST_SETUP_DOC["dtolnay/rust-toolchain@nightly"]
        BUILD_DOCS["cargo doc --no-deps --all-features"]
        DEPLOY["JamesIves/github-pages-deploy-action@v4"]
    end
    subgraph subGraph1["ci job"]
        CHECKOUT_CI["actions/checkout@v4"]
        RUST_SETUP["dtolnay/rust-toolchain@nightly"]
        VERSION_CHECK["rustc --version --verbose"]
        FORMAT_CHECK["cargo fmt --all -- --check"]
        CLIPPY["cargo clippy --target x86_64-unknown-linux-gnu --all-features"]
        BUILD["cargo build --target x86_64-unknown-linux-gnu --all-features"]
        TEST["cargo test --target x86_64-unknown-linux-gnu -- --nocapture"]
    end
end
subgraph subGraph0["Trigger Events"]
    PUSH["push event"]
    PR["pull_request event"]
end

BUILD --> ARTIFACTS
BUILD --> TEST
BUILD_DOCS --> DEPLOY
BUILD_DOCS --> DOCS
CHECKOUT_CI --> RUST_SETUP
CHECKOUT_DOC --> RUST_SETUP_DOC
CLIPPY --> BUILD
DEPLOY --> PAGES
FORMAT_CHECK --> CLIPPY
PR --> WORKFLOW
PUSH --> WORKFLOW
RUST_SETUP --> VERSION_CHECK
RUST_SETUP_DOC --> BUILD_DOCS
TEST --> QUALITY
VERSION_CHECK --> FORMAT_CHECK
WORKFLOW --> CHECKOUT_CI
WORKFLOW --> CHECKOUT_DOC

Sources: .github/workflows/ci.yml(L1 - L53) 

Job Definitions and Matrix Strategy

The CI workflow uses a matrix strategy for the ci job to support multiple Rust toolchains and target architectures, though currently configured for a single combination.

CI Job Configuration

ParameterValue
runs-onubuntu-latest
rust-toolchainnightly
targetsx86_64-unknown-linux-gnu
fail-fastfalse

The job includes comprehensive Rust toolchain setup with required components:

flowchart TD
subgraph subGraph1["Setup Action"]
    DTOLNAY["dtolnay/rust-toolchain@nightly"]
end
subgraph subGraph0["Rust Toolchain Components"]
    TOOLCHAIN["nightly toolchain"]
    RUST_SRC["rust-src component"]
    CLIPPY_COMP["clippy component"]
    RUSTFMT_COMP["rustfmt component"]
    TARGET["x86_64-unknown-linux-gnu target"]
end

DTOLNAY --> CLIPPY_COMP
DTOLNAY --> RUSTFMT_COMP
DTOLNAY --> RUST_SRC
DTOLNAY --> TARGET
DTOLNAY --> TOOLCHAIN

Sources: .github/workflows/ci.yml(L8 - L19) 

Quality Assurance Process

The CI pipeline implements a multi-stage quality assurance process that validates code formatting, performs static analysis, builds the project, and runs tests.

Quality Gates Sequence

StageCommandPurpose
Version Checkrustc --version --verboseVerify toolchain installation
Format Checkcargo fmt --all -- --checkEnforce code formatting standards
Static Analysiscargo clippy --target x86_64-unknown-linux-gnu --all-featuresDetect potential issues and style violations
Buildcargo build --target x86_64-unknown-linux-gnu --all-featuresEnsure compilation succeeds
Testcargo test --target x86_64-unknown-linux-gnu -- --nocaptureExecute unit and integration tests

The pipeline uses the --all-features flag to ensure both axconfig-gen and axconfig-macros packages are tested with all feature combinations enabled.

Sources: .github/workflows/ci.yml(L20 - L29) 

Documentation Pipeline

The doc job handles automated documentation generation and deployment to GitHub Pages with conditional execution based on the repository branch.

Documentation Workflow

flowchart TD
subgraph subGraph3["GitHub Pages"]
    GH_PAGES["gh-pages branch"]
    SINGLE_COMMIT["single-commit: true"]
end
subgraph subGraph2["Deployment Logic"]
    BRANCH_CHECK["github.ref == refs/heads/main"]
    CONTINUE_ERROR["continue-on-error for non-main branches"]
    DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph1["Documentation Generation"]
    CARGO_DOC["cargo doc --no-deps --all-features"]
    TARGET_DOC["target/doc output directory"]
end
subgraph subGraph0["Documentation Environment"]
    ENV_VAR["default-branch environment variable"]
    RUSTDOCFLAGS["RUSTDOCFLAGS with unstable options"]
    PERMISSIONS["contents: write permissions"]
end

BRANCH_CHECK --> DEPLOY_ACTION
CARGO_DOC --> TARGET_DOC
DEPLOY_ACTION --> GH_PAGES
DEPLOY_ACTION --> SINGLE_COMMIT
ENV_VAR --> BRANCH_CHECK
RUSTDOCFLAGS --> CARGO_DOC
TARGET_DOC --> DEPLOY_ACTION

The documentation build uses specific RUSTDOCFLAGS configuration:

  • -Zunstable-options --enable-index-page: Enables unstable documentation features
  • -D rustdoc::broken_intra_doc_links: Treats broken internal links as errors
  • -D missing-docs: Treats missing documentation as errors

Sources: .github/workflows/ci.yml(L31 - L53) 

Deployment and Automation

The CI system implements conditional deployment logic that ensures documentation is only published from the main branch while allowing documentation builds to continue on other branches for validation.

Deployment Configuration

SettingValuePurpose
single-committrueMaintains clean gh-pages history
branchgh-pagesTarget branch for documentation
foldertarget/docSource directory for documentation files

The deployment uses the JamesIves/github-pages-deploy-action@v4 action with conditional execution based on github.ref == env.default-branch to ensure only main branch changes trigger actual deployment.

Sources: .github/workflows/ci.yml(L46 - L52) 

Overview

Relevant source files

Purpose and Scope

The page_table_multiarch repository provides a generic, unified, architecture-independent page table management library for Rust systems programming. This library enables operating systems, hypervisors, and bare-metal applications to manage virtual memory translation across multiple hardware architectures through a single, consistent API.

The repository implements hardware abstraction for page table operations on x86_64, AArch64, RISC-V, and LoongArch64 architectures, providing both low-level page table entry manipulation and high-level page table management functionality. For detailed information about individual architecture implementations, see Architecture Support. For development and contribution guidelines, see Development Guide.

Sources: README.md(L1 - L16)  Cargo.toml(L17 - L18) 

System Architecture

The library implements a layered architecture that separates generic page table operations from architecture-specific implementations through Rust's trait system.

Core System Structure

flowchart TD
subgraph subGraph3["Architecture Implementations"]
    X86_IMPL["X64PageTableX64PTEX64PagingMetaData"]
    ARM_IMPL["A64PageTableA64PTEA64PagingMetaData"]
    RV_IMPL["Sv39PageTable, Sv48PageTableRv64PTESvPagingMetaData"]
    LA_IMPL["LA64PageTableLA64PTELA64PagingMetaData"]
end
subgraph subGraph2["Trait Abstraction Layer"]
    PMD["PagingMetaData trait"]
    GPTE["GenericPTE trait"]
    PH["PagingHandler trait"]
end
subgraph subGraph1["Generic API Layer"]
    PT64["PageTable64<M,PTE,H>"]
    API["map(), unmap(), protect()"]
    FLAGS["MappingFlags"]
end
subgraph subGraph0["Application Layer"]
    OS["Operating Systems"]
    HV["Hypervisors"]
    BM["Bare Metal Code"]
end

API --> GPTE
API --> PH
API --> PMD
BM --> PT64
GPTE --> ARM_IMPL
GPTE --> LA_IMPL
GPTE --> RV_IMPL
GPTE --> X86_IMPL
HV --> PT64
OS --> PT64
PH --> ARM_IMPL
PH --> LA_IMPL
PH --> RV_IMPL
PH --> X86_IMPL
PMD --> ARM_IMPL
PMD --> LA_IMPL
PMD --> RV_IMPL
PMD --> X86_IMPL
PT64 --> API
PT64 --> FLAGS

Sources: Cargo.toml(L4 - L7)  README.md(L5 - L10) 

Workspace Structure

The repository is organized as a Rust workspace containing two interdependent crates that together provide the complete page table management functionality.

Crate Dependencies and Relationships

flowchart TD
subgraph subGraph5["page_table_multiarch Workspace"]
    WS["Cargo.tomlworkspace root"]
    PT64_TYPE["PageTable64<M,PTE,H>"]
    GPTE_TRAIT["GenericPTE trait"]
    ARCH_TABLES["X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"]
    PTE_TYPES["X64PTEA64PTERv64PTELA64PTE"]
    FLAGS_TYPE["MappingFlags"]
    subgraph subGraph4["External Dependencies"]
        MEMADDR["memory_addr"]
        LOG["log"]
        BITFLAGS["bitflags"]
    end
    subgraph subGraph3["page_table_entry crate"]
        PTE["page_table_entry"]
        PTE_LIB["lib.rs"]
        PTE_ARCH["arch/ modules"]
        subgraph subGraph2["Low-level Types"]
            GPTE_TRAIT["GenericPTE trait"]
            PTE_TYPES["X64PTEA64PTERv64PTELA64PTE"]
            FLAGS_TYPE["MappingFlags"]
        end
    end
    subgraph subGraph1["page_table_multiarch crate"]
        PTM["page_table_multiarch"]
        PTM_LIB["lib.rs"]
        PTM_BITS["bits64.rs"]
        PTM_ARCH["arch/ modules"]
        subgraph subGraph0["High-level Types"]
            PT64_TYPE["PageTable64<M,PTE,H>"]
            ARCH_TABLES["X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"]
        end
    end
end

ARCH_TABLES --> PTE_TYPES
GPTE_TRAIT --> FLAGS_TYPE
PT64_TYPE --> GPTE_TRAIT
PTE --> BITFLAGS
PTE --> MEMADDR
PTE_TYPES --> GPTE_TRAIT
PTM --> LOG
PTM --> MEMADDR
PTM --> PTE
WS --> PTE
WS --> PTM

The page_table_multiarch crate provides high-level page table management through the PageTable64 generic struct and architecture-specific type aliases. The page_table_entry crate provides low-level page table entry definitions and the GenericPTE trait that enables architecture abstraction.

Sources: Cargo.toml(L4 - L7)  README.md(L12 - L15) 

Supported Architectures

The library supports four major processor architectures, each with specific paging characteristics and implementation details.

Architecture Support Matrix

ArchitecturePage Table LevelsVirtual Address WidthPhysical Address WidthImplementation Types
x86_644 levels48-bit52-bitX64PageTable,X64PTE
AArch644 levels48-bit48-bitA64PageTable,A64PTE
RISC-V Sv393 levels39-bit56-bitSv39PageTable,Rv64PTE
RISC-V Sv484 levels48-bit56-bitSv48PageTable,Rv64PTE
LoongArch644 levels48-bit48-bitLA64PageTable,LA64PTE

Each architecture implementation provides the same generic interface through the trait system while handling architecture-specific page table formats, address translation mechanisms, and memory attribute encodings.

Sources: README.md(L5 - L10)  CHANGELOG.md(L21) 

Key Abstractions

The library's architecture independence is achieved through three core traits that define the interface between generic and architecture-specific code.

Trait System Overview

flowchart TD
subgraph subGraph4["Generic Implementation"]
    PT64_STRUCT["PageTable64<M: PagingMetaData,PTE: GenericPTE,H: PagingHandler>"]
end
subgraph subGraph3["PagingHandler Methods"]
    PH_METHODS["alloc_frame()dealloc_frame()phys_to_virt()"]
end
subgraph subGraph2["GenericPTE Methods"]
    GPTE_METHODS["new_page()new_table()paddr()flags()is_present()is_huge()empty()"]
end
subgraph subGraph1["PagingMetaData Methods"]
    PMD_METHODS["PAGE_SIZEVADDR_SIZEPADDR_SIZEENTRY_COUNTMAX_LEVEL"]
end
subgraph subGraph0["Core Trait Definitions"]
    PMD_TRAIT["PagingMetaData"]
    GPTE_TRAIT["GenericPTE"]
    PH_TRAIT["PagingHandler"]
end
API_METHODS["map()unmap()protect()map_region()unmap_region()protect_region()"]

GPTE_TRAIT --> GPTE_METHODS
GPTE_TRAIT --> PT64_STRUCT
PH_TRAIT --> PH_METHODS
PH_TRAIT --> PT64_STRUCT
PMD_TRAIT --> PMD_METHODS
PMD_TRAIT --> PT64_STRUCT
PT64_STRUCT --> API_METHODS

The PagingMetaData trait defines architecture constants, GenericPTE provides page table entry manipulation methods, and PagingHandler abstracts memory allocation and address translation for the operating system interface.

Sources: README.md(L3)  CHANGELOG.md(L7)  CHANGELOG.md(L61 - L63) 

System Architecture

Relevant source files

This document explains the overall design philosophy, abstraction layers, and architecture independence mechanisms of the page_table_multiarch library. The purpose is to provide a comprehensive understanding of how the system achieves unified page table management across multiple processor architectures through a layered abstraction approach.

For detailed information about specific processor architectures, see Architecture Support. For implementation details of the core abstractions, see Core Concepts.

Design Philosophy

The page_table_multiarch library implements a generic, unified, architecture-independent approach to page table management. The system separates architecture-specific concerns from generic page table operations through a trait-based abstraction layer that allows the same high-level API to work across x86_64, AArch64, RISC-V, and LoongArch64 platforms.

Core Abstraction Model

flowchart TD
subgraph subGraph4["Hardware Layer"]
    X86HW["x86_64 MMU"]
    ARMHW["AArch64 MMU"]
    RVHW["RISC-V MMU"]
    LAHW["LoongArch64 MMU"]
end
subgraph subGraph3["Architecture Implementation Layer"]
    X86Meta["X64PagingMetaData"]
    ARMeta["A64PagingMetaData"]
    RVMeta["Sv39/Sv48MetaData"]
    LAMeta["LA64MetaData"]
    X86PTE["X64PTE"]
    ARMPTE["A64PTE"]
    RVPTE["Rv64PTE"]
    LAPTE["LA64PTE"]
end
subgraph subGraph2["Trait Abstraction Layer"]
    PMD["PagingMetaData trait"]
    GPTE["GenericPTE trait"]
    PH["PagingHandler trait"]
end
subgraph subGraph1["Generic Interface Layer"]
    PT64["PageTable64<M,PTE,H>"]
    API["Unified API Methods"]
end
subgraph subGraph0["Application Layer"]
    App["OS/Hypervisor Code"]
end

API --> GPTE
API --> PH
API --> PMD
ARMPTE --> ARMHW
ARMeta --> ARMHW
App --> PT64
GPTE --> ARMPTE
GPTE --> LAPTE
GPTE --> RVPTE
GPTE --> X86PTE
LAMeta --> LAHW
LAPTE --> LAHW
PMD --> ARMeta
PMD --> LAMeta
PMD --> RVMeta
PMD --> X86Meta
PT64 --> API
RVMeta --> RVHW
RVPTE --> RVHW
X86Meta --> X86HW
X86PTE --> X86HW

Sources: page_table_multiarch/src/lib.rs(L9 - L19)  page_table_multiarch/README.md(L9 - L20) 

Workspace Architecture

The system is organized as a two-crate Cargo workspace that separates high-level page table management from low-level page table entry definitions:

Crate Dependency Structure

flowchart TD
subgraph subGraph3["page_table_multiarch Workspace"]
    PTM["page_table_multiarch crate"]
    PTELib["lib.rs"]
    PTEArch["arch/ modules"]
    PTMLib["lib.rs"]
    PTMBits["bits64.rs"]
    subgraph subGraph2["External Dependencies"]
        MemAddr["memory_addr"]
        Log["log"]
        Bitflags["bitflags"]
    end
    subgraph subGraph0["PTM Modules"]
        PTE["page_table_entry crate"]
        PTMArch["arch/ modules"]
        subgraph subGraph1["PTE Modules"]
            PTM["page_table_multiarch crate"]
            PTELib["lib.rs"]
            PTEArch["arch/ modules"]
            PTMLib["lib.rs"]
            PTMBits["bits64.rs"]
        end
    end
end

PTE --> Bitflags
PTE --> MemAddr
PTELib --> PTEArch
PTM --> Log
PTM --> MemAddr
PTM --> PTE
PTMLib --> PTMArch
PTMLib --> PTMBits
CratePurposeKey Exports
page_table_multiarchHigh-level page table abstractionsPageTable64,PagingMetaData,PagingHandler
page_table_entryLow-level page table entry definitionsGenericPTE,MappingFlags

Sources: page_table_multiarch/src/lib.rs(L15 - L19)  page_table_entry/src/lib.rs(L10) 

Core Trait System

The architecture independence is achieved through three primary traits that define contracts between generic and architecture-specific code:

Trait Relationships and Responsibilities

flowchart TD
subgraph subGraph2["Trait Methods & Constants"]
    PMDMethods["LEVELS: usizePA_MAX_BITS: usizeVA_MAX_BITS: usizeVirtAddr: MemoryAddrflush_tlb()"]
    GPTEMethods["new_page()new_table()paddr()flags()is_present()is_huge()"]
    PHMethods["alloc_frame()dealloc_frame()phys_to_virt()"]
end
subgraph subGraph1["Core Traits"]
    PMDTrait["PagingMetaData"]
    GPTETrait["GenericPTE"]
    PHTrait["PagingHandler"]
end
subgraph subGraph0["Generic Types"]
    PT64Struct["PageTable64<M,PTE,H>"]
    TlbFlush["TlbFlush<M>"]
    TlbFlushAll["TlbFlushAll<M>"]
    MFlags["MappingFlags"]
    PSize["PageSize"]
end

GPTETrait --> GPTEMethods
GPTETrait --> MFlags
PHTrait --> PHMethods
PMDTrait --> PMDMethods
PT64Struct --> GPTETrait
PT64Struct --> PHTrait
PT64Struct --> PMDTrait
TlbFlush --> PMDTrait
TlbFlushAll --> PMDTrait

Trait Responsibilities

TraitResponsibilityKey Types
PagingMetaDataArchitecture constants and TLB operationsLEVELS,PA_MAX_BITS,VA_MAX_BITS,VirtAddr
GenericPTEPage table entry manipulationEntry creation, flag handling, address extraction
PagingHandlerOS-dependent memory operationsFrame allocation, virtual-physical address translation

Sources: page_table_multiarch/src/lib.rs(L40 - L92)  page_table_entry/src/lib.rs(L38 - L68) 

Architecture Independence Mechanisms

Generic Parameter System

The PageTable64<M, PTE, H> struct uses three generic parameters to achieve architecture independence:

// From page_table_multiarch/src/lib.rs and bits64.rs
PageTable64<M: PagingMetaData, PTE: GenericPTE, H: PagingHandler>
  • M: PagingMetaData - Provides architecture-specific constants and TLB operations
  • PTE: GenericPTE - Handles architecture-specific page table entry formats
  • H: PagingHandler - Abstracts OS-specific memory management operations

Architecture-Specific Implementations

Each supported architecture provides concrete implementations of the core traits:

ArchitectureMetadata TypePTE TypeExample Usage
x86_64X64PagingMetaDataX64PTEX64PageTable
AArch64A64PagingMetaDataA64PTEA64PageTable
RISC-V Sv39Sv39MetaDataRv64PTESv39PageTable
RISC-V Sv48Sv48MetaDataRv64PTESv48PageTable
LoongArch64LA64MetaDataLA64PTELA64PageTable

Error Handling and Type Safety

The system defines a comprehensive error model through PagingError and PagingResult types:

flowchart TD
subgraph subGraph1["Error Variants"]
    NoMemory["NoMemory"]
    NotAligned["NotAligned"]
    NotMapped["NotMapped"]
    AlreadyMapped["AlreadyMapped"]
    MappedToHuge["MappedToHugePage"]
end
subgraph subGraph0["Error Types"]
    PError["PagingError"]
    PResult["PagingResult<T>"]
end

PError --> AlreadyMapped
PError --> MappedToHuge
PError --> NoMemory
PError --> NotAligned
PError --> NotMapped
PResult --> PError

Sources: page_table_multiarch/src/lib.rs(L21 - L38) 

TLB Management Architecture

The system implements a type-safe TLB (Translation Lookaside Buffer) management mechanism through specialized wrapper types:

TLB Flush Types

flowchart TD
subgraph subGraph2["Architecture Implementation"]
    FlushTLB["M::flush_tlb(Option<VirtAddr>)"]
end
subgraph subGraph1["TLB Operations"]
    FlushSingle["flush() - Single address"]
    FlushAll["flush_all() - Entire TLB"]
    Ignore["ignore() - Skip flush"]
end
subgraph subGraph0["TLB Management Types"]
    TlbFlush["TlbFlush<M>"]
    TlbFlushAll["TlbFlushAll<M>"]
end

FlushAll --> FlushTLB
FlushSingle --> FlushTLB
TlbFlush --> FlushSingle
TlbFlush --> Ignore
TlbFlushAll --> FlushAll
TlbFlushAll --> Ignore

The #[must_use] attribute ensures that TLB flush operations are not accidentally ignored, promoting system correctness.

Sources: page_table_multiarch/src/lib.rs(L130 - L172) 

Page Size Support

The system supports multiple page sizes through the PageSize enumeration:

Page SizeValueUsage
Size4K4 KiB (0x1000)Standard pages
Size2M2 MiB (0x200000)Huge pages
Size1G1 GiB (0x40000000)Huge pages

The PageSize::is_huge() method distinguishes between standard and huge pages, enabling architecture-specific optimizations for large memory mappings.

Sources: page_table_multiarch/src/lib.rs(L94 - L128) 

Supported Platforms

Relevant source files

This document provides an overview of the hardware architectures supported by the page_table_multiarch library and their specific implementation characteristics. The library provides a unified interface for page table management across multiple processor architectures through architecture-specific implementations.

For detailed information about the implementation architecture, see System Architecture. For information about building and testing across platforms, see Building and Testing.

Architecture Support Matrix

The page_table_multiarch library supports four major processor architectures through conditional compilation and architecture-specific implementations. Each architecture provides different virtual address space configurations and page table structures.

Supported Architecture Matrix

ArchitectureVirtual Address BitsPhysical Address BitsPage Table LevelsCrate Dependencies
x86_6448-bit52-bit4 levelsx86
AArch6448-bit48-bit4 levelsaarch64-cpu
RISC-V Sv3939-bit56-bit3 levelsriscv
RISC-V Sv4848-bit56-bit4 levelsriscv
LoongArch6448-bit48-bit4 levelsBuilt-in

Sources: page_table_multiarch/Cargo.toml(L20 - L24)  page_table_entry/Cargo.toml(L22 - L26)  CHANGELOG.md(L21) 

Platform Implementation Structure

Architecture-Specific Type Mapping

flowchart TD
subgraph LoongArch_Impl["LoongArch64 Implementation"]
    LA64PT["LA64PageTable"]
    LA64PTE["LA64PTE"]
    LA64MD["LA64MetaData"]
end
subgraph RISC_V_Impl["RISC-V Implementation"]
    SV39PT["Sv39PageTable"]
    SV48PT["Sv48PageTable"]
    RV64PTE["Rv64PTE"]
    SV39MD["Sv39MetaData"]
    SV48MD["Sv48MetaData"]
end
subgraph AArch64_Impl["AArch64 Implementation"]
    A64PT["A64PageTable"]
    A64PTE["A64PTE"]
    A64MD["A64PagingMetaData"]
end
subgraph x86_64_Impl["x86_64 Implementation"]
    X64PT["X64PageTable"]
    X64PTE["X64PTE"]
    X64MD["X64PagingMetaData"]
end
subgraph Generic_Interface["Generic Interface Layer"]
    PT64["PageTable64<M, PTE, H>"]
    GPTE["GenericPTE trait"]
    PMD["PagingMetaData trait"]
end

GPTE --> A64PTE
GPTE --> LA64PTE
GPTE --> RV64PTE
GPTE --> X64PTE
PMD --> A64MD
PMD --> LA64MD
PMD --> SV39MD
PMD --> SV48MD
PMD --> X64MD
PT64 --> A64PT
PT64 --> LA64PT
PT64 --> SV39PT
PT64 --> SV48PT
PT64 --> X64PT

Sources: page_table_multiarch/Cargo.toml(L1 - L28)  page_table_entry/Cargo.toml(L1 - L29) 

Conditional Compilation Dependencies

flowchart TD
subgraph Features["Optional Features"]
    ARM_EL2["arm-el2"]
end
subgraph Conditional_Deps["Conditional Dependencies"]
    X86_DEP["x86 = '0.52'"]
    X86_64_DEP["x86_64 = '0.15.2'"]
    ARM_DEP["aarch64-cpu = '10.0'"]
    RV_DEP["riscv = '0.12'"]
end
subgraph Build_Targets["Build Target Architecture"]
    X86_TARGET["x86_64"]
    ARM_TARGET["aarch64"]
    RV32_TARGET["riscv32"]
    RV64_TARGET["riscv64"]
    LA_TARGET["loongarch64"]
    DOC_TARGET["doc"]
end

ARM_TARGET --> ARM_DEP
ARM_TARGET --> ARM_EL2
DOC_TARGET --> ARM_DEP
DOC_TARGET --> ARM_EL2
DOC_TARGET --> RV_DEP
DOC_TARGET --> X86_64_DEP
DOC_TARGET --> X86_DEP
RV32_TARGET --> RV_DEP
RV64_TARGET --> RV_DEP
X86_TARGET --> X86_64_DEP
X86_TARGET --> X86_DEP

Sources: page_table_multiarch/Cargo.toml(L20 - L27)  page_table_entry/Cargo.toml(L15 - L29) 

x86_64 Platform Support

The x86_64 implementation provides support for Intel and AMD 64-bit processors using 4-level page tables with 48-bit virtual addresses and up to 52-bit physical addresses.

x86_64 Characteristics

  • Page Table Structure: 4-level paging (PML4, PDPT, PD, PT)
  • Virtual Address Space: 48 bits (256 TB)
  • Physical Address Space: Up to 52 bits (4 PB)
  • Page Sizes: 4 KB, 2 MB, 1 GB
  • Hardware Dependencies: x86 crate for low-level x86 functionality

Sources: page_table_multiarch/Cargo.toml(L20 - L21)  page_table_entry/Cargo.toml(L25 - L26) 

AArch64 Platform Support

The AArch64 implementation supports ARM 64-bit processors using the VMSAv8-64 translation table format with 4-level page tables.

AArch64 Characteristics

  • Page Table Structure: 4-level translation tables (L0, L1, L2, L3)
  • Virtual Address Space: 48 bits (256 TB)
  • Physical Address Space: 48 bits (256 TB)
  • Page Sizes: 4 KB, 2 MB, 1 GB
  • Hardware Dependencies: aarch64-cpu crate for ARM-specific functionality
  • Special Features: EL2 privilege level support via arm-el2 feature

Sources: page_table_entry/Cargo.toml(L15 - L23)  CHANGELOG.md(L47) 

RISC-V Platform Support

The RISC-V implementation supports both Sv39 (3-level) and Sv48 (4-level) virtual memory configurations, providing flexibility for different RISC-V implementations.

RISC-V Sv39 Characteristics

  • Page Table Structure: 3-level page tables
  • Virtual Address Space: 39 bits (512 GB)
  • Physical Address Space: 56 bits (64 PB)
  • Page Sizes: 4 KB, 2 MB, 1 GB

RISC-V Sv48 Characteristics

  • Page Table Structure: 4-level page tables
  • Virtual Address Space: 48 bits (256 TB)
  • Physical Address Space: 56 bits (64 PB)
  • Page Sizes: 4 KB, 2 MB, 1 GB, 512 GB

RISC-V Dependencies

  • Hardware Dependencies: riscv crate v0.12 with default features disabled
  • Target Support: Both riscv32 and riscv64 target architectures

Sources: page_table_multiarch/Cargo.toml(L23 - L24)  CHANGELOG.md(L37) 

LoongArch64 Platform Support

LoongArch64 support was added in version 0.5.1, providing page table management for LoongArch 64-bit processors with 4-level page tables by default.

LoongArch64 Characteristics

  • Page Table Structure: 4-level page tables (default configuration)
  • Virtual Address Space: 48 bits (256 TB)
  • Physical Address Space: 48 bits (256 TB)
  • Page Sizes: 4 KB, 2 MB, 1 GB
  • Hardware Dependencies: Built-in support, no external crate dependencies

Sources: CHANGELOG.md(L13 - L21) 

Build System Integration

The platform support is implemented through Cargo's conditional compilation features, ensuring that only the necessary architecture-specific dependencies are included in builds.

Target-Specific Dependencies

[target.'cfg(any(target_arch = "x86_64", doc))'.dependencies]
x86 = "0.52"

[target.'cfg(any(target_arch = "aarch64", doc))'.dependencies]
aarch64-cpu = "10.0"

[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64", doc))'.dependencies]
riscv = { version = "0.12", default-features = false }

The doc target ensures all architecture-specific code is available when building documentation, providing complete API documentation across all supported platforms.

Sources: page_table_multiarch/Cargo.toml(L20 - L27)  page_table_entry/Cargo.toml(L22 - L29) 

Workspace Structure

Relevant source files

This document explains the two-crate workspace structure of the page_table_multiarch repository and how the crates interact to provide multi-architecture page table functionality. It covers the workspace configuration, crate responsibilities, dependency management, and conditional compilation strategy.

For detailed information about the individual crate implementations, see page_table_multiarch Crate and page_table_entry Crate.

Workspace Overview

The page_table_multiarch repository is organized as a Cargo workspace containing two complementary crates that provide a layered approach to page table management across multiple hardware architectures.

Workspace Definition

The workspace is defined in the root Cargo.toml(L1 - L7)  with two member crates:

CratePurposeRole
page_table_multiarchHigh-level page table abstractionsMain API providingPageTable64and unified interface
page_table_entryLow-level page table entriesArchitecture-specific PTE implementations andGenericPTEtrait

Workspace Structure Diagram

flowchart TD
subgraph subGraph2["page_table_multiarch Workspace"]
    ROOT["Cargo.toml(Workspace Root)"]
    subgraph subGraph1["page_table_entry Crate"]
        PTE["page_table_entryPage table entry definitions"]
        PTE_CARGO["page_table_entry/Cargo.toml"]
    end
    subgraph subGraph0["page_table_multiarch Crate"]
        PTM_CARGO["page_table_multiarch/Cargo.toml"]
        PTM["page_table_multiarchGeneric page table structures"]
    end
end

PTE_CARGO --> PTE
PTM --> PTE
PTM_CARGO --> PTM
ROOT --> PTE_CARGO
ROOT --> PTM_CARGO

Sources: Cargo.toml(L4 - L7)  page_table_multiarch/Cargo.toml(L2 - L3)  page_table_entry/Cargo.toml(L2 - L3) 

Shared Workspace Configuration

The workspace defines common package metadata in Cargo.toml(L9 - L19)  that is inherited by both member crates:

  • Version: 0.5.3 - synchronized across all crates
  • Edition: 2024 - uses latest Rust edition
  • Categories: ["os", "hardware-support", "memory-management", "no-std"]
  • Keywords: ["arceos", "paging", "page-table", "virtual-memory"]
  • Rust Version: 1.85 - minimum supported Rust version

Sources: Cargo.toml(L9 - L19)  page_table_multiarch/Cargo.toml(L5 - L13)  page_table_entry/Cargo.toml(L5 - L13) 

Crate Interaction Architecture

The two crates form a layered architecture where page_table_multiarch provides high-level abstractions while depending on page_table_entry for low-level functionality.

Crate Dependency and Interaction Diagram

flowchart TD
subgraph subGraph3["External Dependencies"]
    MEMORY_ADDR["memory_addr crateAddress types"]
    LOG["log crateLogging"]
    BITFLAGS["bitflags crateFlag manipulation"]
    ARCH_SPECIFIC["x86, riscv, aarch64-cpux86_64 crates"]
end
subgraph subGraph2["page_table_entry Crate"]
    PTE_TRAIT["GenericPTE trait"]
    PTE_IMPL["X64PTE, A64PTERv64PTE, LA64PTE"]
    PTE_FLAGS["MappingFlagsArchitecture conversion"]
end
subgraph subGraph1["page_table_multiarch Crate"]
    PTM_API["PageTable64"]
    PTM_TRAITS["PagingMetaDataPagingHandler traits"]
    PTM_IMPL["Architecture-specificPageTable implementations"]
end
subgraph subGraph0["Application Layer"]
    APP["Operating SystemsHypervisorsKernel Code"]
end

APP --> PTM_API
PTE_IMPL --> ARCH_SPECIFIC
PTE_IMPL --> BITFLAGS
PTE_IMPL --> PTE_FLAGS
PTE_TRAIT --> MEMORY_ADDR
PTE_TRAIT --> PTE_FLAGS
PTM_API --> LOG
PTM_API --> MEMORY_ADDR
PTM_API --> PTM_IMPL
PTM_API --> PTM_TRAITS
PTM_IMPL --> ARCH_SPECIFIC
PTM_IMPL --> PTE_IMPL
PTM_TRAITS --> PTE_TRAIT

Sources: page_table_multiarch/Cargo.toml(L15 - L18)  page_table_entry/Cargo.toml(L18 - L20) 

Dependency Relationship

The page_table_multiarch crate explicitly depends on page_table_entry as defined in page_table_multiarch/Cargo.toml(L18) :

page_table_entry = { path = "../page_table_entry", version = "0.5.2" }

This creates a clear separation of concerns:

  • High-level operations (page table walking, mapping, unmapping) in page_table_multiarch
  • Low-level PTE manipulation (bit operations, flag conversions) in page_table_entry

Sources: page_table_multiarch/Cargo.toml(L18) 

Architecture-Specific Dependencies

Both crates use conditional compilation to include only the necessary architecture-specific dependencies based on the target platform.

page_table_multiarch Dependencies

Conditional Dependencies Diagram

flowchart TD
subgraph subGraph0["page_table_multiarch Dependencies"]
    CORE["Core Dependencieslog, memory_addrpage_table_entry"]
    X86_TARGET["cfg(target_arch = x86_64)"]
    X86_DEP["x86 = 0.52"]
    RISCV_TARGET["cfg(target_arch = riscv32/64)"]
    RISCV_DEP["riscv = 0.12"]
    DOC_TARGET["cfg(doc)"]
    ALL_DEP["All arch dependenciesfor documentation"]
end

CORE --> DOC_TARGET
CORE --> RISCV_TARGET
CORE --> X86_TARGET
DOC_TARGET --> ALL_DEP
RISCV_TARGET --> RISCV_DEP
X86_TARGET --> X86_DEP

Sources: page_table_multiarch/Cargo.toml(L20 - L24)  page_table_multiarch/Cargo.toml(L26 - L27) 

page_table_entry Dependencies

The page_table_entry crate includes more architecture-specific dependencies:

Target ArchitectureDependenciesPurpose
x86_64x86_64 = "0.15.2"x86-64 specific register and instruction access
aarch64aarch64-cpu = "10.0"ARM64 system register and instruction access
riscv32/riscv64None (built-in)RISC-V support uses standard library features
loongarch64None (built-in)LoongArch support uses standard library features

page_table_entry Architecture Dependencies

flowchart TD
subgraph subGraph2["page_table_entry Conditional Dependencies"]
    PTE_CORE["Core Dependenciesbitflags, memory_addr"]
    subgraph subGraph1["Feature Flags"]
        ARM_EL2["arm-el2 featureARM Exception Level 2"]
    end
    subgraph subGraph0["Architecture Dependencies"]
        AARCH64_CFG["cfg(target_arch = aarch64)"]
        X86_64_CFG["cfg(target_arch = x86_64)"]
        DOC_CFG["cfg(doc)"]
        AARCH64_DEP["aarch64-cpu = 10.0"]
        X86_64_DEP["x86_64 = 0.15.2"]
        DOC_DEP["All dependencies"]
    end
end

AARCH64_CFG --> AARCH64_DEP
AARCH64_DEP --> ARM_EL2
DOC_CFG --> DOC_DEP
PTE_CORE --> AARCH64_CFG
PTE_CORE --> DOC_CFG
PTE_CORE --> X86_64_CFG
X86_64_CFG --> X86_64_DEP

Sources: page_table_entry/Cargo.toml(L22 - L26)  page_table_entry/Cargo.toml(L15 - L16) 

Documentation Configuration

Both crates include special configuration for documentation generation using docs.rs:

  • Documentation builds include all architecture dependencies via cfg(doc)
  • Rustc arguments add --cfg doc for conditional compilation during docs generation
  • This ensures complete documentation coverage across all supported architectures

The configuration in page_table_multiarch/Cargo.toml(L26 - L27)  and page_table_entry/Cargo.toml(L28 - L29)  enables this behavior.

Sources: page_table_multiarch/Cargo.toml(L26 - L27)  page_table_entry/Cargo.toml(L28 - L29) 

Workspace Benefits

The two-crate structure provides several architectural advantages:

  1. Separation of Concerns: High-level page table operations vs. low-level PTE manipulation
  2. Conditional Compilation: Architecture-specific dependencies are isolated and only included when needed
  3. Reusability: The page_table_entry crate can be used independently for PTE operations
  4. Testing: Each layer can be tested independently with appropriate mocking
  5. Documentation: Clear API boundaries make the system easier to understand and document

Sources: Cargo.toml(L1 - L19)  page_table_multiarch/Cargo.toml(L1 - L28)  page_table_entry/Cargo.toml(L1 - L29) 

page_table_multiarch Crate

Relevant source files

The page_table_multiarch crate provides high-level, architecture-independent page table management abstractions for 64-bit platforms. This crate implements the generic PageTable64 structure and supporting traits that enable unified page table operations across multiple hardware architectures including x86_64, AArch64, RISC-V, and LoongArch64.

For low-level page table entry definitions and architecture-specific implementations, see page_table_entry Crate. For detailed architecture-specific support information, see Architecture Support.

Core Components

The crate centers around three main abstractions that work together to provide architecture-independent page table management:


Architecture Abstraction Through Generic Types

Sources: page_table_multiarch/src/bits64.rs(L28 - L31)  page_table_multiarch/src/lib.rs(L40 - L92) 

PageTable64 Implementation

The PageTable64 struct is the central component providing a unified interface for page table operations across all supported architectures:

ComponentType ParameterPurpose
MPagingMetaDataArchitecture-specific constants and validation
PTEGenericPTEPage table entry manipulation
HPagingHandlerOS-dependent memory management
flowchart TD
subgraph subGraph2["Bulk Operations"]
    MAP_REGION["map_region()"]
    UNMAP_REGION["unmap_region()"]
    PROTECT_REGION["protect_region()"]
end
subgraph subGraph1["Core Operations"]
    MAP["map()"]
    UNMAP["unmap()"]
    QUERY["query()"]
    PROTECT["protect()"]
    REMAP["remap()"]
end
subgraph subGraph0["PageTable64 Structure"]
    ROOT["root_paddr: PhysAddr"]
    PHANTOM["_phantom: PhantomData"]
end
subgraph subGraph3["Utility Operations"]
    WALK["walk()"]
    COPY["copy_from()"]
    TRY_NEW["try_new()"]
end

MAP --> MAP_REGION
PROTECT --> PROTECT_REGION
ROOT --> MAP
ROOT --> QUERY
ROOT --> UNMAP
UNMAP --> UNMAP_REGION

Page Table Management Methods

Single Page Operations

The PageTable64 provides methods for managing individual page mappings:

Bulk Region Operations

For efficient handling of large memory regions, bulk operations automatically detect and use huge pages when possible:

Sources: page_table_multiarch/src/bits64.rs(L33 - L347) 

Architecture Abstraction System

The crate achieves architecture independence through a trait-based abstraction system:

flowchart TD
subgraph subGraph3["Entry Operations"]
    NEW_PAGE["new_page()"]
    NEW_TABLE["new_table()"]
    IS_PRESENT["is_present()"]
    IS_HUGE["is_huge()"]
    PADDR["paddr()"]
    FLAGS["flags()"]
end
subgraph subGraph2["OS Integration"]
    ALLOC["alloc_frame()"]
    DEALLOC["dealloc_frame()"]
    PHYS_TO_VIRT["phys_to_virt()"]
end
subgraph subGraph1["Architecture Constants"]
    LEVELS["LEVELS: usize"]
    PA_BITS["PA_MAX_BITS: usize"]
    VA_BITS["VA_MAX_BITS: usize"]
    VADDR_TYPE["VirtAddr: MemoryAddr"]
end
subgraph subGraph0["Trait System"]
    PMD_TRAIT["PagingMetaData trait"]
    PH_TRAIT["PagingHandler trait"]
    GPTE_TRAIT["GenericPTE trait"]
end

GPTE_TRAIT --> FLAGS
GPTE_TRAIT --> IS_HUGE
GPTE_TRAIT --> IS_PRESENT
GPTE_TRAIT --> NEW_PAGE
GPTE_TRAIT --> NEW_TABLE
GPTE_TRAIT --> PADDR
PH_TRAIT --> ALLOC
PH_TRAIT --> DEALLOC
PH_TRAIT --> PHYS_TO_VIRT
PMD_TRAIT --> LEVELS
PMD_TRAIT --> PA_BITS
PMD_TRAIT --> VADDR_TYPE
PMD_TRAIT --> VA_BITS

Trait Responsibilities

PagingMetaData Trait

Defines architecture-specific constants and validation logic:

  • LEVELS - Number of page table levels (3 or 4)
  • PA_MAX_BITS / VA_MAX_BITS - Maximum address widths
  • VirtAddr - Associated type for virtual addresses
  • flush_tlb() - Architecture-specific TLB flushing

PagingHandler Trait

Provides OS-dependent memory management operations:

  • alloc_frame() - Allocates 4K physical frames
  • dealloc_frame() - Deallocates physical frames
  • phys_to_virt() - Converts physical to virtual addresses for direct access

Sources: page_table_multiarch/src/lib.rs(L40 - L92) 

Page Table Navigation

The PageTable64 implements multi-level page table navigation supporting both 3-level and 4-level configurations:

flowchart TD
subgraph subGraph3["Page Sizes"]
    SIZE_1G["1GB Pages (P3 level)"]
    SIZE_2M["2MB Pages (P2 level)"]
    SIZE_4K["4KB Pages (P1 level)"]
end
subgraph subGraph2["Index Functions"]
    P4_IDX["p4_index(vaddr)"]
    P3_IDX["p3_index(vaddr)"]
    P2_IDX["p2_index(vaddr)"]
    P1_IDX["p1_index(vaddr)"]
end
subgraph subGraph1["3-Level Page Table (RISC-V Sv39)"]
    P3_3["P3 Table (Level 0)"]
    P2_3["P2 Table (Level 1)"]
    P1_3["P1 Table (Level 2)"]
end
subgraph subGraph0["4-Level Page Table (x86_64, AArch64)"]
    P4["P4 Table (Level 0)"]
    P3_4["P3 Table (Level 1)"]
    P2_4["P2 Table (Level 2)"]
    P1_4["P1 Table (Level 3)"]
end

P1_IDX --> P1_3
P1_IDX --> P1_4
P2_IDX --> P2_3
P2_IDX --> P2_4
P3_IDX --> P3_3
P3_IDX --> P3_4
P4 --> P3_4
P4_IDX --> P4

Virtual Address Translation

The implementation uses bit manipulation to extract table indices from virtual addresses:

Sources: page_table_multiarch/src/bits64.rs(L8 - L22)  page_table_multiarch/src/bits64.rs(L401 - L484) 

Error Handling and TLB Management

The crate provides comprehensive error handling and TLB management:

Error TypeDescriptionUsage
NoMemoryFrame allocation failureMemory exhaustion scenarios
NotAlignedAddress alignment violationInvalid page boundaries
NotMappedMissing page table entryQuery/unmap operations
AlreadyMappedExisting mapping conflictDuplicate map operations
MappedToHugePageHuge page access conflictTable navigation errors
flowchart TD
subgraph subGraph2["Operation Results"]
    SINGLE["Single page operations"]
    BULK["Bulk region operations"]
end
subgraph subGraph1["TLB Management"]
    TF["TlbFlush<M>"]
    TFA["TlbFlushAll<M>"]
    subgraph Operations["Operations"]
        FLUSH["flush()"]
        IGNORE["ignore()"]
        FLUSH_ALL["flush_all()"]
    end
end

BULK --> TFA
SINGLE --> TF
TF --> FLUSH
TF --> IGNORE
TFA --> FLUSH_ALL

TLB Flush Types

Sources: page_table_multiarch/src/lib.rs(L21 - L38)  page_table_multiarch/src/lib.rs(L130 - L172) 

Dependencies and Integration

The crate integrates with the workspace through carefully managed dependencies:


Conditional Compilation

The build system includes architecture-specific dependencies only when targeting supported platforms or building documentation:

Sources: page_table_multiarch/Cargo.toml(L15 - L28)  page_table_multiarch/src/lib.rs(L15 - L19) 

page_table_entry Crate

Relevant source files

This document covers the page_table_entry crate, which provides low-level page table entry definitions and abstractions for multiple hardware architectures. This crate serves as the foundation layer that defines the GenericPTE trait and architecture-specific page table entry implementations.

For information about the high-level page table management abstractions that build upon this crate, see page_table_multiarch Crate.

Purpose and Core Functionality

The page_table_entry crate abstracts page table entry manipulation across different processor architectures through a unified trait-based interface. It provides architecture-specific implementations of page table entries while maintaining a common API for higher-level page table management code.

Core Components:

  • GenericPTE trait defining unified page table entry operations
  • MappingFlags bitflags for architecture-independent memory permissions
  • Architecture-specific PTE implementations: X64PTE, A64PTE, Rv64PTE, LA64PTE

Sources: page_table_entry/README.md(L7 - L18)  page_table_entry/src/lib.rs(L38 - L68) 

GenericPTE Trait Architecture

The GenericPTE trait provides the core abstraction that enables architecture-independent page table entry manipulation. All architecture-specific page table entry types implement this trait.

GenericPTE Interface

classDiagram
class GenericPTE {
    <<trait>>
    
    +new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) Self
    +new_table(paddr: PhysAddr) Self
    +paddr() PhysAddr
    +flags() MappingFlags
    +set_paddr(paddr: PhysAddr)
    +set_flags(flags: MappingFlags, is_huge: bool)
    +bits() usize
    +is_unused() bool
    +is_present() bool
    +is_huge() bool
    +clear()
}

class X64PTE {
    
    +new_page()
    +new_table()
    +paddr()
    +flags()
}

class A64PTE {
    
    +new_page()
    +new_table()
    +paddr()
    +flags()
}

class Rv64PTE {
    
    +new_page()
    +new_table()
    +paddr()
    +flags()
}

class LA64PTE {
    
    +new_page()
    +new_table()
    +paddr()
    +flags()
}

GenericPTE  ..|>  X64PTE
GenericPTE  ..|>  A64PTE
GenericPTE  ..|>  Rv64PTE
LA64PTE  ..|>  A64PTE

Key Methods

MethodPurposeReturn Type
new_page()Create PTE for terminal page/block mappingSelf
new_table()Create PTE pointing to next-level page tableSelf
paddr()Extract mapped physical addressPhysAddr
flags()Get generic mapping flagsMappingFlags
set_paddr()Modify mapped physical address()
set_flags()Update mapping permissions()
bits()Get raw PTE bitsusize
is_present()Check if mapping is validbool
is_huge()Check if PTE maps large pagebool

Sources: page_table_entry/src/lib.rs(L41 - L68) 

MappingFlags System

The MappingFlags bitflags provide architecture-independent representation of memory mapping permissions and attributes.

flowchart TD
subgraph subGraph1["Architecture Conversion"]
    X86_R["x86: PRESENT"]
    X86_W["x86: WRITE"]
    X86_X["x86: !NO_EXECUTE"]
    X86_U["x86: USER"]
    ARM_R["ARM: AF + validity"]
    ARM_W["ARM: AP[1:0]"]
    ARM_X["ARM: !PXN/UXN"]
    ARM_D["ARM: AttrIndx"]
    RV_R["RISC-V: R"]
    RV_W["RISC-V: W"]
    RV_X["RISC-V: X"]
    RV_U["RISC-V: U"]
end
subgraph subGraph0["MappingFlags Bitflags"]
    READ["READ (1 << 0)"]
    WRITE["WRITE (1 << 1)"]
    EXECUTE["EXECUTE (1 << 2)"]
    USER["USER (1 << 3)"]
    DEVICE["DEVICE (1 << 4)"]
    UNCACHED["UNCACHED (1 << 5)"]
end

DEVICE --> ARM_D
EXECUTE --> ARM_X
EXECUTE --> RV_X
EXECUTE --> X86_X
READ --> ARM_R
READ --> RV_R
READ --> X86_R
USER --> RV_U
USER --> X86_U
WRITE --> ARM_W
WRITE --> RV_W
WRITE --> X86_W

Flag Definitions

FlagBit PositionPurpose
READ0Memory is readable
WRITE1Memory is writable
EXECUTE2Memory is executable
USER3User-mode accessible
DEVICE4Device memory type
UNCACHED5Uncached memory access

Sources: page_table_entry/src/lib.rs(L12 - L30) 

Architecture Implementation Matrix

The crate supports four major processor architectures through dedicated PTE implementations:

flowchart TD
subgraph subGraph5["Hardware Dependencies"]
    X64_DEP["x86_64 crate v0.15.2"]
    ARM_DEP["aarch64-cpu crate v10.0"]
    RV_DEP["riscv crate (implied)"]
end
subgraph subGraph4["Architecture Support"]
    subgraph AArch64["AArch64"]
        A64PTE["A64PTE"]
        A64_BITS["64-bit entries"]
        A64_LEVELS["4-level translation"]
        A64_EL2["EL2 support (feature)"]
    end
    subgraph x86_64["x86_64"]
        X64PTE["X64PTE"]
        X64_BITS["64-bit entries"]
        X64_LEVELS["4-level paging"]
    end
    subgraph LoongArch64["LoongArch64"]
        LA64PTE["LA64PTE"]
        LA_BITS["64-bit entries"]
        LA_LEVELS["4-level paging"]
        Rv64PTE["Rv64PTE"]
        RV_BITS["64-bit entries"]
        RV_SV39["Sv39 (3-level)"]
    end
    subgraph RISC-V["RISC-V"]
        LA64PTE["LA64PTE"]
        LA_BITS["64-bit entries"]
        LA_LEVELS["4-level paging"]
        Rv64PTE["Rv64PTE"]
        RV_BITS["64-bit entries"]
        RV_SV39["Sv39 (3-level)"]
        RV_SV48["Sv48 (4-level)"]
    end
end

A64PTE --> ARM_DEP
A64_EL2 --> ARM_DEP
X64PTE --> X64_DEP

Conditional Compilation

The crate uses target-specific conditional compilation to include only necessary dependencies:

Target ArchitectureDependenciesFeatures
x86_64x86_64 = "0.15.2"Standard x86_64 support
aarch64aarch64-cpu = "10.0"ARM64, optionalarm-el2
riscv32/riscv64Architecture support built-inSv39/Sv48 modes
loongarch64Built-in supportLA64 paging
docAll dependenciesDocumentation builds

Sources: page_table_entry/Cargo.toml(L22 - L27)  page_table_entry/Cargo.toml(L15 - L16) 

Usage Patterns

Basic PTE Creation and Manipulation

The typical usage pattern involves creating PTEs through the GenericPTE trait methods:

// Example from documentation
use memory_addr::PhysAddr;
use page_table_entry::{GenericPTE, MappingFlags, x86_64::X64PTE};

let paddr = PhysAddr::from(0x233000);
let pte = X64PTE::new_page(
    paddr,
    MappingFlags::READ | MappingFlags::WRITE,
    false, // not huge page
);

PTE State Queries

stateDiagram-v2
[*] --> Unused : "clear()"
Unused --> Present : "new_page() / new_table()"
Present --> Modified : "set_paddr() / set_flags()"
Modified --> Present : "architecture conversion"
Present --> Unused : "clear()"
Present --> QueryState : "is_present()"
Present --> QueryAddr : "paddr()"
Present --> QueryFlags : "flags()"
Present --> QueryHuge : "is_huge()"
Present --> QueryBits : "bits()"
QueryState --> Present
QueryAddr --> Present
QueryFlags --> Present
QueryHuge --> Present
QueryBits --> Present

Sources: page_table_entry/README.md(L28 - L46)  page_table_entry/src/lib.rs(L41 - L68) 

Dependencies and Build Configuration

Core Dependencies

DependencyVersionPurpose
bitflags2.6MappingFlagsimplementation
memory_addr0.3PhysAddrtype for physical addresses

Architecture-Specific Dependencies

Architecture-specific crates are conditionally included based on compilation target:

  • x86_64: Uses x86_64 crate for hardware-specific page table flag definitions
  • AArch64: Uses aarch64-cpu crate for ARM64 system register and memory attribute definitions
  • RISC-V: Built-in support for RISC-V page table formats
  • LoongArch: Built-in support for LoongArch64 page table formats

The doc configuration includes all architecture dependencies to enable complete documentation generation.

Sources: page_table_entry/Cargo.toml(L18 - L29) 

Core Concepts

Relevant source files

This document explains the fundamental abstractions that enable page_table_multiarch to provide a unified page table interface across multiple processor architectures. These core concepts form the foundation that allows the same high-level API to work with x86_64, AArch64, RISC-V, and LoongArch64 page tables.

For architecture-specific implementations of these concepts, see Architecture Support. For the high-level PageTable64 API that builds on these concepts, see PageTable64 Implementation.

Abstraction Architecture

The system achieves architecture independence through a layered abstraction model built on three core traits and several supporting types. This design separates architecture-specific details from the generic page table logic.

flowchart TD
subgraph subGraph3["Architecture Implementations"]
    X86_MD["X64PagingMetaData"]
    ARM_MD["A64PagingMetaData"]
    RV_MD["Sv39MetaData"]
    LA_MD["LA64MetaData"]
    X86_PTE["X64PTE"]
    ARM_PTE["A64PTE"]
    RV_PTE["Rv64PTE"]
    LA_PTE["LA64PTE"]
end
subgraph subGraph2["Supporting Types"]
    MF["MappingFlags"]
    PS["PageSize"]
    PE["PagingError"]
    TLB["TlbFlush<M>"]
end
subgraph subGraph1["Abstraction Traits"]
    PMD["PagingMetaData"]
    GPTE["GenericPTE"]
    PH["PagingHandler"]
end
subgraph subGraph0["Generic Layer"]
    PT64["PageTable64<M,PTE,H>"]
    API["map() unmap() walk()"]
end

API --> MF
API --> PE
API --> PS
API --> TLB
GPTE --> ARM_PTE
GPTE --> LA_PTE
GPTE --> RV_PTE
GPTE --> X86_PTE
PMD --> ARM_MD
PMD --> LA_MD
PMD --> RV_MD
PMD --> X86_MD
PT64 --> GPTE
PT64 --> PH
PT64 --> PMD

Sources: page_table_multiarch/src/lib.rs(L11 - L19)  page_table_multiarch/src/lib.rs(L42 - L92) 

Architecture Independence Traits

Three core traits provide the interface between generic page table logic and architecture-specific implementations.

PagingMetaData Trait

The PagingMetaData trait encapsulates architecture-specific constants and behaviors that vary between processor families.

classDiagram
class PagingMetaData {
    +LEVELS: usize
    +PA_MAX_BITS: usize
    +VA_MAX_BITS: usize
    +PA_MAX_ADDR: usize
    +VirtAddr: MemoryAddr
    +paddr_is_valid(paddr: usize) bool
    +vaddr_is_valid(vaddr: usize) bool
    +flush_tlb(vaddr: Option~VirtAddr~)
}

class X64PagingMetaData {
    +LEVELS = 4
    +PA_MAX_BITS = 52
    +VA_MAX_BITS = 48
    
}

class A64PagingMetaData {
    +LEVELS = 4
    +PA_MAX_BITS = 48
    +VA_MAX_BITS = 48
    
}

class Sv39MetaData {
    +LEVELS = 3
    +PA_MAX_BITS = 56
    +VA_MAX_BITS = 39
    
}

PagingMetaData  --|>  X64PagingMetaData
PagingMetaData  --|>  A64PagingMetaData
PagingMetaData  --|>  Sv39MetaData

Key responsibilities include:

  • Page table structure: LEVELS defines the number of translation levels
  • Address space limits: PA_MAX_BITS and VA_MAX_BITS specify supported address ranges
  • Address validation: paddr_is_valid() and vaddr_is_valid() enforce architecture constraints
  • TLB management: flush_tlb() handles cache invalidation

Sources: page_table_multiarch/src/lib.rs(L42 - L79) 

GenericPTE Trait

The GenericPTE trait provides a unified interface for manipulating page table entries across different architectures.

classDiagram
class GenericPTE {
    
    +new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) Self
    +new_table(paddr: PhysAddr) Self
    +paddr() PhysAddr
    +flags() MappingFlags
    +set_paddr(paddr: PhysAddr)
    +set_flags(flags: MappingFlags, is_huge: bool)
    +bits() usize
    +is_unused() bool
    +is_present() bool
    +is_huge() bool
    +clear()
}

class X64PTE {
    +bits: usize
    
}

class A64PTE {
    +bits: usize
    
}

class Rv64PTE {
    +bits: usize
    
}

GenericPTE  --|>  X64PTE
GenericPTE  --|>  A64PTE
GenericPTE  --|>  Rv64PTE

The trait supports two types of entries:

  • Page entries: Created with new_page(), point to actual memory pages
  • Table entries: Created with new_table(), point to next-level page tables

Sources: page_table_entry/src/lib.rs(L38 - L68) 

PagingHandler Trait

The PagingHandler trait abstracts OS-dependent memory management operations.

MethodPurposeReturn Type
alloc_frame()Allocate a 4K physical frameOption
dealloc_frame(paddr)Free an allocated frame()
phys_to_virt(paddr)Convert physical to virtual addressVirtAddr

This trait allows the page table implementation to work with different memory allocators and virtual memory layouts without being tied to a specific operating system.

Sources: page_table_multiarch/src/lib.rs(L83 - L92) 

Memory Management Types

MappingFlags

The MappingFlags bitflags provide a generic representation of memory permissions and attributes that gets translated to architecture-specific page table entry bits.

flowchart TD
subgraph subGraph1["Architecture Translation"]
    X86_BITS["x86_64 PTE bits"]
    ARM_BITS["AArch64 descriptor bits"]
    RV_BITS["RISC-V PTE bits"]
end
subgraph subGraph0["Generic MappingFlags"]
    READ["READ (1<<0)"]
    WRITE["WRITE (1<<1)"]
    EXECUTE["EXECUTE (1<<2)"]
    USER["USER (1<<3)"]
    DEVICE["DEVICE (1<<4)"]
    UNCACHED["UNCACHED (1<<5)"]
end

DEVICE --> ARM_BITS
DEVICE --> RV_BITS
DEVICE --> X86_BITS
EXECUTE --> ARM_BITS
EXECUTE --> RV_BITS
EXECUTE --> X86_BITS
READ --> ARM_BITS
READ --> RV_BITS
READ --> X86_BITS
UNCACHED --> ARM_BITS
UNCACHED --> RV_BITS
UNCACHED --> X86_BITS
USER --> ARM_BITS
USER --> RV_BITS
USER --> X86_BITS
WRITE --> ARM_BITS
WRITE --> RV_BITS
WRITE --> X86_BITS

Sources: page_table_entry/src/lib.rs(L12 - L36) 

PageSize Enumeration

The PageSize enum defines supported page sizes across architectures:

SizeValueUsage
Size4K0x1000 (4 KB)Standard page size
Size2M0x20_0000 (2 MB)Huge page (x86_64, AArch64)
Size1G0x4000_0000 (1 GB)Giant page (x86_64)

The enum provides utility methods:

  • is_huge(): Returns true for sizes larger than 4K
  • is_aligned(addr): Checks address alignment
  • align_offset(addr): Calculates alignment offset

Sources: page_table_multiarch/src/lib.rs(L95 - L128) 

Error Handling

The PagingError enum defines standard error conditions:

flowchart TD
PE["PagingError"]
NM["NoMemoryCannot allocate memory"]
NA["NotAlignedAddress not page-aligned"]
NMP["NotMappedMapping not present"]
AM["AlreadyMappedMapping already exists"]
HP["MappedToHugePagePTE is huge but target is 4K"]

PE --> AM
PE --> HP
PE --> NA
PE --> NM
PE --> NMP

Sources: page_table_multiarch/src/lib.rs(L22 - L38) 

TLB Management

Translation Lookaside Buffer (TLB) management is handled through two RAII types that ensure proper cache invalidation.

TlbFlush and TlbFlushAll


Both types are marked with #[must_use] to ensure TLB invalidation is explicitly handled. Callers must either:

  • Call .flush() or .flush_all() to invalidate TLB entries
  • Call .ignore() if TLB flushing will be handled elsewhere

Sources: page_table_multiarch/src/lib.rs(L130 - L172) 

Integration Model

The abstractions work together to provide a cohesive system where generic logic operates through trait interfaces while architecture-specific implementations handle the details.

flowchart TD
subgraph Hardware/OS["Hardware/OS"]
    MEM_ALLOC["Memory allocator"]
    TLB_HW["TLB hardware"]
    PT_HW["Page table hardware"]
end
subgraph subGraph2["Trait Implementations"]
    MD_CHECK["M::vaddr_is_valid()"]
    PTE_NEW["PTE::new_page()"]
    FLAGS_CONV["flags → PTE bits"]
    TLB_FLUSH["M::flush_tlb()"]
end
subgraph PageTable64&lt;M,PTE,H&gt;["PageTable64<M,PTE,H>"]
    PT64_IMPL["Page table traversal logic"]
    ALLOC["H::alloc_frame()"]
    CONVERT["H::phys_to_virt()"]
end
subgraph subGraph0["Application Code"]
    APP["map(vaddr, paddr, flags)"]
end

ALLOC --> MEM_ALLOC
APP --> PT64_IMPL
FLAGS_CONV --> PT_HW
MD_CHECK --> TLB_FLUSH
PT64_IMPL --> ALLOC
PT64_IMPL --> CONVERT
PT64_IMPL --> MD_CHECK
PT64_IMPL --> PTE_NEW
PTE_NEW --> FLAGS_CONV
TLB_FLUSH --> TLB_HW

This design allows the same PageTable64 implementation to work across all supported architectures by delegating architecture-specific operations to trait implementations while maintaining type safety and performance.

Sources: page_table_multiarch/src/lib.rs(L11 - L19)  page_table_multiarch/src/bits64.rs

PageTable64 Implementation

Relevant source files

This document covers the core PageTable64 struct that provides the main page table management functionality for 64-bit platforms in the page_table_multiarch library. It explains how the generic implementation handles multi-level page tables, memory mapping operations, and TLB management across different architectures.

For information about the trait system that enables architecture abstraction, see Generic Traits System. For details about architecture-specific implementations, see Architecture Support.

Core Structure and Generic Parameters

The PageTable64 struct serves as the primary interface for page table operations. It uses three generic parameters to achieve architecture independence while maintaining type safety.

flowchart TD
subgraph subGraph2["Core Operations"]
    MAP["map()Single page mapping"]
    UNMAP["unmap()Remove mapping"]
    QUERY["query()Lookup mapping"]
    PROTECT["protect()Change flags"]
    REGION["map_region()Bulk operations"]
end
subgraph subGraph1["Generic Parameters"]
    M["M: PagingMetaDataArchitecture constantsAddress validationTLB flushing"]
    PTE["PTE: GenericPTEPage table entryFlag manipulationAddress extraction"]
    H["H: PagingHandlerMemory allocationPhysical-virtual mapping"]
end
subgraph subGraph0["PageTable64 Structure"]
    PT64["PageTable64<M, PTE, H>"]
    ROOT["root_paddr: PhysAddr"]
    PHANTOM["_phantom: PhantomData<(M, PTE, H)>"]
end

H --> MAP
M --> MAP
MAP --> PROTECT
MAP --> QUERY
MAP --> REGION
MAP --> UNMAP
PT64 --> H
PT64 --> M
PT64 --> PHANTOM
PT64 --> PTE
PT64 --> ROOT
PTE --> MAP

The struct maintains only the physical address of the root page table, with all other state managed through the generic trait system. This design enables the same implementation to work across x86_64, AArch64, RISC-V, and LoongArch64 architectures.

Sources: page_table_multiarch/src/bits64.rs(L24 - L31)  page_table_multiarch/src/lib.rs(L40 - L92) 

Page Table Hierarchy and Indexing

PageTable64 supports both 3-level and 4-level page table configurations through constant indexing functions. The implementation uses bit manipulation to extract page table indices from virtual addresses.

flowchart TD
subgraph subGraph2["Page Table Levels"]
    ROOT["Root Table(P4 or P3)"]
    L3["Level 3 Table512 entries"]
    L2["Level 2 Table512 entriesCan map 2M pages"]
    L1["Level 1 Table512 entriesMaps 4K pages"]
end
subgraph subGraph1["Index Functions"]
    P4FUNC["p4_index()(vaddr >> 39) & 511"]
    P3FUNC["p3_index()(vaddr >> 30) & 511"]
    P2FUNC["p2_index()(vaddr >> 21) & 511"]
    P1FUNC["p1_index()(vaddr >> 12) & 511"]
end
subgraph subGraph0["Virtual Address Breakdown"]
    VADDR["Virtual Address (64-bit)"]
    P4["P4 Indexbits[48:39](4-level only)"]
    P3["P3 Indexbits[38:30]"]
    P2["P2 Indexbits[29:21]"]
    P1["P1 Indexbits[20:12]"]
    OFFSET["Page Offsetbits[11:0]"]
end

P1 --> P1FUNC
P1FUNC --> L1
P2 --> P2FUNC
P2FUNC --> L2
P3 --> P3FUNC
P3FUNC --> L3
P4 --> P4FUNC
P4FUNC --> ROOT
VADDR --> OFFSET
VADDR --> P1
VADDR --> P2
VADDR --> P3
VADDR --> P4

The system handles both 3-level configurations (like RISC-V Sv39) and 4-level configurations (like x86_64, AArch64) by checking M::LEVELS at compile time and selecting the appropriate starting table level.

Sources: page_table_multiarch/src/bits64.rs(L6 - L22)  page_table_multiarch/src/bits64.rs(L401 - L426) 

Memory Mapping Operations

The core mapping functionality provides both single-page operations and bulk region operations. The implementation automatically handles huge page detection and creation of intermediate page tables.

sequenceDiagram
    participant User as User
    participant PageTable64 as PageTable64
    participant get_entry_mut_or_create as get_entry_mut_or_create
    participant GenericPTE as GenericPTE
    participant PagingHandler as PagingHandler

    Note over User,PagingHandler: Single Page Mapping Flow
    User ->> PageTable64: "map(vaddr, paddr, size, flags)"
    PageTable64 ->> get_entry_mut_or_create: "get_entry_mut_or_create(vaddr, size)"
    alt "Entry doesn't exist"
        get_entry_mut_or_create ->> PagingHandler: "alloc_frame()"
        PagingHandler -->> get_entry_mut_or_create: "PhysAddr"
        get_entry_mut_or_create ->> GenericPTE: "new_table(paddr)"
        get_entry_mut_or_create -->> PageTable64: "&mut PTE"
    else "Entry exists"
        get_entry_mut_or_create -->> PageTable64: "&mut PTE"
    end
    alt "Entry is unused"
        PageTable64 ->> GenericPTE: "new_page(paddr, flags, is_huge)"
        PageTable64 -->> User: "TlbFlush"
    else "Entry already mapped"
        PageTable64 -->> User: "Err(AlreadyMapped)"
    end

The mapping process supports three page sizes through the PageSize enum:

  • Size4K (0x1000): Standard 4KB pages mapped at level 1
  • Size2M (0x200000): 2MB huge pages mapped at level 2
  • Size1G (0x40000000): 1GB huge pages mapped at level 3

Sources: page_table_multiarch/src/bits64.rs(L59 - L72)  page_table_multiarch/src/bits64.rs(L455 - L484)  page_table_multiarch/src/lib.rs(L94 - L128) 

Region Operations and Huge Page Optimization

The map_region() method provides optimized bulk mapping with automatic huge page selection when allow_huge is enabled. The implementation analyzes address alignment and region size to choose the largest possible page size.

Page SizeAlignment RequiredMinimum SizeUse Case
4K4KB4KBGeneral purpose mapping
2M2MB2MBLarge allocations, stack regions
1G1GB1GBKernel text, large data structures
flowchart TD
START["map_region(vaddr, size, allow_huge)"]
CHECK_ALIGN["Is vaddr and paddr1G aligned?size >= 1G?"]
USE_1G["Use PageSize::Size1G"]
CHECK_2M["Is vaddr and paddr2M aligned?size >= 2M?"]
USE_2M["Use PageSize::Size2M"]
USE_4K["Use PageSize::Size4K"]
MAP["map(vaddr, paddr, page_size, flags)"]
UPDATE["vaddr += page_sizesize -= page_size"]
DONE["size == 0?"]

CHECK_2M --> USE_2M
CHECK_2M --> USE_4K
CHECK_ALIGN --> CHECK_2M
CHECK_ALIGN --> USE_1G
DONE --> CHECK_ALIGN
DONE --> START
MAP --> UPDATE
START --> CHECK_ALIGN
UPDATE --> DONE
USE_1G --> MAP
USE_2M --> MAP
USE_4K --> MAP

The algorithm prioritizes larger page sizes when possible, reducing TLB pressure and improving performance for large memory regions.

Sources: page_table_multiarch/src/bits64.rs(L157 - L217)  page_table_multiarch/src/bits64.rs(L181 - L197) 

TLB Management and Flushing

The system provides fine-grained TLB management through the TlbFlush and TlbFlushAll types. These types enforce explicit handling of TLB invalidation to prevent stale translations.

flowchart TD
subgraph subGraph2["Flush Actions"]
    FLUSH["flush()Immediate TLB invalidation"]
    IGNORE["ignore()Defer to batch flush"]
end
subgraph subGraph1["Operations Returning Flushes"]
    MAP["map()"]
    UNMAP["unmap()"]
    PROTECT["protect()"]
    REMAP["remap()"]
    MAP_REGION["map_region()"]
    UNMAP_REGION["unmap_region()"]
end
subgraph subGraph0["TLB Flush Types"]
    SINGLE["TlbFlush<M>Single page flushContains VirtAddr"]
    ALL["TlbFlushAll<M>Complete TLB flushNo address needed"]
end

ALL --> FLUSH
ALL --> IGNORE
MAP --> SINGLE
MAP_REGION --> ALL
PROTECT --> SINGLE
REMAP --> SINGLE
SINGLE --> FLUSH
SINGLE --> IGNORE
UNMAP --> SINGLE
UNMAP_REGION --> ALL

The #[must_use] attribute on flush types ensures that TLB management is never accidentally ignored, preventing subtle bugs from stale page table entries.

Sources: page_table_multiarch/src/lib.rs(L130 - L172)  page_table_multiarch/src/bits64.rs(L65 - L91) 

Memory Management and Cleanup

PageTable64 implements automatic memory management through the Drop trait, ensuring all allocated page tables are freed when the structure is destroyed. The cleanup process uses recursive walking to identify and deallocate intermediate tables.

flowchart TD
subgraph subGraph1["Walk Process"]
    VISIT["Visit each table entry"]
    RECURSE["Recurse to next level"]
    CLEANUP["Post-order cleanup"]
end
subgraph subGraph0["Drop Implementation"]
    DROP["Drop::drop()"]
    WALK["walk() with post_func"]
    CHECK["level < LEVELS-1&&entry.is_present()&&!entry.is_huge()"]
    DEALLOC["H::dealloc_frame(entry.paddr())"]
    DEALLOC_ROOT["H::dealloc_frame(root_paddr)"]
end

CHECK --> CLEANUP
CHECK --> DEALLOC
CLEANUP --> CHECK
DEALLOC --> CLEANUP
DROP --> WALK
RECURSE --> CLEANUP
VISIT --> RECURSE
WALK --> DEALLOC_ROOT
WALK --> VISIT

The implementation carefully avoids freeing leaf-level entries, which point to actual data pages rather than page table structures that were allocated by the page table system itself.

Sources: page_table_multiarch/src/bits64.rs(L525 - L539)  page_table_multiarch/src/bits64.rs(L313 - L325) 

Private Implementation Details

The PageTable64 implementation uses several private helper methods to manage the complexity of multi-level page table traversal and memory allocation.

MethodPurposeReturns
alloc_table()Allocate and zero new page tablePagingResult
table_of()Get immutable slice to table entries&[PTE]
table_of_mut()Get mutable slice to table entries&mut [PTE]
next_table()Follow entry to next level tablePagingResult<&[PTE]>
next_table_mut_or_create()Get next level, creating if neededPagingResult<&mut [PTE]>
get_entry()Find entry for virtual addressPagingResult<(&PTE, PageSize)>
get_entry_mut()Find mutable entry for virtual addressPagingResult<(&mut PTE, PageSize)>

The helper methods handle error conditions like unmapped intermediate tables (NotMapped) and attempts to traverse through huge pages (MappedToHugePage), providing clear error semantics for page table operations.

Sources: page_table_multiarch/src/bits64.rs(L350 - L523)  page_table_multiarch/src/lib.rs(L21 - L38) 

Generic Traits System

Relevant source files

This document describes the core trait system that enables architecture abstraction in the page_table_multiarch library. The traits define interfaces for architecture-specific metadata, OS-level memory management, and page table entry manipulation, allowing a single PageTable64 implementation to work across multiple processor architectures.

For information about how these traits are implemented for specific architectures, see the Architecture Support section 4. For details about the main page table implementation that uses these traits, see PageTable64 Implementation.

Core Trait Architecture

The generic traits system consists of three primary traits that decouple architecture-specific behavior from the common page table logic:

flowchart TD
subgraph subGraph3["Architecture Implementations"]
    X86_MD["X64PagingMetaData"]
    ARM_MD["A64PagingMetaData"]
    RV_MD["Sv39MetaData/Sv48MetaData"]
    LA_MD["LA64MetaData"]
    X86_PTE["X64PTE"]
    ARM_PTE["A64PTE"]
    RV_PTE["Rv64PTE"]
    LA_PTE["LA64PTE"]
end
subgraph subGraph2["Page Table Implementation"]
    PT64["PageTable64<M,PTE,H>Generic page table"]
end
subgraph subGraph1["Supporting Types"]
    MF["MappingFlagsGeneric permission flags"]
    PS["PageSize4K, 2M, 1G page sizes"]
    TLB["TlbFlush/TlbFlushAllTLB invalidation"]
end
subgraph subGraph0["Generic Traits Layer"]
    PMD["PagingMetaDataArchitecture constants& address validation"]
    PH["PagingHandlerOS memory management& virtual mapping"]
    GPTE["GenericPTEPage table entrymanipulation"]
end

GPTE --> ARM_PTE
GPTE --> LA_PTE
GPTE --> RV_PTE
GPTE --> X86_PTE
PMD --> ARM_MD
PMD --> LA_MD
PMD --> RV_MD
PMD --> X86_MD
PT64 --> GPTE
PT64 --> MF
PT64 --> PH
PT64 --> PMD
PT64 --> PS
PT64 --> TLB

Sources: page_table_multiarch/src/lib.rs(L42 - L92)  page_table_entry/src/lib.rs(L41 - L68) 

PagingMetaData Trait

The PagingMetaData trait defines architecture-specific constants and validation logic for page table implementations:

Method/ConstantPurposeType
LEVELSNumber of page table levelsconst usize
PA_MAX_BITSMaximum physical address bitsconst usize
VA_MAX_BITSMaximum virtual address bitsconst usize
VirtAddrVirtual address type for this architectureAssociated type
paddr_is_valid()Validates physical addressesfn(usize) -> bool
vaddr_is_valid()Validates virtual addressesfn(usize) -> bool
flush_tlb()Flushes Translation Lookaside Bufferfn(OptionSelf::VirtAddr)

Address Validation Logic

The trait provides default implementations for address validation that work for most architectures:

flowchart TD
PADDR_CHECK["paddr_is_valid(paddr)"]
PADDR_RESULT["paddr <= PA_MAX_ADDR"]
VADDR_CHECK["vaddr_is_valid(vaddr)"]
VADDR_MASK["top_mask = usize::MAX << (VA_MAX_BITS - 1)"]
VADDR_SIGN["(vaddr & top_mask) == 0 ||(vaddr & top_mask) == top_mask"]

PADDR_CHECK --> PADDR_RESULT
VADDR_CHECK --> VADDR_MASK
VADDR_MASK --> VADDR_SIGN

Sources: page_table_multiarch/src/lib.rs(L61 - L72) 

PagingHandler Trait

The PagingHandler trait abstracts OS-level memory management operations required by the page table implementation:

MethodPurposeSignature
alloc_frame()Allocate a 4K physical framefn() -> Option
dealloc_frame()Free a physical framefn(PhysAddr)
phys_to_virt()Get virtual address for physical memory accessfn(PhysAddr) -> VirtAddr

This trait enables the page table implementation to work with different memory management systems by requiring the OS to provide these three fundamental operations.

Sources: page_table_multiarch/src/lib.rs(L83 - L92) 

GenericPTE Trait

The GenericPTE trait provides a unified interface for manipulating page table entries across different architectures:

flowchart TD
subgraph subGraph4["Query Methods"]
    IS_UNUSED["is_unused() -> bool"]
    IS_PRESENT["is_present() -> bool"]
    IS_HUGE["is_huge() -> bool"]
end
subgraph subGraph3["Modification Methods"]
    SET_PADDR["set_paddr(paddr)"]
    SET_FLAGS["set_flags(flags, is_huge)"]
    CLEAR["clear()"]
end
subgraph subGraph2["Access Methods"]
    GET_PADDR["paddr() -> PhysAddr"]
    GET_FLAGS["flags() -> MappingFlags"]
    GET_BITS["bits() -> usize"]
end
subgraph subGraph1["Creation Methods"]
    NEW_PAGE["new_page(paddr, flags, is_huge)"]
    NEW_TABLE["new_table(paddr)"]
end
subgraph subGraph0["GenericPTE Interface"]
    CREATE["Creation Methods"]
    ACCESS["Access Methods"]
    MODIFY["Modification Methods"]
    QUERY["Query Methods"]
end

ACCESS --> GET_BITS
ACCESS --> GET_FLAGS
ACCESS --> GET_PADDR
CREATE --> NEW_PAGE
CREATE --> NEW_TABLE
MODIFY --> CLEAR
MODIFY --> SET_FLAGS
MODIFY --> SET_PADDR
QUERY --> IS_HUGE
QUERY --> IS_PRESENT
QUERY --> IS_UNUSED

Sources: page_table_entry/src/lib.rs(L41 - L68) 

MappingFlags System

The MappingFlags bitflags provide a generic representation of memory permissions and attributes:

FlagValuePurpose
READ1 << 0Memory is readable
WRITE1 << 1Memory is writable
EXECUTE1 << 2Memory is executable
USER1 << 3Memory is user-accessible
DEVICE1 << 4Memory is device memory
UNCACHED1 << 5Memory is uncached

Architecture-specific implementations convert between these generic flags and their hardware-specific representations.

Sources: page_table_entry/src/lib.rs(L12 - L30) 

Page Size Support

The PageSize enum defines supported page sizes across architectures:

flowchart TD
subgraph subGraph0["Helper Methods"]
    IS_HUGE["is_huge() -> bool"]
    IS_ALIGNED["is_aligned(addr) -> bool"]
    ALIGN_OFFSET["align_offset(addr) -> usize"]
end
PS["PageSize enum"]
SIZE_4K["Size4K = 0x1000(4 KiB)"]
SIZE_2M["Size2M = 0x20_0000(2 MiB)"]
SIZE_1G["Size1G = 0x4000_0000(1 GiB)"]

PS --> SIZE_1G
PS --> SIZE_2M
PS --> SIZE_4K
SIZE_1G --> IS_HUGE
SIZE_2M --> IS_HUGE

Sources: page_table_multiarch/src/lib.rs(L95 - L128) 

TLB Management Types

The system provides type-safe TLB invalidation through must-use wrapper types:

flowchart TD
subgraph Actions["Actions"]
    FLUSH["flush() - Execute flush"]
    IGNORE["ignore() - Skip flush"]
    FLUSH_ALL["flush_all() - Execute full flush"]
end
subgraph subGraph0["TLB Flush Types"]
    TLB_FLUSH["TlbFlush<M>Single address flush"]
    TLB_FLUSH_ALL["TlbFlushAll<M>Complete TLB flush"]
end

TLB_FLUSH --> FLUSH
TLB_FLUSH --> IGNORE
TLB_FLUSH_ALL --> FLUSH_ALL
TLB_FLUSH_ALL --> IGNORE

These types ensure that TLB flushes are not accidentally forgotten after page table modifications by using the #[must_use] attribute.

Sources: page_table_multiarch/src/lib.rs(L130 - L172) 

Trait Integration Pattern

The three core traits work together to parameterize the PageTable64 implementation:


This design allows compile-time specialization while maintaining a common interface, enabling zero-cost abstractions across different processor architectures.

Sources: page_table_multiarch/src/lib.rs(L42 - L92)  page_table_entry/src/lib.rs(L41 - L68) 

Memory Mapping Flags

Relevant source files

This document explains the memory mapping flags system that provides a generic abstraction layer for page table entry permissions and attributes across different processor architectures. The MappingFlags type serves as the common interface that is converted to and from architecture-specific page table entry flags, enabling consistent memory management behavior regardless of the underlying hardware platform.

For information about the overall page table implementation, see PageTable64 Implementation. For details about the trait system that enables this abstraction, see Generic Traits System.

MappingFlags Structure

The MappingFlags type is defined as a bitflags structure that represents generic memory permissions and attributes that are common across all supported architectures.

flowchart TD
subgraph subGraph0["MappingFlags Bitfield Structure"]
    MF["MappingFlags (usize)"]
    READ["READ (1 << 0)Memory is readable"]
    WRITE["WRITE (1 << 1)Memory is writable"]
    EXECUTE["EXECUTE (1 << 2)Memory is executable"]
    USER["USER (1 << 3)Memory is user accessible"]
    DEVICE["DEVICE (1 << 4)Memory is device memory"]
    UNCACHED["UNCACHED (1 << 5)Memory is uncached"]
end

MF --> DEVICE
MF --> EXECUTE
MF --> READ
MF --> UNCACHED
MF --> USER
MF --> WRITE

The flags are designed to capture the essential memory access properties that operating systems need to control:

FlagPurposeTypical Usage
READMemory can be readAll mapped pages typically have this
WRITEMemory can be writtenData pages, stack, heap
EXECUTEMemory can contain executable codeCode segments, shared libraries
USERMemory accessible from user modeUser space mappings
DEVICEMemory-mapped device registersHardware device interfaces
UNCACHEDMemory should not be cachedPerformance-critical or coherency-sensitive regions

Sources: page_table_entry/src/lib.rs(L12 - L30) 

Architecture Conversion System

Each supported architecture implements bidirectional conversion between MappingFlags and its native page table entry flag representation through From trait implementations.

flowchart TD
subgraph subGraph3["RISC-V Architecture"]
    RVPTF["RV Page Flags"]
    RVPTE["Rv64PTE"]
end
subgraph subGraph2["AArch64 Architecture"]
    A64ATTR["DescriptorAttr"]
    A64PTE["A64PTE"]
    MATTR["MemAttr enum"]
end
subgraph subGraph1["x86_64 Architecture"]
    X86PTF["PageTableFlags (PTF)"]
    X86PTE["X64PTE"]
end
subgraph subGraph0["Generic Layer"]
    MF["MappingFlags"]
    GPTE["GenericPTE trait"]
end

A64ATTR --> A64PTE
A64ATTR --> MATTR
A64ATTR --> MF
A64PTE --> GPTE
MF --> A64ATTR
MF --> RVPTF
MF --> X86PTF
RVPTE --> GPTE
RVPTF --> MF
RVPTF --> RVPTE
X86PTE --> GPTE
X86PTF --> MF
X86PTF --> X86PTE

Sources: page_table_entry/src/arch/x86_64.rs(L10 - L52)  page_table_entry/src/arch/aarch64.rs(L114 - L189) 

x86_64 Flag Mapping

The x86_64 implementation uses the PageTableFlags from the x86_64 crate and maps generic flags to hardware-specific bits:

flowchart TD
subgraph subGraph0["MappingFlags to x86_64 PTF"]
    MF_READ["MappingFlags::READ"]
    PTF_PRESENT["PTF::PRESENT"]
    MF_WRITE["MappingFlags::WRITE"]
    PTF_WRITABLE["PTF::WRITABLE"]
    MF_EXEC["MappingFlags::EXECUTE"]
    PTF_NO_EXECUTE["PTF::NO_EXECUTE"]
    MF_USER["MappingFlags::USER"]
    PTF_USER_ACC["PTF::USER_ACCESSIBLE"]
    MF_DEVICE["MappingFlags::DEVICE"]
    PTF_NO_CACHE["PTF::NO_CACHE"]
    PTF_WRITE_THROUGH["PTF::WRITE_THROUGH"]
    MF_UNCACHED["MappingFlags::UNCACHED"]
end

MF_DEVICE --> PTF_NO_CACHE
MF_DEVICE --> PTF_WRITE_THROUGH
MF_EXEC --> PTF_NO_EXECUTE
MF_READ --> PTF_PRESENT
MF_UNCACHED --> PTF_NO_CACHE
MF_UNCACHED --> PTF_WRITE_THROUGH
MF_USER --> PTF_USER_ACC
MF_WRITE --> PTF_WRITABLE

Key mapping behaviors:

  • Any non-empty MappingFlags automatically gets PTF::PRESENT
  • Execute permission is inverted: absence of EXECUTE sets NO_EXECUTE
  • Both DEVICE and UNCACHED flags result in cache-disabled memory
  • Table entries always get PRESENT | WRITABLE | USER_ACCESSIBLE regardless of flags

Sources: page_table_entry/src/arch/x86_64.rs(L32 - L52)  page_table_entry/src/arch/x86_64.rs(L76 - L79) 

AArch64 Memory Attribute System

AArch64 implements a more sophisticated memory attribute system using the Memory Attribute Indirection Register (MAIR) approach:

flowchart TD
subgraph subGraph3["AArch64 Memory Attributes"]
    MF2["MappingFlags"]
    subgraph subGraph2["MAIR Configuration"]
        MAIR_0["MAIR[7:0] = Device-nGnRE"]
        MAIR_1["MAIR[15:8] = Normal WriteBack"]
        MAIR_2["MAIR[23:16] = Normal NonCacheable"]
    end
    subgraph subGraph1["Descriptor Attributes"]
        ATTR_INDX["ATTR_INDX[2:0]"]
        VALID["VALID"]
        AP_RO["AP_RO (read-only)"]
        AP_EL0["AP_EL0 (user access)"]
        UXN["UXN (user execute never)"]
        PXN["PXN (privileged execute never)"]
    end
    subgraph subGraph0["Memory Types"]
        DEVICE_MEM["MemAttr::Device"]
        NORMAL_MEM["MemAttr::Normal"]
        UNCACHED_MEM["MemAttr::NormalNonCacheable"]
    end
end

ATTR_INDX --> MAIR_0
ATTR_INDX --> MAIR_1
ATTR_INDX --> MAIR_2
DEVICE_MEM --> ATTR_INDX
MF2 --> DEVICE_MEM
MF2 --> NORMAL_MEM
MF2 --> UNCACHED_MEM
NORMAL_MEM --> ATTR_INDX
UNCACHED_MEM --> ATTR_INDX

The AArch64 system requires careful handling of execute permissions based on privilege level:

MappingFlags StateEL0 (User) AccessEL1+ (Kernel) Access
USER + EXECUTEAP_EL0, noUXNPXNset
USERonlyAP_EL0,UXNsetPXNset
EXECUTEonlyNoAP_EL0,UXNsetNoPXN
NeitherNoAP_EL0,UXNsetPXNset

Sources: page_table_entry/src/arch/aarch64.rs(L99 - L112)  page_table_entry/src/arch/aarch64.rs(L149 - L189)  page_table_entry/src/arch/aarch64.rs(L167 - L186) 

GenericPTE Integration

The GenericPTE trait methods utilize MappingFlags to create and manipulate page table entries in an architecture-neutral way:

sequenceDiagram
    participant ApplicationCode as "Application Code"
    participant GenericPTEImplementation as "GenericPTE Implementation"
    participant MappingFlags as "MappingFlags"
    participant ArchitectureFlags as "Architecture Flags"

    Note over ApplicationCode,ArchitectureFlags: Creating a Page Entry
    ApplicationCode ->> GenericPTEImplementation: new_page(paddr, flags, is_huge)
    GenericPTEImplementation ->> MappingFlags: Convert MappingFlags
    MappingFlags ->> ArchitectureFlags: From<MappingFlags>
    ArchitectureFlags ->> GenericPTEImplementation: Architecture-specific bits
    GenericPTEImplementation ->> ApplicationCode: Complete PTE
    Note over ApplicationCode,ArchitectureFlags: Reading Entry Flags
    ApplicationCode ->> GenericPTEImplementation: flags()
    GenericPTEImplementation ->> ArchitectureFlags: Extract raw bits
    ArchitectureFlags ->> MappingFlags: From<ArchFlags>
    MappingFlags ->> GenericPTEImplementation: Generic MappingFlags
    GenericPTEImplementation ->> ApplicationCode: MappingFlags

The trait provides these flag-related methods:

  • new_page(paddr, flags, is_huge) - Creates page entries with specified permissions
  • flags() - Extracts current permissions as MappingFlags
  • set_flags(flags, is_huge) - Updates entry permissions

Sources: page_table_entry/src/lib.rs(L41 - L56)  page_table_entry/src/arch/x86_64.rs(L69 - L95)  page_table_entry/src/arch/aarch64.rs(L210 - L236) 

Conditional Compilation Features

The flag conversion system supports conditional compilation for different execution environments:

FeatureEffectUsage
arm-el2Modifies AArch64 execute permission handlingHypervisor/EL2 execution
DefaultStandard EL0/EL1 permission modelNormal kernel/user operation

When arm-el2 is enabled, the execute permission logic is simplified to only use the UXN bit rather than the complex EL0/EL1+ distinction.

Sources: page_table_entry/src/arch/aarch64.rs(L123 - L139)  page_table_entry/src/arch/aarch64.rs(L181 - L186) 

Architecture Support

Relevant source files

This document provides an overview of how the page_table_multiarch library supports multiple processor architectures through a unified abstraction layer. It covers the conditional compilation strategy, supported architectures, and the trait-based abstraction mechanism that enables architecture-independent page table management.

For detailed implementation specifics of individual architectures, see x86_64 Support, AArch64 Support, RISC-V Support, and LoongArch64 Support.

Conditional Compilation Strategy

The library uses Rust's conditional compilation features to include only the relevant architecture-specific code for the target platform. This approach minimizes binary size and compile time while maintaining support for multiple architectures in a single codebase.

Compilation Configuration

The architecture modules are conditionally compiled based on the target architecture:


Sources: page_table_entry/src/arch/mod.rs(L1 - L12)  page_table_multiarch/src/arch/mod.rs(L1 - L12) 

The doc condition ensures all architecture modules are available during documentation generation, allowing comprehensive API documentation regardless of the build target.

Supported Architectures Overview

The library supports four major processor architectures, each with distinct paging characteristics and implementations:

ArchitecturePage Table LevelsVirtual Address WidthPhysical Address WidthKey Features
x86_644 levels (PML4)48 bits52 bitsLegacy support, complex permissions
AArch644 levels (VMSAv8-64)48 bits48 bitsMemory attributes, EL2 support
RISC-V3 levels (Sv39) / 4 levels (Sv48)39/48 bitsVariableMultiple page table formats
LoongArch644 levelsVariableVariablePage Walk Controller (PWC)

Architecture Module Structure

flowchart TD
subgraph subGraph2["Concrete Types"]
    X64PT["X64PageTable"]
    A64PT["A64PageTable"]
    SV39PT["Sv39PageTable"]
    SV48PT["Sv48PageTable"]
    LA64PT["LA64PageTable"]
    X64PTE["X64PTE"]
    A64PTE["A64PTE"]
    RV64PTE["Rv64PTE"]
    LA64PTE["LA64PTE"]
end
subgraph subGraph1["page_table_entry Crate"]
    PTE_ARCH["src/arch/"]
    PTE_X86["src/arch/x86_64.rs"]
    PTE_ARM["src/arch/aarch64.rs"]
    PTE_RV["src/arch/riscv.rs"]
    PTE_LA["src/arch/loongarch64.rs"]
end
subgraph subGraph0["page_table_multiarch Crate"]
    PTA_ARCH["src/arch/"]
    PTA_X86["src/arch/x86_64.rs"]
    PTA_ARM["src/arch/aarch64.rs"]
    PTA_RV["src/arch/riscv.rs"]
    PTA_LA["src/arch/loongarch64.rs"]
end

A64PT --> A64PTE
LA64PT --> LA64PTE
PTA_ARCH --> PTA_ARM
PTA_ARCH --> PTA_LA
PTA_ARCH --> PTA_RV
PTA_ARCH --> PTA_X86
PTA_ARM --> A64PT
PTA_LA --> LA64PT
PTA_RV --> SV39PT
PTA_RV --> SV48PT
PTA_X86 --> X64PT
PTE_ARCH --> PTE_ARM
PTE_ARCH --> PTE_LA
PTE_ARCH --> PTE_RV
PTE_ARCH --> PTE_X86
PTE_ARM --> A64PTE
PTE_LA --> LA64PTE
PTE_RV --> RV64PTE
PTE_X86 --> X64PTE
SV39PT --> RV64PTE
SV48PT --> RV64PTE
X64PT --> X64PTE

Sources: page_table_entry/src/arch/mod.rs(L1 - L12)  page_table_multiarch/src/arch/mod.rs(L1 - L12) 

Architecture Abstraction Mechanism

The multi-architecture support is achieved through a trait-based abstraction layer that defines common interfaces while allowing architecture-specific implementations.

Core Abstraction Traits

flowchart TD
subgraph subGraph5["Architecture Implementations"]
    subgraph LoongArch64["LoongArch64"]
        LA64MD["LA64MetaData"]
        LA64PTE_IMPL["LA64PTE"]
    end
    subgraph RISC-V["RISC-V"]
        SV39MD["Sv39MetaData"]
        SV48MD["Sv48MetaData"]
        RV64PTE_IMPL["Rv64PTE"]
    end
    subgraph AArch64["AArch64"]
        A64MD["A64PagingMetaData"]
        A64PTE_IMPL["A64PTE"]
    end
    subgraph x86_64["x86_64"]
        X64MD["X64PagingMetaData"]
        X64PTE_IMPL["X64PTE"]
    end
end
subgraph subGraph0["Generic Abstractions"]
    PT64["PageTable64<M, PTE, H>"]
    PMD["PagingMetaData trait"]
    GPTE["GenericPTE trait"]
    PH["PagingHandler trait"]
    MF["MappingFlags"]
end

GPTE --> A64PTE_IMPL
GPTE --> LA64PTE_IMPL
GPTE --> MF
GPTE --> RV64PTE_IMPL
GPTE --> X64PTE_IMPL
PMD --> A64MD
PMD --> LA64MD
PMD --> SV39MD
PMD --> SV48MD
PMD --> X64MD
PT64 --> GPTE
PT64 --> PH
PT64 --> PMD

Sources: page_table_entry/src/arch/mod.rs(L1 - L12)  page_table_multiarch/src/arch/mod.rs(L1 - L12) 

Generic Type Parameters

The PageTable64<M, PTE, H> type uses three generic parameters to achieve architecture independence:

  • M: Implements PagingMetaData - provides architecture-specific constants like page sizes, address widths, and level counts
  • PTE: Implements GenericPTE - handles page table entry creation, flag conversion, and address extraction
  • H: Implements PagingHandler - manages frame allocation and deallocation through OS-specific interfaces

Integration Between Crates

The workspace structure separates concerns between high-level page table management and low-level entry manipulation:

Crate Dependency Flow

flowchart TD
subgraph subGraph2["External Dependencies"]
    MEMORY_ADDR["memory_addr"]
    HW_CRATES["Architecture-specifichardware crates"]
end
subgraph page_table_entry["page_table_entry"]
    LIB_PTE["lib.rs"]
    ARCH_PTE["arch/ modules"]
    PTE_TYPES["PTE Types:X64PTEA64PTERv64PTELA64PTE"]
    TRAITS["GenericPTE traitMappingFlags"]
end
subgraph page_table_multiarch["page_table_multiarch"]
    LIB_PTM["lib.rs"]
    BITS64["bits64.rs"]
    ARCH_PTM["arch/ modules"]
    PT_TYPES["Page Table Types:X64PageTableA64PageTableSv39PageTableSv48PageTableLA64PageTable"]
end

ARCH_PTE --> PTE_TYPES
ARCH_PTM --> PT_TYPES
LIB_PTE --> ARCH_PTE
LIB_PTE --> TRAITS
LIB_PTM --> ARCH_PTM
LIB_PTM --> BITS64
PTE_TYPES --> HW_CRATES
PTE_TYPES --> MEMORY_ADDR
PT_TYPES --> PTE_TYPES
PT_TYPES --> TRAITS

Sources: page_table_entry/src/arch/mod.rs(L1 - L12)  page_table_multiarch/src/arch/mod.rs(L1 - L12) 

This separation allows the page_table_multiarch crate to focus on high-level page table operations while page_table_entry handles the architecture-specific details of page table entry formats and flag mappings. The conditional compilation ensures that only the necessary code for the target architecture is included in the final binary.

x86_64 Support

Relevant source files

This document covers the implementation of x86_64 architecture support in the page_table_multiarch library. It details how x86_64-specific page table entries, paging metadata, and memory management features are integrated with the generic multi-architecture abstraction layer.

For information about other supported architectures, see AArch64 Support, RISC-V Support, and LoongArch64 Support. For details about the overall architecture abstraction system, see Generic Traits System.

x86_64 Architecture Overview

The x86_64 implementation supports Intel and AMD 64-bit processors using 4-level page translation with the following characteristics:

PropertyValueDescription
Paging Levels4PML4, PDPT, PD, PT
Physical Address Space52 bitsUp to 4 PB of physical memory
Virtual Address Space48 bits256 TB of virtual memory
Page Sizes4KB, 2MB, 1GBStandard and huge page support

x86_64 Paging Hierarchy

flowchart TD
subgraph subGraph0["Virtual Address Breakdown"]
    VA["48-bit Virtual Address"]
    PML4_IDX["Bits 47-39PML4 Index"]
    PDPT_IDX["Bits 38-30PDPT Index"]
    PD_IDX["Bits 29-21PD Index"]
    PT_IDX["Bits 20-12PT Index"]
    OFFSET["Bits 11-0Page Offset"]
end
CR3["CR3 RegisterPML4 Base Address"]
PML4["PML4 (Level 4)Page Map Level 4"]
PDPT["PDPT (Level 3)Page Directory Pointer Table"]
PD["PD (Level 2)Page Directory"]
PT["PT (Level 1)Page Table"]
Page["4KB Page"]
HugePage2M["2MB Huge Page"]
HugePage1G["1GB Huge Page"]

CR3 --> PML4
PD --> HugePage2M
PD --> PT
PDPT --> HugePage1G
PDPT --> PD
PML4 --> PDPT
PT --> Page
VA --> OFFSET
VA --> PDPT_IDX
VA --> PD_IDX
VA --> PML4_IDX
VA --> PT_IDX

Sources: page_table_multiarch/src/arch/x86_64.rs(L10 - L12) 

Page Table Entry Implementation

The X64PTE struct implements x86_64-specific page table entries with a 64-bit representation that follows Intel's page table entry format.

X64PTE Structure and Bit Layout

flowchart TD
subgraph subGraph2["GenericPTE Implementation"]
    NEW_PAGE["new_page()"]
    NEW_TABLE["new_table()"]
    PADDR["paddr()"]
    FLAGS_METHOD["flags()"]
    SET_METHODS["set_paddr(), set_flags()"]
    STATE_METHODS["is_present(), is_huge(), is_unused()"]
end
subgraph subGraph0["X64PTE Bit Layout (64 bits)"]
    RESERVED["Bits 63-52Reserved/Available"]
    PHYS_ADDR["Bits 51-12Physical Address(PHYS_ADDR_MASK)"]
    FLAGS["Bits 11-0Page Table Flags"]
end
subgraph subGraph1["Key Flag Bits"]
    PRESENT["Bit 0: Present"]
    WRITABLE["Bit 1: Writable"]
    USER["Bit 2: User Accessible"]
    WRITE_THROUGH["Bit 3: Write Through"]
    NO_CACHE["Bit 4: No Cache"]
    ACCESSED["Bit 5: Accessed"]
    DIRTY["Bit 6: Dirty"]
    HUGE_PAGE["Bit 7: Page Size (Huge)"]
    GLOBAL["Bit 8: Global"]
    NO_EXECUTE["Bit 63: No Execute (NX)"]
end

FLAGS --> FLAGS_METHOD
FLAGS_METHOD --> STATE_METHODS
NEW_PAGE --> SET_METHODS
PHYS_ADDR --> NEW_PAGE

The X64PTE struct uses a transparent representation over a u64 value and implements the GenericPTE trait to provide architecture-neutral page table entry operations.

Sources: page_table_entry/src/arch/x86_64.rs(L54 - L66)  page_table_entry/src/arch/x86_64.rs(L68 - L112) 

GenericPTE Trait Implementation

The X64PTE implements all required methods of the GenericPTE trait:

MethodPurposeImplementation Detail
new_page()Create page mapping entrySets physical address and convertsMappingFlagstoPTF
new_table()Create page table entrySetsPRESENT,WRITABLE,USER_ACCESSIBLEflags
paddr()Extract physical addressMasks bits 12-51 usingPHYS_ADDR_MASK
flags()Get mapping flagsConvertsPTFflags to genericMappingFlags
is_present()Check if entry is validTestsPTF::PRESENTbit
is_huge()Check if huge pageTestsPTF::HUGE_PAGEbit

Sources: page_table_entry/src/arch/x86_64.rs(L69 - L95)  page_table_entry/src/arch/x86_64.rs(L97 - L112) 

Paging Metadata Implementation

The X64PagingMetaData struct defines x86_64-specific constants and operations required by the PagingMetaData trait.

Architecture Constants and TLB Management

flowchart TD
subgraph subGraph2["PageTable64 Integration"]
    PT64["PageTable64"]
    TYPE_ALIAS["X64PageTable"]
end
subgraph subGraph0["X64PagingMetaData Constants"]
    LEVELS["LEVELS = 44-level page tables"]
    PA_BITS["PA_MAX_BITS = 5252-bit physical addresses"]
    VA_BITS["VA_MAX_BITS = 4848-bit virtual addresses"]
    VIRT_ADDR["VirtAddr = memory_addr::VirtAddrVirtual address type"]
end
subgraph subGraph1["TLB Management"]
    FLUSH_TLB["flush_tlb()"]
    SINGLE_FLUSH["x86::tlb::flush(vaddr)Single page invalidation"]
    FULL_FLUSH["x86::tlb::flush_all()Complete TLB flush"]
end

FLUSH_TLB --> FULL_FLUSH
FLUSH_TLB --> SINGLE_FLUSH
LEVELS --> PT64
PA_BITS --> PT64
PT64 --> TYPE_ALIAS
VA_BITS --> PT64
VIRT_ADDR --> PT64

The TLB flushing implementation uses the x86 crate to perform architecture-specific translation lookaside buffer invalidation operations safely.

Sources: page_table_multiarch/src/arch/x86_64.rs(L7 - L25)  page_table_multiarch/src/arch/x86_64.rs(L28) 

Flag Conversion System

The x86_64 implementation provides bidirectional conversion between generic MappingFlags and x86-specific PageTableFlags (PTF).

MappingFlags to PTF Conversion

Generic Flagx86_64 PTFNotes
READPRESENTAlways set for readable pages
WRITEWRITABLEWrite permission
EXECUTE!NO_EXECUTEExecute permission (inverted logic)
USERUSER_ACCESSIBLEUser-mode access
UNCACHEDNO_CACHEDisable caching
DEVICENO_CACHE | WRITE_THROUGHDevice memory attributes

PTF to MappingFlags Conversion

The reverse conversion extracts generic flags from x86-specific page table flags, handling the inverted execute permission logic and presence requirements.

flowchart TD
subgraph subGraph1["Special Handling"]
    EMPTY_CHECK["Empty flags → Empty PTF"]
    PRESENT_REQ["Non-empty flags → PRESENT bit set"]
    EXECUTE_INVERT["EXECUTE → !NO_EXECUTE"]
    DEVICE_COMBO["DEVICE → NO_CACHE | WRITE_THROUGH"]
end
subgraph subGraph0["Flag Conversion Flow"]
    GENERIC["MappingFlags(Architecture-neutral)"]
    X86_FLAGS["PageTableFlags (PTF)(x86-specific)"]
end

GENERIC --> DEVICE_COMBO
GENERIC --> EMPTY_CHECK
GENERIC --> EXECUTE_INVERT
GENERIC --> PRESENT_REQ
GENERIC --> X86_FLAGS
X86_FLAGS --> GENERIC

Sources: page_table_entry/src/arch/x86_64.rs(L10 - L30)  page_table_entry/src/arch/x86_64.rs(L32 - L52) 

Integration with Generic System

The x86_64 support integrates with the generic page table system through the X64PageTable type alias, which specializes the generic PageTable64 struct with x86_64-specific types.

Type Composition

flowchart TD
subgraph subGraph2["Concrete Type"]
    X64_PAGE_TABLE["X64PageTable =PageTable64"]
end
subgraph subGraph1["x86_64 Implementations"]
    X64_METADATA["X64PagingMetaData"]
    X64_PTE["X64PTE"]
    USER_HANDLER["H: PagingHandler(User-provided)"]
end
subgraph subGraph0["Generic Framework"]
    PT64_GENERIC["PageTable64"]
    METADATA_TRAIT["PagingMetaData trait"]
    PTE_TRAIT["GenericPTE trait"]
    HANDLER_TRAIT["PagingHandler trait"]
end

HANDLER_TRAIT --> USER_HANDLER
METADATA_TRAIT --> X64_METADATA
PT64_GENERIC --> X64_PAGE_TABLE
PTE_TRAIT --> X64_PTE
USER_HANDLER --> X64_PAGE_TABLE
X64_METADATA --> X64_PAGE_TABLE
X64_PTE --> X64_PAGE_TABLE

The X64PageTable<H> provides a complete x86_64 page table implementation that maintains all the functionality of the generic PageTable64 while using x86_64-specific page table entries and metadata.

Sources: page_table_multiarch/src/arch/x86_64.rs(L28) 

AArch64 Support

Relevant source files

This document covers the AArch64 (ARM64) architecture implementation in the page_table_multiarch library, including VMSAv8-64 translation table format support, memory attributes, and AArch64-specific page table operations. For information about other supported architectures, see Architecture Support.

Overview

The AArch64 implementation provides support for the VMSAv8-64 translation table format used by ARM64 processors. The implementation consists of two main components: the low-level page table entry definitions in the page_table_entry crate and the high-level page table metadata and operations in the page_table_multiarch crate.

AArch64 Implementation Architecture

flowchart TD
subgraph subGraph3["Hardware Interaction"]
    TLBI["TLBI instructions"]
    MAIR_EL1_reg["MAIR_EL1 register"]
    VMSAv8["VMSAv8-64 format"]
end
subgraph subGraph2["Core Traits"]
    PagingMetaData_trait["PagingMetaData"]
    GenericPTE_trait["GenericPTE"]
    PageTable64_trait["PageTable64"]
end
subgraph page_table_entry/src/arch/aarch64.rs["page_table_entry/src/arch/aarch64.rs"]
    A64PTE["A64PTE"]
    DescriptorAttr["DescriptorAttr"]
    MemAttr["MemAttr"]
    MAIR_VALUE["MAIR_VALUE"]
end
subgraph page_table_multiarch/src/arch/aarch64.rs["page_table_multiarch/src/arch/aarch64.rs"]
    A64PageTable["A64PageTable<H>"]
    A64PagingMetaData["A64PagingMetaData"]
    FlushTLB["flush_tlb()"]
end

A64PTE --> DescriptorAttr
A64PTE --> GenericPTE_trait
A64PTE --> VMSAv8
A64PageTable --> PageTable64_trait
A64PagingMetaData --> FlushTLB
A64PagingMetaData --> PagingMetaData_trait
DescriptorAttr --> MemAttr
DescriptorAttr --> VMSAv8
FlushTLB --> TLBI
MAIR_VALUE --> MAIR_EL1_reg
MemAttr --> MAIR_VALUE

Sources: page_table_multiarch/src/arch/aarch64.rs(L1 - L38)  page_table_entry/src/arch/aarch64.rs(L1 - L265) 

Page Table Entry Implementation

The A64PTE struct implements the GenericPTE trait and represents VMSAv8-64 translation table descriptors. It uses the DescriptorAttr bitflags to manage AArch64-specific memory attributes and access permissions.

AArch64 Page Table Entry Structure

flowchart TD
subgraph subGraph3["Memory Attributes"]
    MemAttr_enum["MemAttr enum"]
    Device["Device (0)"]
    Normal["Normal (1)"]
    NormalNonCacheable["NormalNonCacheable (2)"]
end
subgraph subGraph2["GenericPTE Methods"]
    new_page["new_page()"]
    new_table["new_table()"]
    paddr["paddr()"]
    flags["flags()"]
    is_huge["is_huge()"]
    is_present["is_present()"]
end
subgraph subGraph1["DescriptorAttr Bitflags"]
    VALID["VALID (bit 0)"]
    NON_BLOCK["NON_BLOCK (bit 1)"]
    ATTR_INDX["ATTR_INDX (bits 2-4)"]
    AP_EL0["AP_EL0 (bit 6)"]
    AP_RO["AP_RO (bit 7)"]
    AF["AF (bit 10)"]
    PXN["PXN (bit 53)"]
    UXN["UXN (bit 54)"]
end
subgraph subGraph0["A64PTE Structure"]
    A64PTE_struct["A64PTE(u64)"]
    PHYS_ADDR_MASK["PHYS_ADDR_MASK0x0000_ffff_ffff_f000bits 12..48"]
end

A64PTE_struct --> flags
A64PTE_struct --> is_huge
A64PTE_struct --> is_present
A64PTE_struct --> new_page
A64PTE_struct --> new_table
A64PTE_struct --> paddr
AF --> new_page
AP_EL0 --> flags
AP_RO --> flags
ATTR_INDX --> MemAttr_enum
MemAttr_enum --> Device
MemAttr_enum --> Normal
MemAttr_enum --> NormalNonCacheable
NON_BLOCK --> is_huge
PHYS_ADDR_MASK --> paddr
PXN --> flags
UXN --> flags
VALID --> is_present

Sources: page_table_entry/src/arch/aarch64.rs(L191 - L253)  page_table_entry/src/arch/aarch64.rs(L9 - L58) 

Key Methods and Bit Manipulation

The A64PTE implementation provides several key methods for page table entry manipulation:

MethodPurposePhysical Address Mask
new_page()Creates page descriptor with mapping flags0x0000_ffff_ffff_f000
new_table()Creates table descriptor for next level0x0000_ffff_ffff_f000
paddr()Extracts physical address from bits 12-470x0000_ffff_ffff_f000
is_huge()Checks if NON_BLOCK bit is clearN/A
is_present()Checks VALID bitN/A

Sources: page_table_entry/src/arch/aarch64.rs(L200 - L253) 

Memory Attributes and MAIR Configuration

The AArch64 implementation uses the Memory Attribute Indirection Register (MAIR) to define memory types. The MemAttr enum defines three memory attribute types that correspond to MAIR indices.

Memory Attribute Configuration

flowchart TD
subgraph subGraph2["DescriptorAttr Conversion"]
    from_mem_attr["from_mem_attr()"]
    INNER_SHAREABLE["INNER + SHAREABLEfor Normal memory"]
end
subgraph subGraph1["MAIR_EL1 Register Values"]
    MAIR_VALUE["MAIR_VALUE: 0x44_ff_04"]
    attr0["attr0: Device-nGnREnonGathering_nonReordering_EarlyWriteAck"]
    attr1["attr1: WriteBack_NonTransient_ReadWriteAllocInner + Outer"]
    attr2["attr2: NonCacheableInner + Outer"]
end
subgraph subGraph0["MemAttr Types"]
    Device_attr["Device (index 0)Device-nGnRE"]
    Normal_attr["Normal (index 1)WriteBack cacheable"]
    NormalNC_attr["NormalNonCacheable (index 2)Non-cacheable"]
end

Device_attr --> attr0
Device_attr --> from_mem_attr
NormalNC_attr --> attr2
NormalNC_attr --> from_mem_attr
Normal_attr --> attr1
Normal_attr --> from_mem_attr
attr0 --> MAIR_VALUE
attr1 --> MAIR_VALUE
attr2 --> MAIR_VALUE
from_mem_attr --> INNER_SHAREABLE

Sources: page_table_entry/src/arch/aarch64.rs(L62 - L112)  page_table_entry/src/arch/aarch64.rs(L73 - L97) 

Flag Conversion

The implementation provides bidirectional conversion between generic MappingFlags and AArch64-specific DescriptorAttr:

Flag Mapping Table

MappingFlagsDescriptorAttrDescription
READVALIDEntry is valid and readable
WRITE!AP_ROWrite access permitted (when AP_RO is clear)
EXECUTE!PXNor!UXNExecute permissions based on privilege level
USERAP_EL0Accessible from EL0 (user space)
DEVICEMemAttr::DeviceDevice memory with ATTR_INDX = 0
UNCACHEDMemAttr::NormalNonCacheableNon-cacheable normal memory

Sources: page_table_entry/src/arch/aarch64.rs(L114 - L189) 

Page Table Metadata

The A64PagingMetaData struct implements the PagingMetaData trait and defines AArch64-specific constants and operations for the VMSAv8-64 translation scheme.

Configuration Constants

ConstantValueDescription
LEVELS4Number of page table levels
PA_MAX_BITS48Maximum physical address bits
VA_MAX_BITS48Maximum virtual address bits

Sources: page_table_multiarch/src/arch/aarch64.rs(L11 - L15) 

Virtual Address Validation

The vaddr_is_valid() method implements AArch64's canonical address validation:

fn vaddr_is_valid(vaddr: usize) -> bool {
    let top_bits = vaddr >> Self::VA_MAX_BITS;
    top_bits == 0 || top_bits == 0xffff
}

This ensures that virtual addresses are either in the lower canonical range (top 16 bits all zeros) or upper canonical range (top 16 bits all ones).

Sources: page_table_multiarch/src/arch/aarch64.rs(L17 - L20) 

TLB Management

The AArch64 implementation provides TLB (Translation Lookaside Buffer) invalidation through assembly instructions in the flush_tlb() method.

TLB Invalidation Operations

flowchart TD
subgraph subGraph0["flush_tlb() Implementation"]
    flush_tlb_method["flush_tlb(vaddr: Option<VirtAddr>)"]
    vaddr_check["vaddr.is_some()?"]
    specific_tlbi["TLBI VAAE1ISTLB Invalidate by VAAll ASID, EL1, Inner Shareable"]
    global_tlbi["TLBI VMALLE1TLB Invalidate by VMIDAll at stage 1, EL1"]
    dsb_sy["DSB SYData Synchronization Barrier"]
    isb["ISBInstruction Synchronization Barrier"]
end

dsb_sy --> isb
flush_tlb_method --> vaddr_check
global_tlbi --> dsb_sy
specific_tlbi --> dsb_sy
vaddr_check --> global_tlbi
vaddr_check --> specific_tlbi

Sources: page_table_multiarch/src/arch/aarch64.rs(L22 - L34) 

TLB Instruction Details

OperationInstructionScopeDescription
Specific invalidationtlbi vaae1is, {vaddr}Single VAInvalidates TLB entries for specific virtual address across all ASIDs
Global invalidationtlbi vmalle1All VAsInvalidates all stage 1 TLB entries for current VMID
Memory barrierdsb sy; isbSystem-wideEnsures TLB operations complete before subsequent instructions

Sources: page_table_multiarch/src/arch/aarch64.rs(L24 - L32) 

EL2 Support

The implementation includes conditional compilation support for ARM Exception Level 2 (EL2) through the arm-el2 feature flag. This affects execute permission handling in the flag conversion logic.

Permission Handling Differences

ConfigurationUser ExecuteKernel ExecuteImplementation
Withoutarm-el2UsesUXNwhenAP_EL0setUsesPXNfor kernel-onlySeparate handling for user/kernel
Witharm-el2UsesUXNonlyUsesUXNonlySimplified execute control

Sources: page_table_entry/src/arch/aarch64.rs(L123 - L186) 

Type Alias and Integration

The A64PageTable<H> type alias provides the complete AArch64 page table implementation by combining the metadata, page table entry type, and handler:

pub type A64PageTable<H> = PageTable64<A64PagingMetaData, A64PTE, H>;

This creates a fully configured page table type that applications can use with their chosen PagingHandler implementation.

Sources: page_table_multiarch/src/arch/aarch64.rs(L37) 

RISC-V Support

Relevant source files

This document covers RISC-V architecture support in the page_table_multiarch library, including both Sv39 (3-level) and Sv48 (4-level) page table configurations. The implementation provides a unified interface for RISC-V memory management that abstracts the differences between these two virtual memory systems while maintaining compatibility with the generic PageTable64 interface.

For information about other processor architectures, see x86_64 Support, AArch64 Support, and LoongArch64 Support. For details about the underlying generic abstractions, see Generic Traits System.

RISC-V Virtual Memory Systems

The RISC-V architecture defines multiple virtual memory systems. This library supports the two most common configurations used in 64-bit RISC-V systems.

Sv39 vs Sv48 Comparison

FeatureSv39Sv48
Page Table Levels34
Virtual Address Bits3948
Physical Address Bits5656
Virtual Address Space512 GiB256 TiB
Page Table StructurePage Directory → Page Table → PagePage Map Level 4 → Page Directory → Page Table → Page

RISC-V Page Table Type Hierarchy

flowchart TD
subgraph subGraph4["Virtual Address"]
    SVVA["SvVirtAddr trait"]
    VIRTADDR["memory_addr::VirtAddr"]
end
subgraph subGraph3["Entry Type"]
    RV64PTE["Rv64PTE"]
end
subgraph subGraph2["Metadata Types"]
    SV39_META["Sv39MetaData<VA>"]
    SV48_META["Sv48MetaData<VA>"]
end
subgraph subGraph1["RISC-V Implementations"]
    SV39["Sv39PageTable<H>"]
    SV48["Sv48PageTable<H>"]
end
subgraph subGraph0["Generic Layer"]
    PT64["PageTable64<M,PTE,H>"]
end

PT64 --> SV39
PT64 --> SV48
SV39 --> RV64PTE
SV39 --> SV39_META
SV39_META --> SVVA
SV48 --> RV64PTE
SV48 --> SV48_META
SV48_META --> SVVA
SVVA --> VIRTADDR

Sources: page_table_multiarch/src/arch/riscv.rs(L30 - L68) 

Core Components

Page Table Type Definitions

The RISC-V implementation provides two concrete page table types that specialize the generic PageTable64 for RISC-V systems:

// Sv39: 3-level page table with 39-bit virtual addresses
pub type Sv39PageTable<H> = PageTable64<Sv39MetaData<memory_addr::VirtAddr>, Rv64PTE, H>;

// Sv48: 4-level page table with 48-bit virtual addresses  
pub type Sv48PageTable<H> = PageTable64<Sv48MetaData<memory_addr::VirtAddr>, Rv64PTE, H>;

Both types use:

  • The same page table entry type (Rv64PTE)
  • The same virtual address type (memory_addr::VirtAddr)
  • Different metadata types that define the paging characteristics

Metadata Implementations

The metadata structures implement the PagingMetaData trait to define architecture-specific constants:

Metadata TypeLevelsPA Max BitsVA Max Bits
Sv39MetaData35639
Sv48MetaData45648

RISC-V Metadata Structure

flowchart TD
subgraph subGraph3["Virtual Address Support"]
    SVVA_TRAIT["SvVirtAddr trait"]
    FLUSH_METHOD["flush_tlb(vaddr: Option<Self>)"]
end
subgraph subGraph2["Sv48 Implementation"]
    SV48_META["Sv48MetaData<VA>"]
    SV48_CONSTANTS["LEVELS = 4PA_MAX_BITS = 56VA_MAX_BITS = 48"]
end
subgraph subGraph1["Sv39 Implementation"]
    SV39_META["Sv39MetaData<VA>"]
    SV39_CONSTANTS["LEVELS = 3PA_MAX_BITS = 56VA_MAX_BITS = 39"]
end
subgraph subGraph0["PagingMetaData Trait"]
    PMD["PagingMetaData"]
    PMD_METHODS["LEVELS: usizePA_MAX_BITS: usizeVA_MAX_BITS: usizeVirtAddr: typeflush_tlb(vaddr)"]
end

PMD --> PMD_METHODS
PMD_METHODS --> SV39_META
PMD_METHODS --> SV48_META
SV39_META --> SV39_CONSTANTS
SV39_META --> SVVA_TRAIT
SV48_META --> SV48_CONSTANTS
SV48_META --> SVVA_TRAIT
SVVA_TRAIT --> FLUSH_METHOD

Sources: page_table_multiarch/src/arch/riscv.rs(L30 - L62) 

Page Table Entry Implementation

Rv64PTE Structure

The Rv64PTE struct implements the GenericPTE trait and provides RISC-V-specific page table entry functionality. It uses a 64-bit representation with specific bit field layouts defined by the RISC-V specification.

RISC-V PTE Bit Layout

flowchart TD
subgraph subGraph2["Physical Address"]
    PHYS_MASK["PHYS_ADDR_MASK= (1<<54) - (1<<10)= bits 10..54"]
end
subgraph subGraph1["Flag Bits (PTEFlags)"]
    FLAGS["V: Valid (bit 0)R: Read (bit 1)W: Write (bit 2)X: Execute (bit 3)U: User (bit 4)G: Global (bit 5)A: Accessed (bit 6)D: Dirty (bit 7)"]
end
subgraph subGraph0["Rv64PTE (64 bits)"]
    BITS["Bits 63-54: ReservedBits 53-10: Physical Page NumberBits 9-8: ReservedBits 7-0: Flags"]
end

BITS --> FLAGS
BITS --> PHYS_MASK

Sources: page_table_entry/src/arch/riscv.rs(L77 - L89) 

Flag Conversion

The implementation provides bidirectional conversion between RISC-V-specific PTEFlags and generic MappingFlags:

Generic FlagRISC-V FlagDescription
READRPage is readable
WRITEWPage is writable
EXECUTEXPage is executable
USERUPage is accessible in user mode

The conversion automatically sets additional RISC-V flags:

  • V (Valid) flag is set for any non-empty mapping
  • A (Accessed) and D (Dirty) flags are automatically set to avoid hardware page faults

Sources: page_table_entry/src/arch/riscv.rs(L33 - L75) 

GenericPTE Implementation

The Rv64PTE implements key GenericPTE methods:

// Create page entry with proper flag conversion
fn new_page(paddr: PhysAddr, flags: MappingFlags, _is_huge: bool) -> Self

// Create page table entry (only Valid flag set)
fn new_table(paddr: PhysAddr) -> Self

// Extract physical address from PTE
fn paddr(&self) -> PhysAddr

// Convert RISC-V flags to generic flags
fn flags(&self) -> MappingFlags

// Check if entry represents a huge page (has R or X flags)
fn is_huge(&self) -> bool

Sources: page_table_entry/src/arch/riscv.rs(L91 - L131) 

Virtual Address Management

SvVirtAddr Trait

The SvVirtAddr trait extends the generic MemoryAddr trait to add RISC-V-specific TLB management capabilities:

Virtual Address Trait Hierarchy

flowchart TD
subgraph subGraph2["Concrete Implementation"]
    VIRTADDR["memory_addr::VirtAddr"]
    IMPL_FLUSH["calls riscv_flush_tlb()"]
end
subgraph subGraph1["RISC-V Extension"]
    SVVA["SvVirtAddr trait"]
    FLUSH_FUNC["flush_tlb(vaddr: Option<Self>)"]
end
subgraph subGraph0["Base Traits"]
    MEMADDR["memory_addr::MemoryAddr"]
    SEND["Send"]
    SYNC["Sync"]
end

MEMADDR --> SVVA
SEND --> SVVA
SVVA --> FLUSH_FUNC
SVVA --> VIRTADDR
SYNC --> SVVA
VIRTADDR --> IMPL_FLUSH

Sources: page_table_multiarch/src/arch/riscv.rs(L17 - L28) 

TLB Management

TLB Flushing Implementation

RISC-V TLB management uses the sfence.vma instruction through the riscv crate:

fn riscv_flush_tlb(vaddr: Option<memory_addr::VirtAddr>) {
    unsafe {
        if let Some(vaddr) = vaddr {
            riscv::asm::sfence_vma(0, vaddr.as_usize())  // Flush specific address
        } else {
            riscv::asm::sfence_vma_all();                // Flush entire TLB
        }
    }
}

The TLB flush operation supports:

  • Selective flushing: Flush TLB entries for a specific virtual address
  • Global flushing: Flush all TLB entries when no address is specified

This functionality is integrated into the metadata types through the PagingMetaData::flush_tlb method, which delegates to the SvVirtAddr::flush_tlb implementation.

Sources: page_table_multiarch/src/arch/riscv.rs(L6 - L15)  page_table_multiarch/src/arch/riscv.rs(L46 - L61) 

LoongArch64 Support

Relevant source files

This page documents the LoongArch64 architecture support within the page_table_multiarch library. LoongArch64 is implemented as one of the four supported architectures, providing 4-level page table management with architecture-specific features like Page Walk Controllers (PWC) and privilege level control.

For information about the overall multi-architecture design, see Architecture Support. For implementation details of other architectures, see x86_64 Support, AArch64 Support, and RISC-V Support.

Architecture Overview

LoongArch64 uses a 4-level page table structure with 48-bit physical and virtual addresses. The implementation leverages LoongArch64's Page Walk Controller (PWC) mechanism for hardware-assisted page table walking and provides comprehensive memory management capabilities including privilege level control and memory access type configuration.

Page Table Structure

LoongArch64 employs a 4-level hierarchical page table structure consisting of DIR3, DIR2, DIR1, and PT levels. The DIR4 level is ignored in this implementation, focusing on the standard 4-level configuration.

flowchart TD
subgraph subGraph2["PWC Configuration"]
    PWCL["PWCL CSRLower Half Address SpacePTBase=12, PTWidth=9Dir1Base=21, Dir1Width=9Dir2Base=30, Dir2Width=9"]
    PWCH["PWCH CSRHigher Half Address SpaceDir3Base=39, Dir3Width=9"]
end
subgraph subGraph0["LoongArch64 4-Level Page Table"]
    VA["Virtual Address (48-bit)"]
    DIR3["DIR3 Table (Level 3)"]
    DIR2["DIR2 Table (Level 2)"]
    DIR1["DIR1 Table (Level 1)"]
    PT["Page Table (Level 0)"]
    PA["Physical Address (48-bit)"]
end

DIR1 --> PT
DIR2 --> DIR1
DIR3 --> DIR2
PT --> PA
PWCH --> DIR3
PWCL --> DIR1
PWCL --> DIR2
PWCL --> PT
VA --> DIR3

Sources: page_table_multiarch/src/arch/loongarch64.rs(L11 - L41)  page_table_multiarch/src/arch/loongarch64.rs(L43 - L46) 

Page Table Entry Implementation

The LoongArch64 page table entry is implemented through the LA64PTE struct, which provides a complete set of architecture-specific flags and integrates with the generic trait system.

LA64PTE Structure

The LA64PTE struct wraps a 64-bit value containing both the physical address and control flags. The physical address occupies bits 12-47, providing support for 48-bit physical addressing.

flowchart TD
subgraph subGraph0["LA64PTE Bit Layout (64 bits)"]
    RPLV["RPLV (63)Privilege Level Restricted"]
    NX["NX (62)Not Executable"]
    NR["NR (61)Not Readable"]
    UNUSED["Unused (60-13)"]
    G["G (12)Global (Huge Page)"]
    ADDR["Physical Address (47-12)36 bits"]
    W["W (8)Writable"]
    P["P (7)Present"]
    GH["GH (6)Global/Huge"]
    MATH["MATH (5)Memory Access Type High"]
    MATL["MATL (4)Memory Access Type Low"]
    PLVH["PLVH (3)Privilege Level High"]
    PLVL["PLVL (2)Privilege Level Low"]
    D["D (1)Dirty"]
    V["V (0)Valid"]
end

Sources: page_table_entry/src/arch/loongarch64.rs(L11 - L59)  page_table_entry/src/arch/loongarch64.rs(L121 - L133) 

LoongArch64-Specific Flags

The PTEFlags bitflags define comprehensive control over memory access and privilege levels:

FlagBitPurpose
V0Page table entry is valid
D1Page has been written (dirty bit)
PLVL/PLVH2-3Privilege level control (4 levels)
MATL/MATH4-5Memory Access Type (SUC/CC/WUC)
GH6Global mapping or huge page indicator
P7Physical page exists
W8Page is writable
G12Global mapping for huge pages
NR61Page is not readable
NX62Page is not executable
RPLV63Privilege level restriction

The Memory Access Type (MAT) field supports three modes:

  • 00 (SUC): Strongly-ordered uncached
  • 01 (CC): Coherent cached
  • 10 (WUC): Weakly-ordered uncached
  • 11: Reserved

Sources: page_table_entry/src/arch/loongarch64.rs(L16 - L58) 

Flag Conversion System

The LoongArch64 implementation provides bidirectional conversion between architecture-specific PTEFlags and generic MappingFlags:

flowchart TD
subgraph subGraph1["LoongArch64 to Generic"]
    PF["PTEFlags"]
    NR["!NR → READ"]
    W["W → WRITE"]
    NX["!NX → EXECUTE"]
    PLV["PLVL|PLVH → USER"]
    MAT["MAT bits → DEVICE/UNCACHED"]
    MF["MappingFlags"]
    READ["READ → !NR"]
    WRITE["WRITE → W"]
    EXECUTE["EXECUTE → !NX"]
    USER["USER → PLVH|PLVL"]
    DEVICE["DEVICE → MAT=00"]
end
subgraph subGraph0["Generic to LoongArch64"]
    PF["PTEFlags"]
    NR["!NR → READ"]
    W["W → WRITE"]
    NX["!NX → EXECUTE"]
    PLV["PLVL|PLVH → USER"]
    MAT["MAT bits → DEVICE/UNCACHED"]
    MF["MappingFlags"]
    READ["READ → !NR"]
    WRITE["WRITE → W"]
    EXECUTE["EXECUTE → !NX"]
    USER["USER → PLVH|PLVL"]
    DEVICE["DEVICE → MAT=00"]
    UNCACHED["UNCACHED → MAT=10"]
    CACHED["Default → MAT=01"]
end

MF --> CACHED
MF --> DEVICE
MF --> EXECUTE
MF --> READ
MF --> UNCACHED
MF --> USER
MF --> WRITE
PF --> MAT
PF --> NR
PF --> NX
PF --> PLV
PF --> W

Sources: page_table_entry/src/arch/loongarch64.rs(L61 - L119) 

Page Table Metadata

The LA64MetaData struct implements the PagingMetaData trait, providing architecture-specific constants and TLB management functionality.

Page Walk Controller Configuration

LoongArch64 uses Page Walk Controllers (PWC) to configure hardware page table walking. The implementation defines specific CSR values for both lower and upper address space translation:

flowchart TD
subgraph subGraph2["PWCH Fields"]
    DIR3BASE["Dir3Base = 39Directory 3 Base"]
    DIR3WIDTH["Dir3Width = 9Directory 3 Width"]
    HPTW["HPTW_En = 0Hardware Page Table Walk"]
end
subgraph subGraph1["PWCL Fields"]
    PTBASE["PTBase = 12Page Table Base"]
    PTWIDTH["PTWidth = 9Page Table Width"]
    DIR1BASE["Dir1Base = 21Directory 1 Base"]
    DIR1WIDTH["Dir1Width = 9Directory 1 Width"]
    DIR2BASE["Dir2Base = 30Directory 2 Base"]
    DIR2WIDTH["Dir2Width = 9Directory 2 Width"]
end
subgraph subGraph0["PWC Configuration"]
    PWCL_REG["PWCL CSR (0x1C)Lower Half Address Space"]
    PWCH_REG["PWCH CSR (0x1D)Higher Half Address Space"]
end

PWCH_REG --> DIR3BASE
PWCH_REG --> DIR3WIDTH
PWCH_REG --> HPTW
PWCL_REG --> DIR1BASE
PWCL_REG --> DIR1WIDTH
PWCL_REG --> DIR2BASE
PWCL_REG --> DIR2WIDTH
PWCL_REG --> PTBASE
PWCL_REG --> PTWIDTH

Sources: page_table_multiarch/src/arch/loongarch64.rs(L11 - L41) 

TLB Management

The LoongArch64 implementation provides sophisticated TLB (Translation Lookaside Buffer) management using the invtlb instruction with data barrier synchronization:

flowchart TD
subgraph Synchronization["Synchronization"]
    DBAR["dbar 0Data BarrierMemory ordering"]
end
subgraph subGraph0["TLB Flush Operations"]
    FLUSH_ALL["flush_tlb(None)Clear all entriesinvtlb 0x00"]
    FLUSH_ADDR["flush_tlb(Some(vaddr))Clear specific entryinvtlb 0x05"]
end

DBAR --> FLUSH_ADDR
DBAR --> FLUSH_ALL

The implementation uses:

  • dbar 0: Data barrier ensuring memory ordering
  • invtlb 0x00: Clear all page table entries
  • invtlb 0x05: Clear entries matching specific virtual address with G=0

Sources: page_table_multiarch/src/arch/loongarch64.rs(L49 - L76) 

Integration with Generic System

The LoongArch64 implementation seamlessly integrates with the generic trait system, providing the type alias LA64PageTable and implementing all required traits.

Trait Implementation Structure

flowchart TD
subgraph subGraph2["Constants & Methods"]
    LEVELS["LEVELS = 4"]
    PA_BITS["PA_MAX_BITS = 48"]
    VA_BITS["VA_MAX_BITS = 48"]
    FLUSH["flush_tlb()"]
    NEW_PAGE["new_page()"]
    NEW_TABLE["new_table()"]
    FLAGS["flags()"]
    PADDR["paddr()"]
end
subgraph subGraph1["LoongArch64 Implementation"]
    LA64MD["LA64MetaData"]
    LA64PTE["LA64PTE"]
    LA64PT["LA64PageTable<I>"]
end
subgraph subGraph0["Generic Traits"]
    PMD["PagingMetaData trait"]
    GPTE["GenericPTE trait"]
    PT64["PageTable64<M,PTE,H>"]
end

GPTE --> FLAGS
GPTE --> NEW_PAGE
GPTE --> NEW_TABLE
GPTE --> PADDR
LA64MD --> PMD
LA64PT --> PT64
LA64PTE --> GPTE
PMD --> FLUSH
PMD --> LEVELS
PMD --> PA_BITS
PMD --> VA_BITS

Sources: page_table_multiarch/src/arch/loongarch64.rs(L43 - L76)  page_table_entry/src/arch/loongarch64.rs(L135 - L177)  page_table_multiarch/src/arch/loongarch64.rs(L85) 

Type Aliases and Exports

The LoongArch64 support is exposed through a clean type alias that follows the library's naming conventions:

pub type LA64PageTable<I> = PageTable64<LA64MetaData, LA64PTE, I>;

This allows users to instantiate LoongArch64 page tables with their preferred PagingHandler implementation while maintaining full compatibility with the generic PageTable64 interface.

Sources: page_table_multiarch/src/arch/loongarch64.rs(L78 - L85) 

Development Guide

Relevant source files

This guide provides essential information for developers working with or contributing to the page_table_multiarch library. It covers the development environment setup, multi-architecture compilation strategy, testing approach, and documentation generation processes.

For detailed build instructions and testing procedures, see Building and Testing. For contribution guidelines and coding standards, see Contributing.

Development Environment Overview

The page_table_multiarch project uses a sophisticated multi-architecture development approach that requires specific toolchain configurations and conditional compilation strategies. The project is designed to work across five different target architectures while maintaining a unified development experience.

Development Architecture Matrix

flowchart TD
subgraph subGraph2["CI Pipeline Components"]
    FORMAT["cargo fmt"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test"]
    DOC["cargo doc"]
end
subgraph subGraph1["Target Architectures"]
    LINUX["x86_64-unknown-linux-gnu"]
    NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
    LOONG["loongarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Development Environment"]
    RUST["Rust Nightly Toolchain"]
    COMPONENTS["rust-src, clippy, rustfmt"]
end

ARM --> BUILD
ARM --> CLIPPY
COMPONENTS --> ARM
COMPONENTS --> LINUX
COMPONENTS --> LOONG
COMPONENTS --> NONE
COMPONENTS --> RISCV
LINUX --> BUILD
LINUX --> CLIPPY
LINUX --> FORMAT
LINUX --> TEST
LOONG --> BUILD
LOONG --> CLIPPY
NONE --> BUILD
NONE --> CLIPPY
RISCV --> BUILD
RISCV --> CLIPPY
RUST --> COMPONENTS
TEST --> DOC

Sources: .github/workflows/ci.yml(L10 - L32) 

Workspace Dependency Structure

The development environment manages dependencies through a two-crate workspace with architecture-specific conditional compilation. The dependency resolution varies based on the target platform to include only necessary architecture support crates.

flowchart TD
subgraph subGraph2["Hardware Support Crates"]
    TOCK["tock-registers v0.9.0"]
    RAW_CPUID["raw-cpuid v10.7.0"]
    BIT_FIELD["bit_field v0.10.2"]
    BIT_FIELD_2["bit_field v0.10.2"]
    VOLATILE["volatile v0.4.6"]
    CRITICAL["critical-section v1.2.0"]
    EMBEDDED["embedded-hal v1.0.0"]
end
subgraph subGraph1["page_table_entry Dependencies"]
    PTE_MEM["memory_addr v0.3.1"]
    AARCH64["aarch64-cpu v10.0.0"]
    BITFLAGS["bitflags v2.8.0"]
    X86_64["x86_64 v0.15.2"]
end
subgraph subGraph0["page_table_multiarch Dependencies"]
    PTM["page_table_multiarch v0.5.3"]
    LOG["log v0.4.25"]
    MEMORY["memory_addr v0.3.1"]
    PTE["page_table_entry v0.5.3"]
    RISCV_CRATE["riscv v0.12.1"]
    X86_CRATE["x86 v0.52.0"]
end

AARCH64 --> TOCK
PTE --> AARCH64
PTE --> BITFLAGS
PTE --> PTE_MEM
PTE --> X86_64
PTM --> LOG
PTM --> MEMORY
PTM --> PTE
PTM --> RISCV_CRATE
PTM --> X86_CRATE
RISCV_CRATE --> CRITICAL
RISCV_CRATE --> EMBEDDED
X86_CRATE --> BIT_FIELD
X86_CRATE --> RAW_CPUID

Sources: Cargo.lock(L57 - L75)  Cargo.lock(L5 - L149) 

Multi-Architecture Development Strategy

The project employs a conditional compilation strategy that allows development and testing across multiple architectures while maintaining code clarity and avoiding unnecessary dependencies for specific targets.

Conditional Compilation System

The build system uses Cargo's target-specific dependencies and feature flags to include only the necessary architecture support:

Target ArchitectureIncluded DependenciesSpecial Features
x86_64-unknown-linux-gnux86,x86_64, full test suiteUnit testing enabled
x86_64-unknown-nonex86,x86_64Bare metal support
riscv64gc-unknown-none-elfriscvRISC-V Sv39/Sv48 support
aarch64-unknown-none-softfloataarch64-cpuARM EL2 support available
loongarch64-unknown-none-softfloatBasic LoongArch supportCustom PWC configuration

Documentation Build Configuration

Documentation generation requires special configuration to include all architecture-specific code regardless of the build target:

flowchart TD
subgraph subGraph1["Architecture Inclusion"]
    ALL_ARCH["All architecture modules included"]
    X86_DOCS["x86_64 documentation"]
    ARM_DOCS["AArch64 documentation"]
    RISCV_DOCS["RISC-V documentation"]
    LOONG_DOCS["LoongArch documentation"]
end
subgraph subGraph0["Documentation Build Process"]
    DOC_ENV["RUSTFLAGS: --cfg doc"]
    DOC_FLAGS["RUSTDOCFLAGS: -Zunstable-options --enable-index-page"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
end

ALL_ARCH --> ARM_DOCS
ALL_ARCH --> LOONG_DOCS
ALL_ARCH --> RISCV_DOCS
ALL_ARCH --> X86_DOCS
DOC_BUILD --> ALL_ARCH
DOC_ENV --> DOC_BUILD
DOC_FLAGS --> DOC_BUILD

Sources: .github/workflows/ci.yml(L40 - L49) 

CI/CD Pipeline Architecture

The continuous integration system validates code across all supported architectures using a matrix build strategy. This ensures that changes work correctly across the entire supported platform ecosystem.

CI Matrix Strategy

flowchart TD
subgraph subGraph3["Documentation Pipeline"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
    PAGES_DEPLOY["GitHub Pages Deployment"]
end
subgraph subGraph2["Validation Steps"]
    VERSION_CHECK["rustc --version --verbose"]
    FORMAT_CHECK["cargo fmt --all -- --check"]
    CLIPPY_CHECK["cargo clippy --target TARGET --all-features"]
    BUILD_STEP["cargo build --target TARGET --all-features"]
    UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["Build Matrix"]
    NIGHTLY["Rust Nightly Toolchain"]
    TARGETS["5 Target Architectures"]
end
subgraph subGraph0["CI Trigger Events"]
    PUSH["git push"]
    PR["pull_request"]
end

BUILD_STEP --> UNIT_TEST
CLIPPY_CHECK --> BUILD_STEP
DOC_BUILD --> PAGES_DEPLOY
FORMAT_CHECK --> CLIPPY_CHECK
NIGHTLY --> TARGETS
PR --> NIGHTLY
PUSH --> NIGHTLY
TARGETS --> VERSION_CHECK
UNIT_TEST --> DOC_BUILD
VERSION_CHECK --> FORMAT_CHECK

Sources: .github/workflows/ci.yml(L1 - L33)  .github/workflows/ci.yml(L34 - L56) 

Quality Gates

The CI pipeline enforces several quality gates that must pass before code integration:

Check TypeCommandScopeFailure Behavior
Formatcargo fmt --all -- --checkAll filesHard failure
Clippycargo clippy --target TARGET --all-featuresPer targetHard failure
Buildcargo build --target TARGET --all-featuresPer targetHard failure
Unit Testscargo test --target x86_64-unknown-linux-gnuLinux onlyHard failure
Documentationcargo doc --no-deps --all-featuresAll featuresSoft failure on non-default branch

Development Workflow Integration

The development environment integrates multiple tools and processes to maintain code quality across architecture boundaries:

Toolchain Requirements

  • Rust Toolchain: Nightly required for unstable features and cross-compilation support
  • Components: rust-src for cross-compilation, clippy for linting, rustfmt for formatting
  • Targets: All five supported architectures must be installed for comprehensive testing

Feature Flag Usage

The project uses Cargo feature flags to control compilation of architecture-specific code:

flowchart TD
subgraph subGraph1["Compilation Outcomes"]
    FULL_BUILD["Complete architecture support"]
    DOC_BUILD["Documentation with all architectures"]
    ARM_PRIVILEGE["ARM EL2 privilege level support"]
end
subgraph subGraph0["Feature Control"]
    ALL_FEATURES["--all-features"]
    DOC_CFG["--cfg doc"]
    ARM_EL2["arm-el2 feature"]
end

ALL_FEATURES --> FULL_BUILD
ARM_EL2 --> ARM_PRIVILEGE
DOC_CFG --> DOC_BUILD
FULL_BUILD --> DOC_BUILD

Sources: .github/workflows/ci.yml(L25 - L27)  .github/workflows/ci.yml(L42) 

The development environment is designed to handle the complexity of multi-architecture support while providing developers with clear feedback about compatibility and correctness across all supported platforms. The CI pipeline ensures that every change is validated against the complete matrix of supported architectures before integration.

Building and Testing

Relevant source files

This page covers the development workflow for building and testing the page_table_multiarch library. It explains the multi-architecture build system, test execution across different targets, and the CI/CD pipeline that ensures code quality across all supported platforms.

For information about contributing code changes, see Contributing. For architectural details about the supported platforms, see Supported Platforms.

Build System Architecture

The project uses a Cargo workspace structure with conditional compilation to support multiple processor architectures. The build system automatically includes only the necessary dependencies and code paths based on the target architecture.

Workspace Structure

flowchart TD
subgraph subGraph3["Conditional Dependencies"]
    TARGET_CFG["Target Architecture"]
    X86_DEPS["x86 v0.52x86_64 v0.15.1"]
    ARM_DEPS["aarch64-cpu v10.0"]
    RV_DEPS["riscv v0.12"]
    LA_DEPS["Built-in support"]
    DOC_DEPS["All dependenciesfor documentation"]
end
subgraph subGraph2["Cargo Workspace"]
    ROOT["Cargo.tomlWorkspace Root"]
    subgraph subGraph1["page_table_entry"]
        PTE_CARGO["page_table_entry/Cargo.toml"]
        PTE_LIB["lib.rs"]
        PTE_ARCH["arch/ entries"]
    end
    subgraph page_table_multiarch["page_table_multiarch"]
        PTM_CARGO["page_table_multiarch/Cargo.toml"]
        PTM_LIB["lib.rs"]
        PTM_ARCH["arch/ modules"]
    end
end

PTE_CARGO --> ARM_DEPS
PTE_CARGO --> DOC_DEPS
PTE_CARGO --> X86_DEPS
PTM_CARGO --> ARM_DEPS
PTM_CARGO --> LA_DEPS
PTM_CARGO --> PTE_CARGO
PTM_CARGO --> RV_DEPS
PTM_CARGO --> X86_DEPS
ROOT --> PTE_CARGO
ROOT --> PTM_CARGO
TARGET_CFG --> ARM_DEPS
TARGET_CFG --> DOC_DEPS
TARGET_CFG --> LA_DEPS
TARGET_CFG --> RV_DEPS
TARGET_CFG --> X86_DEPS

Architecture-Specific Dependencies

The build system conditionally includes dependencies based on the compilation target:

Target ArchitectureDependenciesPurpose
x86_64x86 v0.52,x86_64 v0.15.1x86-specific register and instruction access
aarch64aarch64-cpu v10.0ARM system register manipulation
riscv32/64riscv v0.12RISC-V CSR and instruction support
loongarch64Built-inNative LoongArch64 support
DocumentationAll dependenciesComplete API documentation

Sources: Cargo.toml(L1 - L20)  Cargo.lock(L1 - L150) 

Development Environment Setup

Prerequisites

The project requires the Rust nightly toolchain with specific components and targets:

# Install nightly toolchain with required components
rustup toolchain install nightly
rustup component add --toolchain nightly rust-src clippy rustfmt

# Add target architectures for cross-compilation
rustup target add --toolchain nightly x86_64-unknown-none
rustup target add --toolchain nightly riscv64gc-unknown-none-elf
rustup target add --toolchain nightly aarch64-unknown-none-softfloat
rustup target add --toolchain nightly loongarch64-unknown-none-softfloat

Environment Configuration

For documentation builds and certain tests, set the doc configuration flag:

export RUSTFLAGS="--cfg doc"

This enables all architecture-specific code paths during documentation generation, ensuring complete API coverage.

Sources: .github/workflows/ci.yml(L15 - L19)  .github/workflows/ci.yml(L31 - L32)  .github/workflows/ci.yml(L42) 

Building the Project

Basic Build Commands

# Build for the host architecture (typically x86_64)
cargo build

# Build with all features enabled
cargo build --all-features

# Cross-compile for specific targets
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
cargo build --target loongarch64-unknown-none-softfloat --all-features

Build Process Flow

flowchart TD
subgraph Compilation["Compilation"]
    COMPILE["rustc compilation"]
    CFG_FLAGS["Conditional compilation#[cfg(target_arch)]"]
    ARCH_MODULES["Architecture-specific modules"]
    LINK["Link final binary/library"]
end
subgraph subGraph0["Dependency Resolution"]
    RESOLVE["Cargo resolves dependencies"]
    TARGET_CHECK["Target Architecture"]
    X86_PATH["Include x86/x86_64 crates"]
    ARM_PATH["Include aarch64-cpu crate"]
    RV_PATH["Include riscv crate"]
    LA_PATH["Built-in support only"]
end
START["cargo build --target TARGET"]

ARCH_MODULES --> LINK
ARM_PATH --> COMPILE
CFG_FLAGS --> ARCH_MODULES
COMPILE --> CFG_FLAGS
LA_PATH --> COMPILE
RESOLVE --> TARGET_CHECK
RV_PATH --> COMPILE
START --> RESOLVE
TARGET_CHECK --> ARM_PATH
TARGET_CHECK --> LA_PATH
TARGET_CHECK --> RV_PATH
TARGET_CHECK --> X86_PATH
X86_PATH --> COMPILE

The build process uses Rust's conditional compilation features to include only the relevant code and dependencies for each target architecture.

Sources: .github/workflows/ci.yml(L26 - L27) 

Running Tests

Test Execution Matrix

Tests are executed differently based on the target platform due to hardware and emulation constraints:

TargetTest TypeExecution Environment
x86_64-unknown-linux-gnuUnit testsNative execution
x86_64-unknown-noneBuild verificationCompile-only
riscv64gc-unknown-none-elfBuild verificationCompile-only
aarch64-unknown-none-softfloatBuild verificationCompile-only
loongarch64-unknown-none-softfloatBuild verificationCompile-only

Test Commands

# Run all tests (only works on x86_64-unknown-linux-gnu)
cargo test -- --nocapture

# Build verification for embedded targets
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
cargo build --target loongarch64-unknown-none-softfloat --all-features

# Code quality checks
cargo fmt --all -- --check
cargo clippy --target TARGET --all-features -- -A clippy::new_without_default

Test Architecture

flowchart TD
subgraph subGraph1["Target Matrix"]
    LINUX["x86_64-unknown-linux-gnu"]
    BARE_X86["x86_64-unknown-none"]
    RV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
    LOONG["loongarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Test Pipeline"]
    FORMAT["cargo fmt --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    UNIT_TEST["cargo test"]
end

BUILD --> ARM
BUILD --> BARE_X86
BUILD --> LINUX
BUILD --> LOONG
BUILD --> RV
BUILD --> UNIT_TEST
CLIPPY --> BUILD
FORMAT --> CLIPPY
UNIT_TEST --> LINUX

Unit tests execute only on x86_64-unknown-linux-gnu because the embedded targets lack standard library support required for the test harness.

Sources: .github/workflows/ci.yml(L28 - L32)  .github/workflows/ci.yml(L24 - L25)  .github/workflows/ci.yml(L22 - L23) 

CI/CD Pipeline

GitHub Actions Workflow

The CI pipeline runs on every push and pull request, executing a comprehensive test matrix across all supported architectures.

flowchart TD
subgraph Documentation["Documentation"]
    DOC_BUILD["cargo doc --no-deps"]
    DEPLOY["Deploy to GitHub Pages"]
end
subgraph subGraph3["CI Steps"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN["Setup Rust nightly"]
    VERSION_CHECK["rustc --version"]
    FMT_CHECK["Format check"]
    CLIPPY_CHECK["Clippy analysis"]
    BUILD_STEP["Build verification"]
    TEST_STEP["Unit test execution"]
end
subgraph subGraph2["Build Matrix"]
    NIGHTLY["Rust Nightly Toolchain"]
    subgraph subGraph1["Target Matrix"]
        LINUX_GNU["x86_64-unknown-linux-gnu"]
        X86_NONE["x86_64-unknown-none"]
        RISCV64["riscv64gc-unknown-none-elf"]
        AARCH64["aarch64-unknown-none-softfloat"]
        LOONGARCH["loongarch64-unknown-none-softfloat"]
    end
end
subgraph subGraph0["CI Triggers"]
    PUSH["git push"]
    PR["Pull Request"]
end

AARCH64 --> BUILD_STEP
BUILD_STEP --> TEST_STEP
CHECKOUT --> DOC_BUILD
CHECKOUT --> TOOLCHAIN
CLIPPY_CHECK --> BUILD_STEP
DOC_BUILD --> DEPLOY
FMT_CHECK --> CLIPPY_CHECK
LINUX_GNU --> TEST_STEP
LOONGARCH --> BUILD_STEP
PR --> CHECKOUT
PUSH --> CHECKOUT
RISCV64 --> BUILD_STEP
TOOLCHAIN --> VERSION_CHECK
VERSION_CHECK --> FMT_CHECK
X86_NONE --> BUILD_STEP

CI Configuration Details

The workflow configuration includes specific settings for multi-architecture support:

ConfigurationValuePurpose
fail-fastfalseContinue testing other targets if one fails
rust-toolchainnightlyRequired for unstable features
componentsrust-src, clippy, rustfmtDevelopment tools and source code
RUSTFLAGS--cfg docEnable documentation-specific code paths
RUSTDOCFLAGS-Zunstable-options --enable-index-pageEnhanced documentation features

Sources: .github/workflows/ci.yml(L1 - L57) 

Documentation Generation

Local Documentation Build

# Build documentation with all features
RUSTFLAGS="--cfg doc" cargo doc --no-deps --all-features

# Open generated documentation
open target/doc/page_table_multiarch/index.html

Documentation Pipeline

sequenceDiagram
    participant Developer as Developer
    participant GitHubActions as GitHub Actions
    participant CargoDoc as Cargo Doc
    participant GitHubPages as GitHub Pages

    Developer ->> GitHubActions: Push to main branch
    GitHubActions ->> CargoDoc: RUSTFLAGS=--cfg doc
    Note over CargoDoc: cargo doc --no-deps --all-features
    CargoDoc ->> GitHubActions: Generated documentation
    GitHubActions ->> GitHubPages: Deploy to gh-pages branch
    GitHubPages ->> Developer: Updated documentation site
    Note over GitHubActions,GitHubPages: Documentation URL:<br>https://arceos-org.github.io/page_table_multiarch

The documentation build process uses special configuration flags to ensure all architecture-specific APIs are documented, even when building on a single architecture.

Documentation Features

  • Index page generation: Provides a unified entry point for all crates
  • Broken link detection: Fails build on invalid cross-references
  • Missing documentation warnings: Ensures comprehensive API coverage
  • All-features documentation: Includes conditional compilation paths

Sources: .github/workflows/ci.yml(L34 - L57)  .github/workflows/ci.yml(L43)  Cargo.toml(L15) 

Contributing

Relevant source files

This document provides guidelines for contributing to the page_table_multiarch project, including development environment setup, code quality standards, testing requirements, and the contribution workflow. For information about building and running tests, see Building and Testing.

Development Environment Setup

Required Tools

The project requires the nightly Rust toolchain with specific components and targets for multi-architecture support.

flowchart TD
subgraph Targets["Targets"]
    LINUX["x86_64-unknown-linux-gnu"]
    BARE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM["aarch64-unknown-none-softfloat"]
    LOONG["loongarch64-unknown-none-softfloat"]
end
subgraph Components["Components"]
    RUSTSRC["rust-src"]
    CLIPPY["clippy"]
    RUSTFMT["rustfmt"]
end
subgraph subGraph0["Rust Toolchain Requirements"]
    NIGHTLY["nightly toolchain"]
    COMPONENTS["Required Components"]
    TARGETS["Target Architectures"]
end
DEV["Development Environment"]

COMPONENTS --> CLIPPY
COMPONENTS --> RUSTFMT
COMPONENTS --> RUSTSRC
DEV --> NIGHTLY
NIGHTLY --> COMPONENTS
NIGHTLY --> TARGETS
TARGETS --> ARM
TARGETS --> BARE
TARGETS --> LINUX
TARGETS --> LOONG
TARGETS --> RISCV

Development Environment Setup

Install the required toolchain components as specified in the CI configuration:

Sources: .github/workflows/ci.yml(L15 - L19) 

IDE Configuration

The project includes basic IDE configuration exclusions in .gitignore for common development environments:

  • /target - Rust build artifacts
  • /.vscode - Visual Studio Code settings
  • .DS_Store - macOS system files

Sources: .gitignore(L1 - L4) 

Code Quality Standards

Formatting Requirements

All code must be formatted using cargo fmt with the default Rust formatting rules. The CI pipeline enforces this requirement:

flowchart TD
PR["Pull Request"]
CHECK["Format Check"]
PASS["Check Passes"]
FAIL["Check Fails"]
REJECT["PR Blocked"]
CONTINUE["Continue CI"]

CHECK --> FAIL
CHECK --> PASS
FAIL --> REJECT
PASS --> CONTINUE
PR --> CHECK

Formatting Enforcement

The CI runs cargo fmt --all -- --check to verify formatting compliance.

Sources: .github/workflows/ci.yml(L22 - L23) 

Linting Standards

Code must pass Clippy analysis with the project's configured lint rules:

flowchart TD
subgraph subGraph1["Allowed Exceptions"]
    NEW_WITHOUT_DEFAULT["clippy::new_without_default"]
end
subgraph subGraph0["Lint Configuration"]
    ALLOW["Allowed Lints"]
    TARGETS_ALL["All Target Architectures"]
    FEATURES["All Features Enabled"]
end
CLIPPY["Clippy Analysis"]

ALLOW --> NEW_WITHOUT_DEFAULT
CLIPPY --> ALLOW
CLIPPY --> FEATURES
CLIPPY --> TARGETS_ALL

Clippy Configuration

The project allows the clippy::new_without_default lint, indicating that new() methods without corresponding Default implementations are acceptable in this codebase.

Sources: .github/workflows/ci.yml(L24 - L25) 

Testing Requirements

Test Execution Strategy

The project uses a targeted testing approach where unit tests only run on the x86_64-unknown-linux-gnu target, while other targets focus on compilation validation:

flowchart TD
subgraph subGraph1["Test Execution"]
    LINUX_ONLY["x86_64-unknown-linux-gnu only"]
    UNIT_TESTS["cargo test"]
    DOC_CFG["RUSTFLAGS: --cfg doc"]
end
subgraph subGraph0["Build Verification"]
    ALL_TARGETS["All Targets"]
    BUILD["cargo build"]
    CLIPPY_CHECK["cargo clippy"]
end
CI["CI Pipeline"]

ALL_TARGETS --> BUILD
ALL_TARGETS --> CLIPPY_CHECK
CI --> ALL_TARGETS
CI --> LINUX_ONLY
LINUX_ONLY --> DOC_CFG
LINUX_ONLY --> UNIT_TESTS

Test Environment Configuration

Unit tests run with RUSTFLAGS: --cfg doc to enable documentation-conditional code paths.

Sources: .github/workflows/ci.yml(L28 - L32) 

Multi-Architecture Validation

While unit tests only run on Linux, the CI ensures that all architecture-specific code compiles correctly across all supported targets:

TargetPurposeValidation
x86_64-unknown-linux-gnuDevelopment and testingFull test suite
x86_64-unknown-noneBare metal x86_64Build + Clippy
riscv64gc-unknown-none-elfRISC-V bare metalBuild + Clippy
aarch64-unknown-none-softfloatARM64 bare metalBuild + Clippy
loongarch64-unknown-none-softfloatLoongArch64 bare metalBuild + Clippy

Sources: .github/workflows/ci.yml(L10 - L12)  .github/workflows/ci.yml(L24 - L27) 

Documentation Standards

Documentation Generation

The project maintains comprehensive API documentation that is automatically built and deployed:

flowchart TD
subgraph Deployment["Deployment"]
    BUILD["cargo doc --no-deps"]
    DEPLOY["GitHub Pages"]
    BRANCH["gh-pages"]
end
subgraph subGraph1["Documentation Flags"]
    INDEX["--enable-index-page"]
    BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"]
    MISSING_DOCS["-D missing-docs"]
end
subgraph subGraph0["Build Configuration"]
    RUSTFLAGS["RUSTFLAGS: --cfg doc"]
    RUSTDOCFLAGS["RUSTDOCFLAGS"]
    ALL_FEATURES["--all-features"]
end
DOC_JOB["Documentation Job"]

BUILD --> DEPLOY
DEPLOY --> BRANCH
DOC_JOB --> ALL_FEATURES
DOC_JOB --> BUILD
DOC_JOB --> RUSTDOCFLAGS
DOC_JOB --> RUSTFLAGS
RUSTDOCFLAGS --> BROKEN_LINKS
RUSTDOCFLAGS --> INDEX
RUSTDOCFLAGS --> MISSING_DOCS

Documentation Requirements

  • All public APIs must have documentation comments
  • Documentation links must be valid (enforced by -D rustdoc::broken_intra_doc_links)
  • Missing documentation is treated as an error (-D missing-docs)
  • Documentation is built with all features enabled

Sources: .github/workflows/ci.yml(L40 - L43)  .github/workflows/ci.yml(L47 - L49) 

Pull Request Workflow

CI Pipeline Execution

Every pull request triggers the complete CI pipeline across all supported architectures:

flowchart TD
subgraph subGraph2["Documentation Execution"]
    DOC_BUILD["Build Documentation"]
    DOC_DEPLOY["Deploy to gh-pages"]
end
subgraph subGraph1["CI Matrix Execution"]
    FORMAT["Format Check"]
    CLIPPY_ALL["Clippy (All Targets)"]
    BUILD_ALL["Build (All Targets)"]
    TEST_LINUX["Unit Tests (Linux only)"]
end
subgraph subGraph0["Parallel CI Jobs"]
    CI_MATRIX["CI Matrix Job"]
    DOC_JOB["Documentation Job"]
end
PR["Pull Request"]

CI_MATRIX --> BUILD_ALL
CI_MATRIX --> CLIPPY_ALL
CI_MATRIX --> FORMAT
CI_MATRIX --> TEST_LINUX
DOC_BUILD --> DOC_DEPLOY
DOC_JOB --> DOC_BUILD
PR --> CI_MATRIX
PR --> DOC_JOB

CI Trigger Events

The CI pipeline runs on both push and pull_request events, ensuring comprehensive validation.

Sources: .github/workflows/ci.yml(L3)  .github/workflows/ci.yml(L5 - L33)  .github/workflows/ci.yml(L34 - L57) 

Deployment Process

Documentation deployment occurs automatically when changes are merged to the default branch, using the JamesIves/github-pages-deploy-action@v4 action with single-commit deployment to the gh-pages branch.

Sources: .github/workflows/ci.yml(L50 - L56) 

Architecture-Specific Considerations

When contributing architecture-specific code, ensure that:

  1. Conditional Compilation: Use appropriate cfg attributes for target-specific code
  2. Cross-Architecture Compatibility: Changes should not break compilation on other architectures
  3. Testing Coverage: While unit tests only run on x86_64, ensure your code compiles cleanly on all targets
  4. Documentation: Architecture-specific features should be clearly documented with appropriate cfg_attr annotations

The CI matrix validates all contributions across the full range of supported architectures, providing confidence that cross-platform compatibility is maintained.

Sources: .github/workflows/ci.yml(L10 - L12)  .github/workflows/ci.yml(L24 - L27) 

Overview

Relevant source files

This document provides a comprehensive overview of the x86_rtc crate, a specialized Rust library that provides low-level hardware access to the Real Time Clock (RTC) on x86_64 systems via CMOS interface. The crate is designed for operating system kernels, embedded systems, and bare-metal applications that require direct hardware clock access without relying on operating system services.

For detailed API documentation and usage patterns, see RTC Driver API. For information about the underlying hardware protocols and register access, see CMOS Hardware Interface. For build configuration and dependency management, see Crate Definition and Metadata.

System Architecture

The x86_rtc crate operates as a hardware abstraction layer between application code and the x86_64 CMOS hardware that maintains system time.

High-Level System Architecture

flowchart TD
subgraph subGraph3["Physical Hardware"]
    CMOS["CMOS Chip"]
    RTC_HW["Real Time Clock"]
    BATTERY["Battery Backup"]
end
subgraph subGraph2["Hardware Abstraction"]
    X86_64["x86_64 crate"]
    PORTS["I/O Port Access"]
    CMD_PORT["CMOS_COMMAND_PORT"]
    DATA_PORT["CMOS_DATA_PORT"]
end
subgraph subGraph1["x86_rtc Crate"]
    API["Rtc struct"]
    TIMESTAMP["get_unix_timestamp()"]
    SETTER["set_unix_timestamp()"]
    CONVERSION["BCD/Binary Conversion"]
end
subgraph subGraph0["Application Layer"]
    APP["Application Code"]
    OS["Operating Systems"]
    EMBEDDED["Embedded Systems"]
end

API --> SETTER
API --> TIMESTAMP
APP --> API
CMD_PORT --> CMOS
CMOS --> BATTERY
CMOS --> RTC_HW
CONVERSION --> X86_64
DATA_PORT --> CMOS
EMBEDDED --> API
OS --> API
PORTS --> CMD_PORT
PORTS --> DATA_PORT
SETTER --> CONVERSION
TIMESTAMP --> CONVERSION

Sources: Cargo.toml(L1 - L22)  README.md(L1 - L13) 

Code Entity Mapping

This diagram maps the crate's public interface to its internal implementation and hardware dependencies, showing the relationship between user-facing APIs and underlying code constructs.

Code Entity Architecture


Sources: Cargo.toml(L14 - L18)  README.md(L9 - L12) 

Key Characteristics

CharacteristicValueDescription
Crate Namex86_rtcPrimary identifier in Rust ecosystem
Version0.1.1Current stable release
Architecture Supportx86_64onlyHardware-specific implementation
Standard Libraryno_stdcompatibleSuitable for bare-metal environments
Primary Dependenciescfg-if,x86_64Minimal dependency footprint
LicenseTriple-licensedGPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
Target ApplicationsOS kernels, embedded systemsLow-level hardware access

Sources: Cargo.toml(L1 - L13) 

Integration Context

The x86_rtc crate is part of the broader ArceOS ecosystem, as indicated by its homepage and keywords. It serves as a specialized hardware driver component that can be integrated into larger operating system or embedded system projects.

Ecosystem Integration

flowchart TD
subgraph subGraph3["Use Cases"]
    SYSTEM_TIME["System Time Management"]
    BOOT_TIME["Boot Time Initialization"]
    TIMESTAMP["Event Timestamping"]
end
subgraph subGraph2["Target Platforms"]
    LINUX["x86_64-unknown-linux-gnu"]
    BARE_METAL["x86_64-unknown-none"]
    EMBEDDED["Embedded x86_64"]
end
subgraph subGraph1["x86_rtc Crate"]
    X86_RTC["x86_rtc v0.1.1"]
    RTC_API["RTC Hardware Interface"]
end
subgraph subGraph0["ArceOS Ecosystem"]
    ARCEOS["ArceOS Operating System"]
    DRIVERS["Hardware Drivers"]
    KERNEL["Kernel Components"]
end

ARCEOS --> DRIVERS
BARE_METAL --> BOOT_TIME
DRIVERS --> X86_RTC
EMBEDDED --> TIMESTAMP
KERNEL --> X86_RTC
LINUX --> SYSTEM_TIME
RTC_API --> BARE_METAL
RTC_API --> EMBEDDED
RTC_API --> LINUX
X86_RTC --> RTC_API

Sources: Cargo.toml(L8 - L12)  Cargo.toml(L11 - L12) 

The crate provides essential time-keeping functionality for systems that need direct hardware access to the RTC, particularly in contexts where higher-level operating system time services are unavailable or insufficient. Its design prioritizes minimal dependencies, hardware-specific optimization, and compatibility with both hosted and bare-metal environments.

Crate Definition and Metadata

Relevant source files

This page provides a detailed examination of the x86_rtc crate's configuration as defined in Cargo.toml. It covers package metadata, dependency specifications, build configurations, and platform-specific settings that define the crate's behavior and integration requirements.

For implementation details of the RTC driver functionality, see RTC Driver API. For broader dependency analysis including transitive dependencies, see Dependency Analysis.

Package Identification and Core Metadata

The crate is defined with specific metadata that establishes its identity and purpose within the Rust ecosystem.

FieldValuePurpose
name"x86_rtc"Unique crate identifier
version"0.1.1"Semantic version following SemVer
edition"2021"Rust language edition
authors["Keyang Hu keyang.hu@qq.com"]Primary maintainer contact

Crate Metadata Structure

flowchart TD
subgraph External_Links["External_Links"]
    HOMEPAGE["homepage: github.com/arceos-org/arceos"]
    REPOSITORY["repository: github.com/arceos-org/x86_rtc"]
    DOCUMENTATION["documentation: docs.rs/x86_rtc"]
end
subgraph Legal_Metadata["Legal_Metadata"]
    LICENSE["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
end
subgraph Description_Fields["Description_Fields"]
    DESC["description: System RTC Drivers for x86_64"]
    KEYWORDS["keywords: [arceos, x86_64, rtc]"]
    CATEGORIES["categories: [os, hardware-support, no-std]"]
end
subgraph Package_Definition["Package_Definition"]
    NAME["name: x86_rtc"]
    VERSION["version: 0.1.1"]
    EDITION["edition: 2021"]
    AUTHOR["authors: Keyang Hu"]
end

AUTHOR --> LICENSE
DESC --> HOMEPAGE
NAME --> DESC
VERSION --> KEYWORDS

Sources: Cargo.toml(L1 - L12) 

Functional Classification and Discovery

The crate uses specific classification metadata to enable discovery and communicate its intended use cases.

The description field Cargo.toml(L6)  explicitly identifies this as "System Real Time Clock (RTC) Drivers for x86_64 based on CMOS", establishing both the hardware target (x86_64) and the underlying technology (CMOS).

The keywords array Cargo.toml(L11)  includes:

  • "arceos" - Associates with the ArceOS operating system project
  • "x86_64" - Specifies the target architecture
  • "rtc" - Identifies the Real Time Clock functionality

The categories array Cargo.toml(L12)  places the crate in:

  • "os" - Operating system development
  • "hardware-support" - Hardware abstraction and drivers
  • "no-std" - Embedded and kernel development compatibility

Sources: Cargo.toml(L6)  Cargo.toml(L11 - L12) 

Dependency Architecture and Conditional Compilation

The crate implements a two-tier dependency strategy with conditional compilation for platform-specific functionality.

Dependency Configuration Structure

flowchart TD
subgraph Build_Matrix["Build_Matrix"]
    LINUX_TARGET["x86_64-unknown-linux-gnu"]
    BARE_TARGET["x86_64-unknown-none"]
    OTHER_ARCH["Other Architectures"]
end
subgraph Dependency_Purposes["Dependency_Purposes"]
    CFG_PURPOSE["Conditional Compilation Utilities"]
    X86_PURPOSE["Hardware Register Access"]
end
subgraph Platform_Conditional["Platform_Conditional"]
    TARGET_CONDITION["cfg(target_arch = x86_64)"]
    X86_64_DEP["x86_64 = 0.15"]
end
subgraph Unconditional_Dependencies["Unconditional_Dependencies"]
    CFG_IF["cfg-if = 1.0"]
end

CFG_IF --> CFG_PURPOSE
TARGET_CONDITION --> OTHER_ARCH
TARGET_CONDITION --> X86_64_DEP
X86_64_DEP --> BARE_TARGET
X86_64_DEP --> LINUX_TARGET
X86_64_DEP --> X86_PURPOSE

Core Dependencies

cfg-if v1.0 Cargo.toml(L15) 

  • Provides conditional compilation utilities
  • Included unconditionally across all platforms
  • Enables clean platform-specific code organization

Platform-Specific Dependencies

x86_64 v0.15 Cargo.toml(L17 - L18) 

  • Only included when target_arch = "x86_64"
  • Provides low-level hardware register access
  • Essential for CMOS port I/O operations
  • Version constraint allows compatible updates within 0.15.x

Sources: Cargo.toml(L14 - L18) 

The crate implements a triple-license strategy providing maximum compatibility across different use cases and legal requirements.

The license specification Cargo.toml(L7)  uses the SPDX format: "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"

This allows users to choose from:

  • GPL-3.0-or-later: Copyleft license for open-source projects
  • Apache-2.0: Permissive license for commercial integration
  • MulanPSL-2.0: Chinese-origin permissive license for regional compliance

Sources: Cargo.toml(L7) 

Repository and Documentation Infrastructure

The crate establishes a distributed documentation and development infrastructure across multiple platforms.

Link TypeURLPurpose
homepagehttps://github.com/arceos-org/arceosParent project (ArceOS)
repositoryhttps://github.com/arceos-org/x86_rtcSource code and issues
documentationhttps://docs.rs/x86_rtcAPI documentation

The separation between homepage and repository Cargo.toml(L8 - L9)  indicates this crate is part of the larger ArceOS ecosystem while maintaining its own development repository.

Sources: Cargo.toml(L8 - L10) 

Build Configuration and Code Quality

The crate defines specific linting rules to customize Clippy behavior for its use case.

Clippy Configuration Cargo.toml(L20 - L21) 

[lints.clippy]
new_without_default = "allow"

This configuration allows the new_without_default lint, permitting constructor functions named new() without requiring a corresponding Default implementation. This is appropriate for hardware drivers where default initialization may not be meaningful or safe.

Sources: Cargo.toml(L20 - L21) 

Crate Architecture Summary

Complete Crate Definition Flow

flowchart TD
subgraph Ecosystem_Integration["Ecosystem_Integration"]
    ARCEOS_PROJECT["ArceOS Operating System"]
    DOCS_RS["docs.rs Documentation"]
    CARGO_REGISTRY["crates.io Registry"]
end
subgraph Functionality["Functionality"]
    RTC_DRIVER["RTC Driver Implementation"]
    CMOS_ACCESS["CMOS Hardware Interface"]
end
subgraph Platform_Support["Platform_Support"]
    ARCH_CHECK["cfg(target_arch = x86_64)"]
    X86_ONLY["x86_64 hardware required"]
end
subgraph Identity["Identity"]
    CRATE_NAME["x86_rtc"]
    VERSION_NUM["v0.1.1"]
end

ARCEOS_PROJECT --> CARGO_REGISTRY
ARCH_CHECK --> X86_ONLY
CMOS_ACCESS --> DOCS_RS
CRATE_NAME --> ARCH_CHECK
RTC_DRIVER --> ARCEOS_PROJECT
VERSION_NUM --> RTC_DRIVER
X86_ONLY --> CMOS_ACCESS

The Cargo.toml configuration establishes x86_rtc as a specialized hardware driver crate with strict platform requirements, flexible licensing, and integration into the ArceOS ecosystem. The conditional dependency structure ensures the crate only pulls in hardware-specific dependencies when building for compatible targets.

Sources: Cargo.toml(L1 - L22) 

Quick Start Guide

Relevant source files

This page provides essential information for getting started with the x86_rtc crate, including basic usage patterns and immediate setup requirements. It covers the fundamental API calls needed to read and write system time via the x86_64 CMOS Real Time Clock interface.

For detailed information about the crate's configuration and dependencies, see Crate Definition and Metadata. For comprehensive API documentation, see RTC Driver API. For platform-specific requirements and architecture details, see Platform and Architecture Requirements.

Prerequisites

The x86_rtc crate requires an x86_64 target architecture and operates at a low level that typically requires kernel or bare-metal environments. The crate is no_std compatible and designed for system-level programming.

Platform Requirements

RequirementDetails
Architecturex86_64 only
EnvironmentBare metal, kernel, or privileged user space
Rust Edition2021
no_stdSupportYes

Sources: src/lib.rs(L8)  src/lib.rs(L196 - L226) 

Basic Usage Pattern

The core workflow involves three main steps: instantiation, reading time, and optionally setting time.

Basic Time Reading Flow

flowchart TD
USER["User Code"]
NEW["Rtc::new()"]
INSTANCE["Rtc struct instance"]
GET["get_unix_timestamp()"]
CMOS_READ["read_cmos_register()"]
HARDWARE["CMOS Hardware"]
TIMESTAMP["Unix timestamp (u64)"]

CMOS_READ --> HARDWARE
GET --> CMOS_READ
GET --> TIMESTAMP
INSTANCE --> GET
NEW --> INSTANCE
USER --> NEW

Sources: src/lib.rs(L96 - L101)  src/lib.rs(L103 - L129)  src/lib.rs(L206 - L211) 

API Method Mapping

flowchart TD
subgraph subGraph2["Hardware Layer"]
    COMMAND_PORT["CMOS_COMMAND_PORT (0x70)"]
    DATA_PORT["CMOS_DATA_PORT (0x71)"]
    TIME_REGISTERS["Time/Date Registers"]
end
subgraph subGraph1["Internal Implementation"]
    READ_REGISTER["read_cmos_register()"]
    WRITE_REGISTER["write_cmos_register()"]
    READ_VALUES["read_all_values()"]
    STATUS_CHECK["CMOS_STATUS_REGISTER_A"]
    FORMAT_DETECT["CMOS_STATUS_REGISTER_B"]
end
subgraph subGraph0["Public API"]
    NEW_METHOD["Rtc::new()"]
    GET_METHOD["get_unix_timestamp()"]
    SET_METHOD["set_unix_timestamp()"]
end

COMMAND_PORT --> TIME_REGISTERS
DATA_PORT --> TIME_REGISTERS
GET_METHOD --> READ_VALUES
GET_METHOD --> STATUS_CHECK
NEW_METHOD --> FORMAT_DETECT
READ_REGISTER --> COMMAND_PORT
READ_REGISTER --> DATA_PORT
READ_VALUES --> READ_REGISTER
SET_METHOD --> WRITE_REGISTER
WRITE_REGISTER --> COMMAND_PORT
WRITE_REGISTER --> DATA_PORT

Sources: src/lib.rs(L96 - L101)  src/lib.rs(L103 - L129)  src/lib.rs(L131 - L194)  src/lib.rs(L199 - L201)  src/lib.rs(L206 - L218) 

Essential Usage Examples

Reading Current Time

The most basic operation is reading the current Unix timestamp:

use x86_rtc::Rtc;

let rtc = Rtc::new();
let current_time = rtc.get_unix_timestamp();

The Rtc::new() method automatically detects the CMOS format configuration by reading CMOS_STATUS_REGISTER_B, while get_unix_timestamp() returns seconds since the Unix epoch (January 1, 1970).

Sources: src/lib.rs(L96 - L101)  src/lib.rs(L103 - L129)  README.md(L9 - L12) 

Setting System Time

To update the hardware clock with a new Unix timestamp:

use x86_rtc::Rtc;

let rtc = Rtc::new();
let new_time = 1672531200; // Example: January 1, 2023
rtc.set_unix_timestamp(new_time);

The set_unix_timestamp() method handles conversion from Unix time to the CMOS register format, including BCD conversion and format handling.

Sources: src/lib.rs(L131 - L194) 

Critical Implementation Details

Interrupt Handling Requirement

The get_unix_timestamp() method includes an important synchronization mechanism to ensure consistent reads:

ConcernImplementationRegister Used
Update in ProgressWait for completionCMOS_STATUS_REGISTER_A
Consistency CheckDouble-read verificationMultiple register reads
Atomic OperationLoop until consistentStatus flag monitoring

The method comment explicitly states: "The call to this RTC method requires the interrupt to be disabled, otherwise the value read may be inaccurate."

Sources: src/lib.rs(L105)  src/lib.rs(L107 - L129)  src/lib.rs(L16)  src/lib.rs(L19) 

Hardware Format Detection

The Rtc struct automatically adapts to different hardware configurations:

flowchart TD
INIT["Rtc::new()"]
READ_STATUS["Read CMOS_STATUS_REGISTER_B"]
CHECK_24H["Check CMOS_24_HOUR_FORMAT_FLAG"]
CHECK_BIN["Check CMOS_BINARY_FORMAT_FLAG"]
FORMAT_24H["24-hour format detection"]
FORMAT_BIN["Binary vs BCD format"]
ADAPT["Automatic format adaptation"]

CHECK_24H --> FORMAT_24H
CHECK_BIN --> FORMAT_BIN
FORMAT_24H --> ADAPT
FORMAT_BIN --> ADAPT
INIT --> READ_STATUS
READ_STATUS --> CHECK_24H
READ_STATUS --> CHECK_BIN

Sources: src/lib.rs(L98 - L100)  src/lib.rs(L17)  src/lib.rs(L20 - L21)  src/lib.rs(L30 - L36) 

Platform-Specific Behavior

The crate uses conditional compilation to handle different target architectures:

Target ArchitectureBehaviorImplementation
x86/x86_64Full hardware accessUsesx86_64::instructions::port::Port
Other architecturesStub implementationReturns zero/no-op

The actual hardware interaction occurs through I/O port operations on CMOS_COMMAND_PORT (0x70) and CMOS_DATA_PORT (0x71).

Sources: src/lib.rs(L196 - L226)  src/lib.rs(L198 - L201)  src/lib.rs(L203 - L204) 

Common Integration Patterns

Embedded/Bare Metal Usage

In no_std environments, the crate integrates directly without additional dependencies:

#![no_std]
use x86_rtc::Rtc;

// Typical usage in kernel or embedded context
let rtc = Rtc::new();
let boot_time = rtc.get_unix_timestamp();

Error Handling Considerations

The current API does not return Result types, operating under the assumption of successful hardware access. Error conditions are handled internally through retry loops and format detection.

Sources: src/lib.rs(L8)  src/lib.rs(L107 - L129) 

Next Steps

Sources: src/lib.rs(L1 - L277)  README.md(L1 - L13) 

Implementation

Relevant source files

This document provides a comprehensive overview of the x86_rtc crate's implementation architecture, covering the core RTC driver functionality, hardware abstraction mechanisms, and the interaction between different system components. For detailed API documentation, see RTC Driver API. For low-level hardware interface specifics, see CMOS Hardware Interface. For data format conversion details, see Data Format Handling.

Implementation Architecture

The x86_rtc implementation follows a layered architecture that abstracts hardware complexity while maintaining performance and safety. The core implementation centers around the Rtc struct, which encapsulates CMOS format configuration and provides high-level time operations.

Core Implementation Flow

flowchart TD
subgraph subGraph3["Low-Level Hardware"]
    COMMAND_PORT["COMMAND_PORT (0x70)"]
    DATA_PORT["DATA_PORT (0x71)"]
    CMOS_REGS["CMOS Registers"]
end
subgraph subGraph2["Hardware Abstraction"]
    READ_REG["read_cmos_register()"]
    WRITE_REG["write_cmos_register()"]
    CHECK_FORMAT["is_24_hour_format()is_binary_format()"]
end
subgraph subGraph1["Implementation Logic"]
    INIT["Read CMOS_STATUS_REGISTER_B"]
    SYNC["Synchronization Loop"]
    READ_ALL["read_all_values()"]
    CONVERT["Format Conversion"]
    CALC["Date/Time Calculations"]
end
subgraph subGraph0["Public API Layer"]
    NEW["Rtc::new()"]
    GET["get_unix_timestamp()"]
    SET["set_unix_timestamp()"]
end

CALC --> WRITE_REG
CHECK_FORMAT --> READ_REG
COMMAND_PORT --> CMOS_REGS
CONVERT --> CALC
DATA_PORT --> CMOS_REGS
GET --> SYNC
INIT --> READ_REG
NEW --> INIT
READ_ALL --> CHECK_FORMAT
READ_REG --> COMMAND_PORT
READ_REG --> DATA_PORT
SET --> CONVERT
SYNC --> READ_ALL
WRITE_REG --> COMMAND_PORT
WRITE_REG --> DATA_PORT

Sources: src/lib.rs(L24 - L194) 

The implementation follows a clear separation of concerns where each layer handles specific responsibilities. The public API provides Unix timestamp operations, the implementation logic handles format detection and conversion, and the hardware abstraction layer manages safe access to CMOS registers.

Rtc Structure and State Management

The Rtc struct maintains minimal state to optimize performance while ensuring thread safety through stateless operations where possible.

flowchart TD
subgraph subGraph2["CMOS Format Flags"]
    FLAG_24H["CMOS_24_HOUR_FORMAT_FLAG (1<<1)"]
    FLAG_BIN["CMOS_BINARY_FORMAT_FLAG (1<<2)"]
end
subgraph subGraph1["Format Detection Methods"]
    IS_24H["is_24_hour_format()"]
    IS_BIN["is_binary_format()"]
end
subgraph subGraph0["Rtc State"]
    STRUCT["Rtc { cmos_format: u8 }"]
end

IS_24H --> FLAG_24H
IS_BIN --> FLAG_BIN
STRUCT --> IS_24H
STRUCT --> IS_BIN

Sources: src/lib.rs(L24 - L36) 

The cmos_format field stores the value from CMOS_STATUS_REGISTER_B during initialization, allowing efficient format checking without repeated hardware access.

Synchronization and Consistency Mechanisms

The implementation employs sophisticated synchronization to handle CMOS update cycles and ensure data consistency.

flowchart TD
subgraph subGraph0["get_unix_timestamp() Flow"]
    START["Start"]
    CHECK_UPDATE["Check CMOS_UPDATE_IN_PROGRESS_FLAG"]
    SPIN_WAIT["spin_loop() wait"]
    READ_1["read_all_values() -> seconds_1"]
    CHECK_UPDATE_2["Check update flag again"]
    READ_2["read_all_values() -> seconds_2"]
    COMPARE["seconds_1 == seconds_2?"]
    RETURN["Return consistent value"]
end

CHECK_UPDATE --> CHECK_UPDATE
CHECK_UPDATE --> READ_1
CHECK_UPDATE --> READ_2
CHECK_UPDATE --> SPIN_WAIT
COMPARE --> CHECK_UPDATE
COMPARE --> RETURN
SPIN_WAIT --> CHECK_UPDATE
START --> CHECK_UPDATE

Sources: src/lib.rs(L106 - L129) 

This double-read verification pattern ensures that time values remain consistent even during CMOS hardware updates, which occur approximately once per second.

Hardware Interface Abstraction

The implementation uses conditional compilation to provide platform-specific hardware access while maintaining a consistent interface.

flowchart TD
subgraph subGraph2["Fallback Implementation"]
    READ_STUB["read_cmos_register() -> 0"]
    WRITE_STUB["write_cmos_register() no-op"]
end
subgraph subGraph1["x86/x86_64 Implementation"]
    PORT_DEF["Port definitions"]
    COMMAND_PORT_IMPL["COMMAND_PORT: Port"]
    DATA_PORT_IMPL["DATA_PORT: Port"]
    READ_IMPL["read_cmos_register()"]
    WRITE_IMPL["write_cmos_register()"]
end
subgraph subGraph0["Platform Detection"]
    CFG_IF["cfg_if! macro"]
    X86_CHECK["target_arch x86/x86_64"]
    OTHER_ARCH["other architectures"]
end

CFG_IF --> OTHER_ARCH
CFG_IF --> X86_CHECK
OTHER_ARCH --> READ_STUB
OTHER_ARCH --> WRITE_STUB
PORT_DEF --> COMMAND_PORT_IMPL
PORT_DEF --> DATA_PORT_IMPL
X86_CHECK --> PORT_DEF
X86_CHECK --> READ_IMPL
X86_CHECK --> WRITE_IMPL

Sources: src/lib.rs(L196 - L226) 

The conditional compilation ensures that the crate can be built on non-x86 platforms for testing purposes while providing no-op implementations for hardware functions.

Register Access Pattern

The CMOS register access follows a strict two-step protocol for all operations.

OperationStep 1: Command PortStep 2: Data Port
ReadWrite register address + NMI disableRead value
WriteWrite register address + NMI disableWrite value

The CMOS_DISABLE_NMI flag (bit 7) is always set to prevent Non-Maskable Interrupts during CMOS access, ensuring atomic operations.

Sources: src/lib.rs(L201 - L218) 

Data Conversion Pipeline

The implementation handles multiple data format conversions in a structured pipeline:

flowchart TD
subgraph subGraph0["Read Pipeline"]
    BCD_CONV["convert_bcd_value()"]
    subgraph subGraph1["Write Pipeline"]
        UNIX_TIME["Unix Timestamp"]
        DATE_CALC["Date Calculation"]
        FORMAT_CONV["Binary to BCD"]
        HOUR_FORMAT["Hour Format Handling"]
        CMOS_WRITE["CMOS Register Write"]
        RAW_READ["Raw CMOS Value"]
        FORMAT_CHECK["Binary vs BCD Check"]
        HOUR_LOGIC["12/24 Hour Conversion"]
        FINAL_VALUE["Final Binary Value"]
    end
end

BCD_CONV --> HOUR_LOGIC
DATE_CALC --> FORMAT_CONV
FORMAT_CHECK --> BCD_CONV
FORMAT_CHECK --> HOUR_LOGIC
FORMAT_CONV --> HOUR_FORMAT
HOUR_FORMAT --> CMOS_WRITE
HOUR_LOGIC --> FINAL_VALUE
RAW_READ --> FORMAT_CHECK
UNIX_TIME --> DATE_CALC

Sources: src/lib.rs(L38 - L48)  src/lib.rs(L171 - L185) 

Error Handling Strategy

The implementation prioritizes data consistency over error reporting, using several defensive programming techniques:

  • Spin-wait loops for hardware synchronization rather than timeouts
  • Double-read verification to detect inconsistent states
  • Format detection caching to avoid repeated hardware queries
  • Const functions where possible to enable compile-time optimization

The absence of explicit error types reflects the design philosophy that hardware RTC operations should either succeed or retry, as partial failures are generally not recoverable at the application level.

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

Calendar Arithmetic Implementation

The date conversion logic implements efficient calendar arithmetic optimized for the Unix epoch:

flowchart TD
subgraph subGraph0["Unix Timestamp to Date"]
    UNIX_IN["Unix Timestamp Input"]
    TIME_CALC["Time Calculation (t % 86400)"]
    MONTH_LOOP["Month Iteration Loop"]
    DAYS_IN_MONTH["days_in_month() Check"]
    subgraph subGraph1["Date to Unix Timestamp"]
        DATE_IN["Date/Time Input"]
        EPOCH_CALC["Days Since Epoch"]
        MKTIME["seconds_from_date()"]
        UNIX_OUT["Unix Timestamp Output"]
        DAYS_CALC["Day Calculation (t / 86400)"]
        YEAR_LOOP["Year Iteration Loop"]
        LEAP_CHECK["is_leap_year() Check"]
    end
end

DATE_IN --> EPOCH_CALC
DAYS_CALC --> YEAR_LOOP
EPOCH_CALC --> MKTIME
MKTIME --> UNIX_OUT
MONTH_LOOP --> DAYS_IN_MONTH
UNIX_IN --> DAYS_CALC
UNIX_IN --> TIME_CALC
YEAR_LOOP --> LEAP_CHECK
YEAR_LOOP --> MONTH_LOOP

Sources: src/lib.rs(L147 - L166)  src/lib.rs(L264 - L276)  src/lib.rs(L228 - L245) 

The implementation uses const functions for calendar utilities to enable compile-time optimization and follows algorithms similar to the Linux kernel's mktime64() function for compatibility and reliability.

RTC Driver API

Relevant source files

This document covers the public interface of the x86_rtc crate's RTC driver, including the Rtc struct and its methods for reading and setting system time via CMOS hardware. This page focuses on the high-level API design and usage patterns. For information about the underlying CMOS hardware protocol and register operations, see CMOS Hardware Interface. For details about data format conversions and timestamp calculations, see Data Format Handling.

API Structure Overview

The RTC driver provides a simple, safe interface to the x86_64 Real Time Clock through a single primary struct and three core methods.

Core API Components

flowchart TD
subgraph subGraph2["Hardware Interface"]
    CMOS_REGS["CMOS RegistersSTATUS_REGISTER_B"]
    UNIX_TIME["Unix Timestampu64 seconds"]
end
subgraph subGraph1["Internal State"]
    CMOS_FORMAT["cmos_format: u8(line 26)"]
end
subgraph subGraph0["Public API Surface"]
    RTC_STRUCT["Rtc struct(lines 24-27)"]
    NEW_METHOD["new() -> Self(lines 96-101)"]
    GET_METHOD["get_unix_timestamp() -> u64(lines 103-129)"]
    SET_METHOD["set_unix_timestamp(u64)(lines 131-194)"]
end

GET_METHOD --> UNIX_TIME
NEW_METHOD --> CMOS_REGS
RTC_STRUCT --> CMOS_FORMAT
RTC_STRUCT --> GET_METHOD
RTC_STRUCT --> NEW_METHOD
RTC_STRUCT --> SET_METHOD
SET_METHOD --> UNIX_TIME

Sources: src/lib.rs(L24 - L194) 

Rtc Struct

The Rtc struct serves as the main entry point for all RTC operations. It maintains internal state about the CMOS hardware configuration.

FieldTypePurpose
cmos_formatu8Cached CMOS Status Register B value containing format flags

The struct stores hardware format information to avoid repeatedly querying CMOS registers during time operations. This includes whether the hardware uses 24-hour format and binary vs BCD encoding.

Sources: src/lib.rs(L24 - L27) 

Constructor Method

Rtc::new() -> Self

Creates a new Rtc instance by reading the current CMOS hardware configuration.

flowchart TD
CALL["Rtc::new()"]
READ_REG["read_cmos_register(CMOS_STATUS_REGISTER_B)"]
STORE_FORMAT["Store format flags in cmos_format field"]
RETURN_INSTANCE["Return Rtc instance"]

CALL --> READ_REG
READ_REG --> STORE_FORMAT
STORE_FORMAT --> RETURN_INSTANCE

The constructor performs a single CMOS register read to determine hardware format configuration, which is then cached for the lifetime of the Rtc instance. This eliminates the need to re-read format flags on every time operation.

Sources: src/lib.rs(L96 - L101) 

Time Reading Method

get_unix_timestamp(&self) -> u64

Returns the current time as seconds since Unix epoch (January 1, 1970 00:00:00 UTC).

flowchart TD
START["get_unix_timestamp() called"]
WAIT_LOOP["Wait for update completion(CMOS_STATUS_REGISTER_A check)"]
READ_TIME1["Read all CMOS time registers(read_all_values)"]
CHECK_UPDATE["Check if update started(CMOS_UPDATE_IN_PROGRESS_FLAG)"]
READ_TIME2["Read time again for verification"]
COMPARE["Compare readings"]
RETURN["Return consistent timestamp"]
RETRY["Continue loop"]

CHECK_UPDATE --> READ_TIME2
CHECK_UPDATE --> RETRY
COMPARE --> RETRY
COMPARE --> RETURN
READ_TIME1 --> CHECK_UPDATE
READ_TIME2 --> COMPARE
RETRY --> WAIT_LOOP
START --> WAIT_LOOP
WAIT_LOOP --> READ_TIME1

The method implements a sophisticated synchronization protocol to ensure consistent readings despite hardware timing constraints. It uses a double-read verification pattern to detect and handle concurrent CMOS updates.

Implementation Note: The method requires interrupts to be disabled by the caller to ensure accurate timing, as documented in the method's comment at src/lib.rs(L105) 

Sources: src/lib.rs(L103 - L129) 

Time Setting Method

set_unix_timestamp(&self, unix_time: u64)

Sets the RTC to the specified Unix timestamp.

flowchart TD
INPUT["Unix timestamp (u64)"]
CONVERT_SECS["Convert to u32 seconds"]
CALC_COMPONENTS["Calculate time components(hour, minute, second)"]
CALC_DATE["Calculate date components(year, month, day)"]
FORMAT_CHECK["Binary format?(is_binary_format)"]
CONVERT_BCD["Convert values to BCD format"]
HANDLE_HOUR["Special hour format handling(12/24 hour conversion)"]
WRITE_REGS["Write to CMOS registers(SECOND, MINUTE, HOUR, DAY, MONTH, YEAR)"]

CALC_COMPONENTS --> CALC_DATE
CALC_DATE --> FORMAT_CHECK
CONVERT_BCD --> HANDLE_HOUR
CONVERT_SECS --> CALC_COMPONENTS
FORMAT_CHECK --> CONVERT_BCD
FORMAT_CHECK --> HANDLE_HOUR
HANDLE_HOUR --> WRITE_REGS
INPUT --> CONVERT_SECS

The method performs comprehensive date/time calculation including leap year handling and month-specific day counts. It respects the hardware's configured data format, converting between binary and BCD as needed.

Sources: src/lib.rs(L131 - L194) 

API Usage Patterns

Basic Time Reading

use x86_rtc::Rtc;
let rtc = Rtc::new();
let current_time = rtc.get_unix_timestamp();

Time Setting

use x86_rtc::Rtc;
let rtc = Rtc::new();
rtc.set_unix_timestamp(1640995200); // Set to specific Unix timestamp

Thread Safety Considerations

The API is designed for single-threaded use in kernel or embedded contexts. Multiple concurrent accesses to the same Rtc instance require external synchronization, particularly for the get_unix_timestamp() method which requires interrupt-free execution.

Sources: README.md(L9 - L12)  src/lib.rs(L105) 

Error Handling

The API uses a panic-free design with no explicit error return types. Invalid operations or hardware communication failures result in:

  • Reading operations return potentially incorrect but valid u64 values
  • Writing operations complete without indication of success/failure
  • Hardware communication errors are handled at the lower CMOS interface level

This design reflects the embedded/kernel context where panic-based error handling is preferred over Result types.

Sources: src/lib.rs(L103 - L194) 

CMOS Hardware Interface

Relevant source files

This document explains the low-level hardware interface for accessing the Real Time Clock (RTC) through CMOS registers on x86_64 systems. It covers the I/O port protocol, register mapping, hardware synchronization, and platform-specific implementation details.

For high-level RTC API usage, see RTC Driver API. For data format conversion details, see Data Format Handling.

CMOS Register Map

The CMOS RTC uses a well-defined register layout accessible through I/O ports. The implementation defines specific register addresses and control flags for accessing time, date, and status information.

Time and Date Registers

flowchart TD
subgraph subGraph3["Format Flags"]
    UIP["CMOS_UPDATE_IN_PROGRESS_FLAGBit 7"]
    H24["CMOS_24_HOUR_FORMAT_FLAGBit 1"]
    BIN["CMOS_BINARY_FORMAT_FLAGBit 2"]
    PM["CMOS_12_HOUR_PM_FLAG0x80"]
end
subgraph subGraph2["Status Registers"]
    DAY["CMOS_DAY_REGISTER0x07"]
    MONTH["CMOS_MONTH_REGISTER0x08"]
    YEAR["CMOS_YEAR_REGISTER0x09"]
    STATA["CMOS_STATUS_REGISTER_A0x0AUpdate Progress"]
    STATB["CMOS_STATUS_REGISTER_B0x0BFormat Control"]
end
subgraph subGraph0["Time Registers"]
    SEC["CMOS_SECOND_REGISTER0x00"]
    MIN["CMOS_MINUTE_REGISTER0x02"]
    HOUR["CMOS_HOUR_REGISTER0x04"]
end
subgraph subGraph1["Date Registers"]
    DAY["CMOS_DAY_REGISTER0x07"]
    MONTH["CMOS_MONTH_REGISTER0x08"]
    YEAR["CMOS_YEAR_REGISTER0x09"]
    STATA["CMOS_STATUS_REGISTER_A0x0AUpdate Progress"]
end

HOUR --> PM
STATA --> UIP
STATB --> BIN
STATB --> H24

Sources: src/lib.rs(L10 - L23) 

Register Access Pattern

RegisterAddressPurposeFormat Dependencies
CMOS_SECOND_REGISTER0x00Current second (0-59)BCD/Binary
CMOS_MINUTE_REGISTER0x02Current minute (0-59)BCD/Binary
CMOS_HOUR_REGISTER0x04Current hourBCD/Binary + 12/24-hour
CMOS_DAY_REGISTER0x07Day of month (1-31)BCD/Binary
CMOS_MONTH_REGISTER0x08Month (1-12)BCD/Binary
CMOS_YEAR_REGISTER0x09Year (0-99, + 2000)BCD/Binary
CMOS_STATUS_REGISTER_A0x0AUpdate statusRaw binary
CMOS_STATUS_REGISTER_B0x0BFormat configurationRaw binary

I/O Port Protocol

The CMOS interface uses a two-port protocol where register selection and data transfer are performed through separate I/O ports.

Port Configuration

flowchart TD
subgraph subGraph2["Access Functions"]
    READ["read_cmos_register()"]
    WRITE["write_cmos_register()"]
end
subgraph subGraph1["Port Operations"]
    CMDPORT["Port<u8>COMMAND_PORT"]
    DATAPORT["Port<u8>DATA_PORT"]
end
subgraph subGraph0["I/O Port Interface"]
    CMD["CMOS_COMMAND_PORT0x70Register Selection"]
    DATA["CMOS_DATA_PORT0x71Data Transfer"]
end

CMD --> CMDPORT
CMDPORT --> READ
CMDPORT --> WRITE
DATA --> DATAPORT
DATAPORT --> READ
DATAPORT --> WRITE

Sources: src/lib.rs(L198 - L204) 

Hardware Communication Protocol

The CMOS access protocol follows a strict sequence to ensure reliable register access:

sequenceDiagram
    participant CPU as CPU
    participant COMMAND_PORT0x70 as "COMMAND_PORT (0x70)"
    participant DATA_PORT0x71 as "DATA_PORT (0x71)"
    participant CMOSChip as "CMOS Chip"

    Note over CPU: Read Operation
    CPU ->> COMMAND_PORT0x70: "CMOS_DISABLE_NMI | register"
    COMMAND_PORT0x70 ->> CMOSChip: "Select Register"
    CPU ->> DATA_PORT0x71: "read()"
    DATA_PORT0x71 ->> CMOSChip: "Read Request"
    CMOSChip ->> DATA_PORT0x71: "Register Value"
    DATA_PORT0x71 ->> CPU: "Return Value"
    Note over CPU: Write Operation
    CPU ->> COMMAND_PORT0x70: "CMOS_DISABLE_NMI | register"
    COMMAND_PORT0x70 ->> CMOSChip: "Select Register"
    CPU ->> DATA_PORT0x71: "write(value)"
    DATA_PORT0x71 ->> CMOSChip: "Write Value"

Sources: src/lib.rs(L206 - L218) 

Hardware Access Implementation

Register Read Operation

The read_cmos_register function implements the low-level hardware access protocol:

flowchart TD
subgraph subGraph1["Hardware Control"]
    NMI["CMOS_DISABLE_NMIBit 7 = 1Prevents interrupts"]
    REGSEL["Register SelectionBits 0-6"]
end
subgraph subGraph0["read_cmos_register Function"]
    START["Start: register parameter"]
    SELECTREG["Write to COMMAND_PORTCMOS_DISABLE_NMI | register"]
    READDATA["Read from DATA_PORT"]
    RETURN["Return u8 value"]
end

READDATA --> RETURN
SELECTREG --> NMI
SELECTREG --> READDATA
SELECTREG --> REGSEL
START --> SELECTREG

Sources: src/lib.rs(L206 - L211) 

Register Write Operation

The write_cmos_register function handles data updates to CMOS registers:

flowchart TD
subgraph subGraph1["Safety Considerations"]
    UNSAFE["unsafe blockRaw port access"]
    RAWPTR["raw mut pointerStatic port references"]
end
subgraph subGraph0["write_cmos_register Function"]
    START["Start: register, value parameters"]
    SELECTREG["Write to COMMAND_PORTCMOS_DISABLE_NMI | register"]
    WRITEDATA["Write value to DATA_PORT"]
    END["End"]
end

SELECTREG --> UNSAFE
SELECTREG --> WRITEDATA
START --> SELECTREG
WRITEDATA --> END
WRITEDATA --> RAWPTR

Sources: src/lib.rs(L213 - L218) 

Status Register Management

Update Synchronization

The CMOS chip updates its registers autonomously, requiring careful synchronization to avoid reading inconsistent values:

flowchart TD
subgraph subGraph0["Update Detection Flow"]
    CHECK1["Read CMOS_STATUS_REGISTER_A"]
    TESTFLAG1["Test CMOS_UPDATE_IN_PROGRESS_FLAG"]
    WAIT["spin_loop() while updating"]
    READ1["Read all time registers"]
    CHECK2["Read CMOS_STATUS_REGISTER_A again"]
    TESTFLAG2["Test update flag again"]
    READ2["Read all time registers again"]
    COMPARE["Compare both readings"]
    RETURN["Return consistent value"]
end
read2["read2"]

CHECK1 --> TESTFLAG1
CHECK2 --> TESTFLAG2
COMPARE --> CHECK1
COMPARE --> RETURN
READ1 --> CHECK2
TESTFLAG1 --> READ1
TESTFLAG1 --> WAIT
TESTFLAG2 --> CHECK1
TESTFLAG2 --> READ2
WAIT --> CHECK1
read2 --> COMPARE

Sources: src/lib.rs(L107 - L129) 

Format Detection

The implementation reads CMOS_STATUS_REGISTER_B during initialization to determine data format:

FlagBit PositionPurposeImpact
CMOS_24_HOUR_FORMAT_FLAG1Hour format detectionAffects hour register interpretation
CMOS_BINARY_FORMAT_FLAG2Number format detectionDetermines BCD vs binary conversion

Sources: src/lib.rs(L30 - L36) 

Platform Abstraction

Conditional Compilation

The implementation uses conditional compilation to provide platform-specific functionality:

flowchart TD
subgraph subGraph2["Fallback Implementation"]
    STUBREAD["Stub read_cmos_register()Returns 0"]
    STUBWRITE["Stub write_cmos_register()No operation"]
end
subgraph subGraph1["x86/x86_64 Implementation"]
    REALPORTS["Real I/O Port Accessx86_64::instructions::port::Port"]
    REALREAD["Actual CMOS register reads"]
    REALWRITE["Actual CMOS register writes"]
end
subgraph subGraph0["Compilation Targets"]
    X86["x86/x86_64target_arch"]
    OTHER["Other Architectures"]
end

OTHER --> STUBREAD
OTHER --> STUBWRITE
REALPORTS --> REALREAD
REALPORTS --> REALWRITE
X86 --> REALPORTS

Sources: src/lib.rs(L196 - L226) 

Safety Considerations

The hardware access requires unsafe code due to direct I/O port manipulation:

  • Raw pointer access: Static Port<u8> instances require raw mutable references
  • Interrupt safety: CMOS_DISABLE_NMI flag prevents hardware interrupts during access
  • Atomic operations: The two-port protocol ensures atomic register selection and data transfer

Sources: src/lib.rs(L207 - L217) 

Data Format Handling

Relevant source files

This document covers the data format conversion and handling mechanisms within the x86_rtc crate. The RTC driver must handle multiple data representation formats used by CMOS hardware, including BCD/binary encoding, 12/24-hour time formats, and Unix timestamp conversions. This section focuses specifically on the format detection, conversion algorithms, and data consistency mechanisms.

For hardware register access details, see CMOS Hardware Interface. For the high-level API usage, see RTC Driver API.

Format Detection and Configuration

The RTC hardware can store time values in different formats, and the driver must detect and handle these variations dynamically. The format configuration is stored in the CMOS Status Register B and cached in the Rtc struct.

flowchart TD
INIT["Rtc::new()"]
READ_STATUS["read_cmos_register(CMOS_STATUS_REGISTER_B)"]
CACHE["cmos_format: u8"]
CHECK_24H["is_24_hour_format()"]
CHECK_BIN["is_binary_format()"]
FLAG_24H["CMOS_24_HOUR_FORMAT_FLAG"]
FLAG_BIN["CMOS_BINARY_FORMAT_FLAG"]
DECISION_TIME["Time Format Decision"]
DECISION_DATA["Data Format Decision"]
FORMAT_12H["12-Hour Format"]
FORMAT_24H["24-Hour Format"]
FORMAT_BCD["BCD Format"]
FORMAT_BINARY["Binary Format"]

CACHE --> CHECK_24H
CACHE --> CHECK_BIN
CHECK_24H --> FLAG_24H
CHECK_BIN --> FLAG_BIN
DECISION_DATA --> FORMAT_BCD
DECISION_DATA --> FORMAT_BINARY
DECISION_TIME --> FORMAT_12H
DECISION_TIME --> FORMAT_24H
FLAG_24H --> DECISION_TIME
FLAG_BIN --> DECISION_DATA
INIT --> READ_STATUS
READ_STATUS --> CACHE

The format detection methods use bitwise operations to check specific flags:

MethodFlag ConstantBit PositionPurpose
is_24_hour_format()CMOS_24_HOUR_FORMAT_FLAGBit 1Determines 12/24 hour format
is_binary_format()CMOS_BINARY_FORMAT_FLAGBit 2Determines BCD/binary encoding

Sources: src/lib.rs(L30 - L36)  src/lib.rs(L20 - L21)  src/lib.rs(L97 - L101) 

BCD and Binary Format Conversion

The CMOS chip can store datetime values in either Binary-Coded Decimal (BCD) or pure binary format. The driver provides bidirectional conversion functions to handle both representations.

flowchart TD
subgraph subGraph0["BCD to Binary Conversion"]
    subgraph subGraph1["Binary to BCD Conversion"]
        BIN_INPUT["Binary Value (23)"]
        BIN_FUNC["convert_binary_value()"]
        BIN_TENS["Tens: binary / 10"]
        BIN_ONES["Ones: binary % 10"]
        BIN_CALC["((binary / 10) << 4) | (binary % 10)"]
        BIN_OUTPUT["BCD Value (0x23)"]
        BCD_INPUT["BCD Value (0x23)"]
        BCD_FUNC["convert_bcd_value()"]
        BCD_EXTRACT["Extract Tens: (0xF0) >> 4"]
        BCD_ONES["Extract Ones: (0x0F)"]
        BCD_CALC["((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3) + (bcd & 0x0f)"]
        BCD_OUTPUT["Binary Value (23)"]
    end
end

BCD_CALC --> BCD_OUTPUT
BCD_EXTRACT --> BCD_CALC
BCD_FUNC --> BCD_EXTRACT
BCD_FUNC --> BCD_ONES
BCD_INPUT --> BCD_FUNC
BCD_ONES --> BCD_CALC
BIN_CALC --> BIN_OUTPUT
BIN_FUNC --> BIN_ONES
BIN_FUNC --> BIN_TENS
BIN_INPUT --> BIN_FUNC
BIN_ONES --> BIN_CALC
BIN_TENS --> BIN_CALC

The read_datetime_register() method automatically applies the appropriate conversion based on the detected format:

flowchart TD
READ_REG["read_datetime_register(register)"]
READ_CMOS["read_cmos_register(register)"]
CHECK_FORMAT["is_binary_format()"]
RETURN_DIRECT["Return value directly"]
CONVERT_BCD["convert_bcd_value(value)"]
OUTPUT["Final Value"]

CHECK_FORMAT --> CONVERT_BCD
CHECK_FORMAT --> RETURN_DIRECT
CONVERT_BCD --> OUTPUT
READ_CMOS --> CHECK_FORMAT
READ_REG --> READ_CMOS
RETURN_DIRECT --> OUTPUT

Sources: src/lib.rs(L38 - L48)  src/lib.rs(L253 - L260)  src/lib.rs(L171 - L177) 

Hour Format Handling

Hour values require the most complex format handling due to the combination of BCD/binary encoding with 12/24-hour format variations and PM flag management.

flowchart TD
READ_HOUR["read_cmos_register(CMOS_HOUR_REGISTER)"]
CHECK_12H["is_24_hour_format()"]
EXTRACT_PM["time_is_pm(hour)"]
SKIP_PM["Skip PM handling"]
MASK_PM["hour &= !CMOS_12_HOUR_PM_FLAG"]
CHECK_BCD_12H["is_binary_format()"]
CHECK_BCD_24H["is_binary_format()"]
CONVERT_BCD_12H["convert_bcd_value(hour)"]
HOUR_READY_12H["hour ready for 12h conversion"]
CONVERT_BCD_24H["convert_bcd_value(hour)"]
HOUR_FINAL["Final 24h hour"]
CONVERT_12_TO_24["Convert 12h to 24h"]
CHECK_NOON["hour == 12"]
SET_ZERO["hour = 0"]
KEEP_HOUR["Keep hour value"]
CHECK_PM_FINAL["is_pm"]
ADD_12["hour += 12"]

CHECK_12H --> EXTRACT_PM
CHECK_12H --> SKIP_PM
CHECK_BCD_12H --> CONVERT_BCD_12H
CHECK_BCD_12H --> HOUR_READY_12H
CHECK_BCD_24H --> CONVERT_BCD_24H
CHECK_BCD_24H --> HOUR_FINAL
CHECK_NOON --> KEEP_HOUR
CHECK_NOON --> SET_ZERO
CHECK_PM_FINAL --> ADD_12
CHECK_PM_FINAL --> HOUR_FINAL
CONVERT_BCD_12H --> CONVERT_12_TO_24
CONVERT_BCD_24H --> HOUR_FINAL
EXTRACT_PM --> MASK_PM
HOUR_READY_12H --> CONVERT_12_TO_24
KEEP_HOUR --> CHECK_PM_FINAL
MASK_PM --> CHECK_BCD_12H
READ_HOUR --> CHECK_12H
SET_ZERO --> CHECK_PM_FINAL
SKIP_PM --> CHECK_BCD_24H

The conversion logic handles these specific cases:

Input (12h)is_pmConverted (24h)Logic
12:xx AMfalse00:xxhour = 0
01:xx AMfalse01:xxhour unchanged
12:xx PMtrue12:xxhour = 0 + 12 = 12
01:xx PMtrue13:xxhour = 1 + 12 = 13

Sources: src/lib.rs(L56 - L84)  src/lib.rs(L247 - L249)  src/lib.rs(L22) 

Unix Timestamp Conversion

The driver provides bidirectional conversion between CMOS datetime values and Unix timestamps (seconds since January 1, 1970).

Reading: CMOS to Unix Timestamp

flowchart TD
GET_TIMESTAMP["get_unix_timestamp()"]
WAIT_STABLE["Wait for !UPDATE_IN_PROGRESS"]
READ_ALL["read_all_values()"]
READ_YEAR["read_datetime_register(CMOS_YEAR_REGISTER) + 2000"]
READ_MONTH["read_datetime_register(CMOS_MONTH_REGISTER)"]
READ_DAY["read_datetime_register(CMOS_DAY_REGISTER)"]
PROCESS_HOUR["Complex hour processing"]
READ_MINUTE["read_datetime_register(CMOS_MINUTE_REGISTER)"]
READ_SECOND["read_datetime_register(CMOS_SECOND_REGISTER)"]
CALC_UNIX["seconds_from_date()"]
CHECK_CONSISTENT["Verify consistency"]
RETURN_TIMESTAMP["Return Unix timestamp"]

CALC_UNIX --> CHECK_CONSISTENT
CHECK_CONSISTENT --> RETURN_TIMESTAMP
CHECK_CONSISTENT --> WAIT_STABLE
GET_TIMESTAMP --> WAIT_STABLE
PROCESS_HOUR --> CALC_UNIX
READ_ALL --> PROCESS_HOUR
READ_ALL --> READ_DAY
READ_ALL --> READ_MINUTE
READ_ALL --> READ_MONTH
READ_ALL --> READ_SECOND
READ_ALL --> READ_YEAR
READ_DAY --> CALC_UNIX
READ_MINUTE --> CALC_UNIX
READ_MONTH --> CALC_UNIX
READ_SECOND --> CALC_UNIX
READ_YEAR --> CALC_UNIX
WAIT_STABLE --> READ_ALL

Writing: Unix Timestamp to CMOS

flowchart TD
SET_TIMESTAMP["set_unix_timestamp(unix_time)"]
CALC_TIME["Calculate hour, minute, second"]
CALC_DATE["Calculate year, month, day"]
EXTRACT_HOUR["hour = t / 3600"]
EXTRACT_MIN["minute = (t % 3600) / 60"]
EXTRACT_SEC["second = t % 60"]
YEAR_LOOP["Subtract full years since 1970"]
MONTH_LOOP["Subtract full months"]
CALC_DAY["Remaining days + 1"]
CHECK_BIN_FORMAT["is_binary_format()"]
CONVERT_TO_BCD["convert_binary_value() for each"]
WRITE_REGISTERS["Write to CMOS registers"]
WRITE_SEC["write_cmos_register(CMOS_SECOND_REGISTER)"]
WRITE_MIN["write_cmos_register(CMOS_MINUTE_REGISTER)"]
WRITE_HOUR["write_cmos_register(CMOS_HOUR_REGISTER)"]
WRITE_DAY["write_cmos_register(CMOS_DAY_REGISTER)"]
WRITE_MONTH["write_cmos_register(CMOS_MONTH_REGISTER)"]
WRITE_YEAR["write_cmos_register(CMOS_YEAR_REGISTER)"]

CALC_DATE --> YEAR_LOOP
CALC_DAY --> CHECK_BIN_FORMAT
CALC_TIME --> EXTRACT_HOUR
CALC_TIME --> EXTRACT_MIN
CALC_TIME --> EXTRACT_SEC
CHECK_BIN_FORMAT --> CONVERT_TO_BCD
CHECK_BIN_FORMAT --> WRITE_REGISTERS
CONVERT_TO_BCD --> WRITE_REGISTERS
EXTRACT_HOUR --> CHECK_BIN_FORMAT
EXTRACT_MIN --> CHECK_BIN_FORMAT
EXTRACT_SEC --> CHECK_BIN_FORMAT
MONTH_LOOP --> CALC_DAY
SET_TIMESTAMP --> CALC_DATE
SET_TIMESTAMP --> CALC_TIME
WRITE_REGISTERS --> WRITE_DAY
WRITE_REGISTERS --> WRITE_HOUR
WRITE_REGISTERS --> WRITE_MIN
WRITE_REGISTERS --> WRITE_MONTH
WRITE_REGISTERS --> WRITE_SEC
WRITE_REGISTERS --> WRITE_YEAR
YEAR_LOOP --> MONTH_LOOP

Sources: src/lib.rs(L106 - L129)  src/lib.rs(L132 - L193)  src/lib.rs(L264 - L276) 

Data Consistency and Synchronization

The CMOS hardware updates its registers periodically, which can cause inconsistent reads if accessed during an update cycle. The driver implements a consistency check mechanism.

flowchart TD
START_READ["Start get_unix_timestamp()"]
CHECK_UPDATE1["Read CMOS_STATUS_REGISTER_A"]
UPDATE_FLAG1["Check CMOS_UPDATE_IN_PROGRESS_FLAG"]
SPIN_WAIT["core::hint::spin_loop()"]
READ_VALUES1["Read all datetime values"]
CHECK_UPDATE2["Check update flag again"]
READ_VALUES2["Read all datetime values again"]
COMPARE["Compare both readings"]
RETURN_CONSISTENT["Return consistent value"]

CHECK_UPDATE1 --> UPDATE_FLAG1
CHECK_UPDATE2 --> READ_VALUES2
CHECK_UPDATE2 --> START_READ
COMPARE --> RETURN_CONSISTENT
COMPARE --> START_READ
READ_VALUES1 --> CHECK_UPDATE2
READ_VALUES2 --> COMPARE
SPIN_WAIT --> CHECK_UPDATE1
START_READ --> CHECK_UPDATE1
UPDATE_FLAG1 --> READ_VALUES1
UPDATE_FLAG1 --> SPIN_WAIT

This double-read mechanism ensures that:

  1. No update is in progress before reading
  2. No update occurred during reading
  3. Two consecutive reads produce identical results

Sources: src/lib.rs(L107 - L129)  src/lib.rs(L19) 

Calendar Arithmetic Functions

The driver includes helper functions for calendar calculations needed during timestamp conversion:

FunctionPurposeKey Logic
is_leap_year()Leap year detection(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
days_in_month()Days per monthHandles leap year February (28/29 days)
seconds_from_date()Date to Unix timestampUses optimized algorithm from Linux kernel

The seconds_from_date() function uses an optimized algorithm that adjusts the year and month to simplify leap year calculations by treating March as the start of the year.

Sources: src/lib.rs(L228 - L245)  src/lib.rs(L264 - L276) 

Dependencies and Platform Support

Relevant source files

This document provides a comprehensive analysis of the x86_rtc crate's dependencies, platform requirements, and architecture-specific constraints. It covers both direct and transitive dependencies, explains the conditional compilation strategy, and documents the specific platform support matrix.

For detailed crate configuration and metadata information, see Crate Definition and Metadata. For implementation-specific dependency usage, see Implementation.

Dependency Architecture Overview

The x86_rtc crate follows a minimal dependency strategy, with only two direct dependencies and a small transitive dependency tree focused on hardware abstraction and conditional compilation.

flowchart TD
subgraph subGraph3["Conditional Compilation"]
    ARCH_GATE["target_arch = x86_64"]
end
subgraph subGraph2["Transitive Dependencies"]
    BIT_FIELD["bit_field v0.10.2"]
    BITFLAGS["bitflags v2.6.0"]
    RUSTVERSION["rustversion v1.0.19"]
    VOLATILE["volatile v0.4.6"]
end
subgraph subGraph1["Direct Dependencies"]
    CFG_IF["cfg-if v1.0"]
    X86_64["x86_64 v0.15"]
end
subgraph subGraph0["x86_rtc Crate"]
    ROOT["x86_rtc v0.1.1"]
end

ARCH_GATE --> X86_64
ROOT --> ARCH_GATE
ROOT --> CFG_IF

Sources: Cargo.toml(L14 - L18)  Cargo.lock(L48 - L53) 

Direct Dependencies

cfg-if v1.0

The cfg-if crate provides ergonomic conditional compilation macros, essential for platform-specific code paths in the RTC driver.

AttributeValue
Version1.0.0
PurposeConditional compilation utilities
UsagePlatform-specific code branching
License CompatibilityMIT/Apache-2.0

Sources: Cargo.toml(L15)  Cargo.lock(L18 - L21) 

x86_64 v0.15 (Conditional)

The x86_64 crate is the primary hardware abstraction layer, providing safe access to x86_64-specific instructions and memory-mapped I/O operations required for CMOS interaction.

AttributeValue
Version0.15.2
Conditiontarget_arch = "x86_64"
PurposeHardware abstraction for x86_64 architecture
Key FeaturesI/O port access, memory safety primitives

Sources: Cargo.toml(L17 - L18)  Cargo.lock(L36 - L45) 

Transitive Dependencies

The x86_64 crate brings in four transitive dependencies that provide low-level hardware access capabilities:

Hardware Access Stack

flowchart TD
subgraph subGraph4["CMOS Hardware Interface"]
    PORT_ACCESS["I/O Port Operations"]
    REGISTER_FIELDS["CMOS Register Fields"]
    MEMORY_SAFETY["Volatile Memory Access"]
end
subgraph subGraph3["Hardware Abstraction Layers"]
    X86_CRATE["x86_64 crate"]
    subgraph subGraph2["Build-time Utilities"]
        RUSTVERSION_DEP["rustversion v1.0.19"]
    end
    subgraph subGraph1["Memory Safety"]
        VOLATILE_DEP["volatile v0.4.6"]
    end
    subgraph subGraph0["Register Management"]
        BIT_FIELD_DEP["bit_field v0.10.2"]
        BITFLAGS_DEP["bitflags v2.6.0"]
    end
end

BITFLAGS_DEP --> REGISTER_FIELDS
BIT_FIELD_DEP --> REGISTER_FIELDS
VOLATILE_DEP --> MEMORY_SAFETY
VOLATILE_DEP --> PORT_ACCESS
X86_CRATE --> BITFLAGS_DEP
X86_CRATE --> BIT_FIELD_DEP
X86_CRATE --> RUSTVERSION_DEP
X86_CRATE --> VOLATILE_DEP

Sources: Cargo.lock(L40 - L44) 

Dependency Details

CrateVersionPurposeRole in RTC
bit_field0.10.2Bit field manipulationCMOS register field extraction
bitflags2.6.0Type-safe bit flag operationsStatus register flags
volatile0.4.6Volatile memory accessHardware register safety
rustversion1.0.19Rust version detectionConditional API compilation

Sources: Cargo.lock(L6 - L45) 

Platform Requirements

Architecture Specificity

The x86_rtc crate is exclusively designed for x86_64 architecture due to its dependence on CMOS hardware implementation specific to x86-compatible systems.

RequirementSpecification
Target Architecturex86_64only
Hardware InterfaceCMOS via I/O ports 0x70/0x71
Memory ModelPhysical memory access required
Privilege LevelKernel/supervisor mode for I/O operations

Sources: Cargo.toml(L17)  Cargo.toml(L11) 

no_std Compatibility

The crate is designed for no_std environments, making it suitable for:

  • Operating system kernels
  • Embedded systems
  • Bootloaders
  • Hypervisors

Sources: Cargo.toml(L12) 

Conditional Compilation Strategy

The crate employs a sophisticated conditional compilation strategy to ensure platform safety and optimal builds.

flowchart TD
subgraph subGraph2["Compilation Decision Tree"]
    START["Build Process"]
    ARCH_CHECK["target_arch == x86_64?"]
    CFG_IF_USAGE["cfg-if macro usage"]
    subgraph subGraph1["Non-x86_64 Path"]
        EXCLUDE_X86["Exclude x86_64 crate"]
        COMPILE_ERROR["Compilation fails"]
        NO_IMPL["No RTC implementation"]
    end
    subgraph subGraph0["x86_64 Path"]
        INCLUDE_X86["Include x86_64 crate"]
        HARDWARE_ACCESS["Enable hardware access"]
        CMOS_INTERFACE["Compile CMOS interface"]
    end
end

ARCH_CHECK --> EXCLUDE_X86
ARCH_CHECK --> INCLUDE_X86
COMPILE_ERROR --> NO_IMPL
EXCLUDE_X86 --> COMPILE_ERROR
HARDWARE_ACCESS --> CMOS_INTERFACE
INCLUDE_X86 --> HARDWARE_ACCESS
START --> ARCH_CHECK
START --> CFG_IF_USAGE

Sources: Cargo.toml(L17 - L18) 

Conditional Dependency Declaration

The conditional dependency syntax in Cargo.toml ensures that the x86_64 crate and its transitive dependencies are only included when building for compatible targets:

[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = "0.15"

Sources: Cargo.toml(L17 - L18) 

Supported Build Targets

Primary Targets

Target TripleEnvironmentSupport Level
x86_64-unknown-linux-gnuStandard LinuxFull support
x86_64-unknown-noneBare metal/kernelFull support

Unsupported Architectures

The following architectures are explicitly unsupported due to hardware constraints:

  • ARM64 (aarch64)
  • ARM32 (arm)
  • RISC-V (riscv64)
  • WebAssembly (wasm32)

Sources: Cargo.toml(L11)  Cargo.toml(L17) 

Version Constraints and Compatibility

The crate maintains conservative version constraints to ensure stability:

Direct Dependency Versions

DependencyConstraintRationale
cfg-if1.0Stable API, wide compatibility
x86_640.15Latest stable hardware abstraction

Transitive Version Resolution

The locked dependency versions provide a stable foundation:

  • All transitive dependencies use semantic versioning
  • Version locks prevent unexpected breaking changes
  • Regular updates maintain security and compatibility

Sources: Cargo.toml(L15)  Cargo.toml(L18)  Cargo.lock(L1 - L54) 

Dependency Analysis

Relevant source files

This document provides a comprehensive analysis of the x86_rtc crate's dependency structure, including direct dependencies, transitive dependencies, version constraints, and conditional compilation requirements. This analysis covers both the explicit dependencies declared in the build configuration and the full resolved dependency tree.

For information about platform-specific compilation targets and architecture requirements, see Platform and Architecture Requirements. For details about the crate's overall configuration and metadata, see Crate Definition and Metadata.

Direct Dependencies

The x86_rtc crate declares two direct dependencies in its Cargo.toml configuration:

DependencyVersionScopePurpose
cfg-if1.0UnconditionalConditional compilation utilities
x86_640.15x86_64 architecture onlyHardware abstraction layer

cfg-if Dependency

The cfg-if crate provides conditional compilation macros that simplify platform-specific code organization. This dependency is declared unconditionally in Cargo.toml(L15)  and resolves to version 1.0.0 in the lock file.

x86_64 Architecture Dependency

The x86_64 crate is conditionally included only when building for x86_64 architecture targets, as specified in Cargo.toml(L17 - L18)  This dependency provides low-level hardware access primitives essential for CMOS register manipulation.

Dependency Graph - Direct Dependencies


Sources: Cargo.toml(L14 - L18)  Cargo.lock(L48 - L53) 

Transitive Dependencies

The x86_64 crate introduces four transitive dependencies that provide essential low-level functionality:

DependencyVersionSourcePurpose
bit_field0.10.2x86_64Bit field manipulation for registers
bitflags2.6.0x86_64Type-safe bit flag operations
rustversion1.0.19x86_64Rust compiler version detection
volatile0.4.6x86_64Memory-mapped I/O safety

Hardware Abstraction Dependencies

The transitive dependencies fall into two categories:

Register Manipulation: bit_field and bitflags provide safe abstractions for manipulating hardware register bits and flags, essential for CMOS register operations.

Memory Safety: volatile ensures proper memory-mapped I/O semantics, preventing compiler optimizations that could break hardware communication protocols.

Build-time Utilities: rustversion enables conditional compilation based on Rust compiler versions, ensuring compatibility across different toolchain versions.

Complete Dependency Tree

flowchart TD
subgraph subGraph1["Transitive Dependencies"]
    bit_field["bit_field v0.10.2"]
    bitflags["bitflags v2.6.0"]
    rustversion["rustversion v1.0.19"]
    volatile["volatile v0.4.6"]
end
subgraph subGraph0["Direct Dependencies"]
    cfg_if["cfg-if v1.0.0"]
    x86_64_crate["x86_64 v0.15.2"]
end
x86_rtc["x86_rtc v0.1.1"]

x86_64_crate --> bit_field
x86_64_crate --> bitflags
x86_64_crate --> rustversion
x86_64_crate --> volatile
x86_rtc --> cfg_if
x86_rtc --> x86_64_crate

Sources: Cargo.lock(L40 - L45)  Cargo.lock(L5 - L53) 

Version Constraints and Compatibility

Semantic Versioning Strategy

The crate uses semantic versioning with specific constraint patterns:

  • Major version pinning: cfg-if = "1.0" allows patch updates within the 1.x series
  • Minor version pinning: x86_64 = "0.15" allows patch updates within the 0.15.x series

Resolved Version Analysis

The dependency resolution in Cargo.lock shows the specific versions chosen within the declared constraints:

Code Entity Dependency Mapping

flowchart TD
subgraph subGraph3["Transitive: Hardware Primitives"]
    bit_field_impl["BitField trait"]
    bitflags_impl["bitflags! macro"]
    volatile_impl["Volatile"]
    rustversion_impl["rustversion attributes"]
end
subgraph subGraph2["x86_64 v0.15.2"]
    port_read["Port::read()"]
    port_write["Port::write()"]
    instructions["instructions module"]
end
subgraph subGraph1["cfg-if v1.0.0"]
    cfg_if_macro["cfg_if! macro"]
end
subgraph subGraph0["Crate: x86_rtc"]
    rtc_new["Rtc::new()"]
    get_timestamp["get_unix_timestamp()"]
    set_timestamp["set_unix_timestamp()"]
end

get_timestamp --> port_read
instructions --> rustversion_impl
port_read --> bit_field_impl
port_read --> volatile_impl
port_write --> bitflags_impl
port_write --> volatile_impl
rtc_new --> cfg_if_macro
set_timestamp --> port_write

Sources: Cargo.toml(L14 - L18)  Cargo.lock(L36 - L45) 

Conditional Compilation Structure

Architecture-Specific Dependencies

The dependency structure implements a conditional compilation pattern where core hardware dependencies are only included for supported architectures:

// From Cargo.toml structure
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = "0.15"

This pattern ensures that:

  • The crate can be parsed on non-x86_64 platforms without pulling in architecture-specific dependencies
  • Hardware-specific functionality is only available when targeting compatible architectures
  • Build times are optimized for cross-compilation scenarios

Dependency Resolution Matrix

Build Targetcfg-ifx86_64Transitive Dependencies
x86_64-unknown-linux-gnuAll included
x86_64-unknown-noneAll included
other architecturesNone included

Conditional Dependency Flow


Sources: Cargo.toml(L17 - L18)  Cargo.lock(L40 - L53) 

Dependency Security and Maintenance

Version Stability

All dependencies use stable release versions:

  • No pre-release or development versions
  • Established crates with mature APIs
  • Conservative version constraints that allow security updates

Maintenance Burden

The dependency tree is intentionally minimal:

  • Only 6 total crates in the dependency graph
  • Well-maintained crates from established authors
  • Clear separation between conditional and unconditional dependencies

The focused dependency set reduces maintenance overhead while providing essential hardware abstraction capabilities required for x86_64 RTC operations.

Sources: Cargo.toml(L14 - L18)  Cargo.lock(L1 - L53) 

Platform and Architecture Requirements

Relevant source files

This document covers the platform-specific requirements and architectural constraints of the x86_rtc crate. It explains the x86_64 architecture dependency, conditional compilation strategies, and target platform support. For information about the crate's direct and transitive dependencies, see Dependency Analysis. For details about the core RTC implementation, see Implementation.

Architecture Specificity

The x86_rtc crate is exclusively designed for x86_64 architecture systems due to its reliance on x86-specific CMOS hardware interfaces. This architectural constraint is enforced through both crate metadata and conditional compilation directives.

x86_64 Hardware Dependency

The crate targets x86_64 systems because the Real Time Clock implementation requires direct access to CMOS registers via specific I/O ports that are part of the x86 architecture specification. The hardware interface is not portable to other architectures like ARM, RISC-V, or other platforms.


Sources: Cargo.toml(L6)  Cargo.toml(L11)  Cargo.toml(L17 - L18) 

Conditional Compilation Strategy

The crate employs Rust's conditional compilation features to ensure the x86_64 dependency is only included on compatible target architectures. This prevents compilation errors on unsupported platforms while maintaining clean dependency management.

Target-Specific Dependencies

The conditional dependency specification ensures that the x86_64 crate is only pulled in when building for x86_64 targets:

ConfigurationDependencyPurpose
cfg(target_arch = "x86_64")x86_64 = "0.15"Hardware register access and I/O port operations
All targetscfg-if = "1.0"Conditional compilation utilities

Sources: Cargo.toml(L15)  Cargo.toml(L17 - L18) 

Target Platform Support

The crate supports multiple x86_64 target environments through its no_std compatibility, enabling deployment in both hosted and bare-metal contexts.

Supported Build Targets

Target TripleEnvironmentUse Case
x86_64-unknown-linux-gnuHosted LinuxStandard applications, system services
x86_64-unknown-noneBare metalOperating system kernels, embedded systems

No-Standard Library Compatibility

The crate's no_std categorization enables usage in resource-constrained and bare-metal environments where the standard library is unavailable. This is essential for:

  • Operating system kernel development
  • Embedded systems programming
  • Bootloader implementations
  • Real-time systems
flowchart TD
subgraph subGraph2["Target Applications"]
    USER_APPS["User Applications"]
    SYSTEM_SERVICES["System Services"]
    OS_KERNELS["OS Kernels"]
    BOOTLOADERS["Bootloaders"]
    EMBEDDED["Embedded Systems"]
end
subgraph subGraph1["Standard Library Usage"]
    STD_AVAILABLE["std available"]
    NO_STD["no_std only"]
end
subgraph subGraph0["Runtime Environments"]
    HOSTED["Hosted Environment"]
    BARE_METAL["Bare Metal Environment"]
end

BARE_METAL --> NO_STD
HOSTED --> STD_AVAILABLE
NO_STD --> BOOTLOADERS
NO_STD --> EMBEDDED
NO_STD --> OS_KERNELS
STD_AVAILABLE --> SYSTEM_SERVICES
STD_AVAILABLE --> USER_APPS

Sources: Cargo.toml(L12) 

Hardware Abstraction Requirements

The platform requirements stem from the need to abstract x86_64-specific hardware features while maintaining safety and performance.

Register Access Patterns

The crate requires direct hardware register access capabilities that are only available through x86_64-specific instructions and I/O operations. This creates a hard dependency on the underlying architecture's instruction set and memory-mapped I/O capabilities.

Safety Constraints

Platform requirements also include memory safety considerations for hardware access:

  • Volatile memory access for hardware registers
  • Atomic operations for register synchronization
  • Interrupt-safe register manipulation
  • Proper handling of hardware timing constraints

Sources: Cargo.toml(L6)  Cargo.toml(L17 - L18) 

Build Configuration Impact

The platform requirements influence several aspects of the build configuration and crate capabilities.

Linting Adjustments

The crate includes specific clippy lint configurations that account for the hardware-specific nature of the implementation:

flowchart TD
subgraph subGraph1["Code Patterns"]
    CONSTRUCTOR_PATTERN["Constructor without Default"]
    HARDWARE_INIT["Hardware initialization"]
    UNSAFE_BLOCKS["Unsafe hardware access"]
end
subgraph subGraph0["Build Configuration"]
    CLIPPY_LINTS["clippy lints"]
    NEW_WITHOUT_DEFAULT["new_without_default = allow"]
    HARDWARE_PATTERNS["Hardware-specific patterns"]
end

CLIPPY_LINTS --> NEW_WITHOUT_DEFAULT
CONSTRUCTOR_PATTERN --> HARDWARE_INIT
HARDWARE_INIT --> UNSAFE_BLOCKS
HARDWARE_PATTERNS --> UNSAFE_BLOCKS
NEW_WITHOUT_DEFAULT --> CONSTRUCTOR_PATTERN

Edition and Toolchain Requirements

The crate specifies Rust 2021 edition compatibility, ensuring access to modern language features while maintaining compatibility with the x86_64 hardware abstraction layer.

Sources: Cargo.toml(L4)  Cargo.toml(L20 - L21) 

Development Workflow

Relevant source files

This document covers the development, testing, and deployment processes for the x86_rtc crate. It details the automated CI/CD pipeline, development environment requirements, and the workflow for contributing to and maintaining the codebase.

For detailed information about the crate's dependencies and platform requirements, see Dependencies and Platform Support. For implementation details of the RTC driver functionality, see Implementation.

CI/CD Pipeline Overview

The x86_rtc crate uses GitHub Actions for continuous integration and deployment, configured through a comprehensive workflow that ensures code quality, cross-platform compatibility, and automated documentation deployment.

Pipeline Architecture

flowchart TD
subgraph subGraph2["Documentation Job"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
    INDEX_GEN["Generate index.html redirect"]
    DEPLOY["Deploy to gh-pages branch"]
end
subgraph subGraph1["Quality Checks"]
    FORMAT["cargo fmt --all -- --check"]
    CLIPPY["cargo clippy --target TARGET --all-features"]
    BUILD["cargo build --target TARGET --all-features"]
    TEST["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph0["CI Job Matrix"]
    SETUP_GNU["Setup Environmentx86_64-unknown-linux-gnu"]
    SETUP_NONE["Setup Environmentx86_64-unknown-none"]
end
TRIGGER["push/pull_request events"]

BUILD --> TEST
CLIPPY --> BUILD
DOC_BUILD --> INDEX_GEN
FORMAT --> CLIPPY
INDEX_GEN --> DEPLOY
SETUP_GNU --> FORMAT
SETUP_NONE --> FORMAT
TRIGGER --> DOC_BUILD
TRIGGER --> SETUP_GNU
TRIGGER --> SETUP_NONE

Sources: .github/workflows/ci.yml(L1 - L56) 

Job Configuration Details

The CI pipeline consists of two primary jobs with specific configurations:

JobPurposeMatrix StrategyKey Steps
ciCode quality and cross-platform builds2 targets × 1 toolchainFormat check, clippy, build, test
docDocumentation generation and deploymentSingle configurationDoc build, GitHub Pages deployment

CI Job Matrix Strategy

The ci job uses a matrix strategy to test across multiple compilation targets:

strategy:
  fail-fast: false
  matrix:
    rust-toolchain: [nightly]
    targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none]

This configuration ensures compatibility with both standard Linux environments and bare-metal/kernel development contexts.

Sources: .github/workflows/ci.yml(L8 - L12) 

Toolchain and Component Setup

The pipeline uses the nightly Rust toolchain with specific components:

flowchart TD
subgraph subGraph1["Target Platforms"]
    GNU_TARGET["x86_64-unknown-linux-gnu"]
    NONE_TARGET["x86_64-unknown-none"]
end
subgraph subGraph0["Required Components"]
    RUST_SRC["rust-src"]
    CLIPPY["clippy"]
    RUSTFMT["rustfmt"]
end
TOOLCHAIN["dtolnay/rust-toolchain@nightly"]

TOOLCHAIN --> CLIPPY
TOOLCHAIN --> GNU_TARGET
TOOLCHAIN --> NONE_TARGET
TOOLCHAIN --> RUSTFMT
TOOLCHAIN --> RUST_SRC

Sources: .github/workflows/ci.yml(L15 - L19) 

Quality Assurance Steps

The CI pipeline enforces code quality through multiple sequential checks:

1. Code Formatting Verification

cargo fmt --all -- --check

Ensures all code adheres to standard Rust formatting conventions.

2. Linting with Clippy

cargo clippy --target ${{ matrix.targets }} --all-features -- -D warnings

Performs static analysis and treats all warnings as errors to maintain code quality.

3. Cross-Platform Build Verification

cargo build --target ${{ matrix.targets }} --all-features

Validates that the crate compiles successfully on both hosted and bare-metal targets.

4. Unit Testing

cargo test --target ${{ matrix.targets }} -- --nocapture

Executes unit tests, but only on x86_64-unknown-linux-gnu target due to hardware dependencies.

Sources: .github/workflows/ci.yml(L22 - L30) 

Documentation Deployment

The documentation job handles automated generation and deployment of API documentation:

sequenceDiagram
    participant Developer as "Developer"
    participant GitHub as "GitHub"
    participant GitHubActions as "GitHub Actions"
    participant GitHubPages as "GitHub Pages"

    Developer ->> GitHub: Push to default branch
    GitHub ->> GitHubActions: Trigger doc job
    GitHubActions ->> GitHubActions: cargo doc --no-deps --all-features
    GitHubActions ->> GitHubActions: Generate index.html redirect
    GitHubActions ->> GitHubPages: Deploy target/doc to gh-pages
    GitHubPages ->> Developer: Documentation available

Documentation Build Configuration

The documentation build uses specific flags to ensure quality:

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

These flags treat broken documentation links and missing documentation as errors.

Sources: .github/workflows/ci.yml(L40) 

Conditional Deployment Logic

Documentation deployment occurs only on the default branch:

if: ${{ github.ref == env.default-branch }}

This prevents documentation pollution from feature branches while ensuring the main documentation stays current.

Sources: .github/workflows/ci.yml(L50) 

Development Environment Setup

Required Tools and Configuration

Developers working on the x86_rtc crate need to configure their environment to match the CI pipeline requirements:

Rust Toolchain Requirements

  • Toolchain: Nightly Rust (required for certain features)
  • Components: rust-src, clippy, rustfmt
  • Targets: x86_64-unknown-linux-gnu, x86_64-unknown-none

Installation Commands

# Install nightly toolchain
rustup toolchain install nightly

# Add required components
rustup component add rust-src clippy rustfmt --toolchain nightly

# Add compilation targets
rustup target add x86_64-unknown-linux-gnu x86_64-unknown-none --toolchain nightly

Ignored Files and Artifacts

The repository uses .gitignore to exclude development artifacts and environment-specific files:

flowchart TD
subgraph subGraph0["Ignored Patterns"]
    TARGET["/targetRust build artifacts"]
    VSCODE["/.vscodeEditor configuration"]
    DSSTORE[".DS_StoremacOS system files"]
end
GITIGNORE[".gitignore"]

GITIGNORE --> DSSTORE
GITIGNORE --> TARGET
GITIGNORE --> VSCODE

Sources: .gitignore(L1 - L4) 

Local Development Workflow

Pre-commit Validation

Before submitting changes, developers should run the same checks as the CI pipeline:

# Format check
cargo fmt --all -- --check

# Linting
cargo clippy --target x86_64-unknown-linux-gnu --all-features -- -D warnings
cargo clippy --target x86_64-unknown-none --all-features -- -D warnings

# Build verification
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features

# Unit tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture

Documentation Development

Local documentation can be built and reviewed using:

# Build documentation
cargo doc --no-deps --all-features --open

# Check for documentation issues
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc

Contribution and Release Workflow

Branch Protection and Automation

The workflow is designed to support a development model where:

  1. All commits trigger format checking, linting, and cross-platform builds
  2. Pull requests receive the same validation without documentation deployment
  3. Default branch pushes additionally trigger documentation deployment to GitHub Pages

Integration Points

The development workflow integrates with several external systems:

IntegrationPurposeConfiguration
GitHub ActionsCI/CD automation.github/workflows/ci.yml
GitHub PagesDocumentation hostingAutomated deployment togh-pagesbranch
Rust toolchainBuild and validationNightly toolchain with specific components

Sources: .github/workflows/ci.yml(L1 - L56)  .gitignore(L1 - L4) 

CI/CD Pipeline

Relevant source files

This document details the continuous integration and continuous deployment (CI/CD) pipeline for the x86_rtc crate, implemented through GitHub Actions. The pipeline ensures code quality, cross-platform compatibility, and automated documentation deployment.

For information about the development environment setup and local development workflows, see Development Environment Setup. For details about the crate's platform requirements and target architectures, see Platform and Architecture Requirements.

Pipeline Overview

The CI/CD pipeline is defined in .github/workflows/ci.yml(L1 - L56)  and consists of two primary jobs: ci for code quality and testing, and doc for documentation generation and deployment.

flowchart TD
subgraph Outputs["Outputs"]
    QUALITY["Code Quality Validation"]
    PAGES["GitHub Pages Documentation"]
end
subgraph subGraph3["GitHub Actions Workflow"]
    subgraph subGraph2["doc job"]
        CHECKOUT_DOC["actions/checkout@v4"]
        TOOLCHAIN_DOC["dtolnay/rust-toolchain@nightly"]
        DOC_BUILD["cargo doc --no-deps --all-features"]
        DEPLOY["JamesIves/github-pages-deploy-action@v4"]
    end
    subgraph subGraph1["ci job"]
        MATRIX["Matrix Strategy"]
        CHECKOUT_CI["actions/checkout@v4"]
        TOOLCHAIN_CI["dtolnay/rust-toolchain@nightly"]
        FORMAT["cargo fmt --all -- --check"]
        CLIPPY["cargo clippy"]
        BUILD["cargo build"]
        TEST["cargo test"]
    end
end
subgraph subGraph0["Trigger Events"]
    PUSH["push events"]
    PR["pull_request events"]
end

BUILD --> TEST
CHECKOUT_CI --> TOOLCHAIN_CI
CHECKOUT_DOC --> TOOLCHAIN_DOC
CLIPPY --> BUILD
DEPLOY --> PAGES
DOC_BUILD --> DEPLOY
FORMAT --> CLIPPY
MATRIX --> CHECKOUT_CI
PR --> CHECKOUT_DOC
PR --> MATRIX
PUSH --> CHECKOUT_DOC
PUSH --> MATRIX
TEST --> QUALITY
TOOLCHAIN_CI --> FORMAT
TOOLCHAIN_DOC --> DOC_BUILD

Sources: .github/workflows/ci.yml(L1 - L56) 

CI Job Configuration

The ci job implements a comprehensive testing strategy using a matrix approach to validate the crate across multiple target platforms.

Matrix Strategy

ParameterValues
rust-toolchainnightly
targetsx86_64-unknown-linux-gnu,x86_64-unknown-none

The matrix configuration in .github/workflows/ci.yml(L8 - L12)  ensures the crate builds and functions correctly for both standard Linux environments and bare-metal/kernel environments.

flowchart TD
subgraph subGraph2["Matrix Execution"]
    NIGHTLY["nightly toolchain"]
    GNU["x86_64-unknown-linux-gnu target"]
    NONE["x86_64-unknown-none target"]
    subgraph subGraph1["Job Instance 2"]
        J2_TOOLCHAIN["nightly"]
        J2_TARGET["x86_64-unknown-none"]
        J2_STEPS["All steps except Unit tests"]
    end
    subgraph subGraph0["Job Instance 1"]
        J1_TOOLCHAIN["nightly"]
        J1_TARGET["x86_64-unknown-linux-gnu"]
        J1_STEPS["All steps + Unit tests"]
    end
end

GNU --> J1_TARGET
J1_TARGET --> J1_STEPS
J1_TOOLCHAIN --> J1_STEPS
J2_TARGET --> J2_STEPS
J2_TOOLCHAIN --> J2_STEPS
NIGHTLY --> J1_TOOLCHAIN
NIGHTLY --> J2_TOOLCHAIN
NONE --> J2_TARGET

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L29 - L30) 

CI Job Steps

The CI job executes the following validation steps in sequence:

flowchart TD
CHECKOUT["actions/checkout@v4Check out repository"]
SETUP["dtolnay/rust-toolchain@nightlyInstall Rust nightly + components"]
VERSION["rustc --version --verboseVerify Rust installation"]
FORMAT["cargo fmt --all -- --checkValidate code formatting"]
CLIPPY["cargo clippy --target TARGET --all-featuresLint analysis"]
BUILD["cargo build --target TARGET --all-featuresCompilation check"]
TEST["cargo test --target TARGET -- --nocaptureUnit testing (linux-gnu only)"]

BUILD --> TEST
CHECKOUT --> SETUP
CLIPPY --> BUILD
FORMAT --> CLIPPY
SETUP --> VERSION
VERSION --> FORMAT

Sources: .github/workflows/ci.yml(L13 - L30) 

The Rust toolchain setup .github/workflows/ci.yml(L15 - L19)  includes essential components:

  • rust-src: Source code for cross-compilation
  • clippy: Linting tool
  • rustfmt: Code formatting tool

Unit tests are conditionally executed only for the x86_64-unknown-linux-gnu target .github/workflows/ci.yml(L29 - L30)  since the bare-metal target (x86_64-unknown-none) cannot execute standard test harnesses.

Documentation Job

The doc job handles automated documentation generation and deployment to GitHub Pages.

Documentation Build Process

flowchart TD
subgraph subGraph2["Documentation Generation"]
    DOC_START["Job trigger"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
    BUILD_DOC["cargo doc --no-deps --all-features"]
    INDEX_GEN["Generate index.html redirect"]
    subgraph subGraph1["Conditional Deployment"]
        BRANCH_CHECK["Check if default branch"]
        DEPLOY["JamesIves/github-pages-deploy-action@v4"]
        PAGES_OUTPUT["GitHub Pages deployment"]
    end
    subgraph subGraph0["Build Configuration"]
        FLAGS["RUSTDOCFLAGS environment"]
        BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"]
        MISSING_DOCS["-D missing-docs"]
    end
end

BRANCH_CHECK --> DEPLOY
BROKEN_LINKS --> BUILD_DOC
BUILD_DOC --> INDEX_GEN
CHECKOUT --> TOOLCHAIN
DEPLOY --> PAGES_OUTPUT
DOC_START --> CHECKOUT
FLAGS --> BROKEN_LINKS
FLAGS --> MISSING_DOCS
INDEX_GEN --> BRANCH_CHECK
MISSING_DOCS --> BUILD_DOC
TOOLCHAIN --> FLAGS

Sources: .github/workflows/ci.yml(L32 - L56) 

Documentation Configuration

The documentation build enforces strict quality standards through RUSTDOCFLAGS .github/workflows/ci.yml(L40) :

FlagPurpose
-D rustdoc::broken_intra_doc_linksTreat broken internal documentation links as errors
-D missing-docsRequire documentation for all public APIs

The build process .github/workflows/ci.yml(L44 - L48)  generates documentation using cargo doc --no-deps --all-features and creates an automatic redirect index page pointing to the crate's documentation root.

Deployment Strategy

Documentation deployment follows a branch-based strategy:

flowchart TD
subgraph subGraph0["Branch Logic"]
    TRIGGER["Documentation build"]
    DEFAULT_CHECK["github.ref == env.default-branch"]
    DEPLOY_ACTION["Deploy to gh-pages branch"]
    CONTINUE["continue-on-error for non-default"]
    SKIP["Skip deployment"]
end

CONTINUE --> SKIP
DEFAULT_CHECK --> CONTINUE
DEFAULT_CHECK --> DEPLOY_ACTION
TRIGGER --> DEFAULT_CHECK

Sources: .github/workflows/ci.yml(L45)  .github/workflows/ci.yml(L49 - L55) 

Only builds from the default branch .github/workflows/ci.yml(L50)  trigger deployment to GitHub Pages using the JamesIves/github-pages-deploy-action@v4 action with single-commit: true to maintain a clean deployment history.

Pipeline Features

Error Handling and Robustness

The pipeline implements several error handling strategies:

Security and Permissions

The documentation job requires specific permissions .github/workflows/ci.yml(L36 - L37) :

  • contents: write for GitHub Pages deployment

Build Optimization

Key optimization features include:

Sources: .github/workflows/ci.yml(L1 - L56) 

Development Environment Setup

Relevant source files

Purpose and Scope

This document provides comprehensive guidelines for setting up a development environment for the x86_rtc crate, including toolchain requirements, project structure understanding, and development best practices. It covers the essential tools, configurations, and workflows needed to contribute effectively to this hardware-level RTC driver.

For information about the automated CI/CD pipeline that validates these development practices, see CI/CD Pipeline.

Prerequisites and Toolchain Requirements

The x86_rtc crate requires a specific Rust toolchain configuration to support its hardware-focused, no_std development model and target platforms.

Required Rust Toolchain

The project mandates the Rust nightly toolchain with specific components and targets:

ComponentPurposeRequired
nightlytoolchainAccess to unstable features for hardware programmingYes
rust-srcSource code for cross-compilationYes
clippyLinting and code quality analysisYes
rustfmtCode formatting standardsYes
x86_64-unknown-linux-gnuStandard Linux development targetYes
x86_64-unknown-noneBare metal/kernel development targetYes

Toolchain Installation Commands

# Install nightly toolchain
rustup toolchain install nightly

# Add required components
rustup component add --toolchain nightly rust-src clippy rustfmt

# Add compilation targets
rustup target add --toolchain nightly x86_64-unknown-linux-gnu
rustup target add --toolchain nightly x86_64-unknown-none

# Set nightly as default (optional)
rustup default nightly

Sources: .github/workflows/ci.yml(L11 - L19) 

Development Toolchain Architecture

flowchart TD
subgraph subGraph3["Development Commands"]
    BUILD_CMD["cargo build"]
    TEST_CMD["cargo test"]
    FMT_CMD["cargo fmt"]
    CLIPPY_CMD["cargo clippy"]
    DOC_CMD["cargo doc"]
end
subgraph subGraph2["Target Platforms"]
    GNU_TARGET["x86_64-unknown-linux-gnuHosted development"]
    NONE_TARGET["x86_64-unknown-noneBare metal/kernel"]
end
subgraph subGraph1["Required Components"]
    RUST_SRC["rust-srcCross-compilation support"]
    CLIPPY["clippyCode linting"]
    RUSTFMT["rustfmtCode formatting"]
end
subgraph subGraph0["Rust Toolchain"]
    NIGHTLY["nightly toolchain"]
    COMPONENTS["Components"]
    TARGETS["Compilation Targets"]
end

CLIPPY --> CLIPPY_CMD
COMPONENTS --> CLIPPY
COMPONENTS --> RUSTFMT
COMPONENTS --> RUST_SRC
GNU_TARGET --> TEST_CMD
NIGHTLY --> COMPONENTS
NIGHTLY --> TARGETS
NONE_TARGET --> BUILD_CMD
RUSTFMT --> FMT_CMD
RUST_SRC --> BUILD_CMD
TARGETS --> GNU_TARGET
TARGETS --> NONE_TARGET

Sources: .github/workflows/ci.yml(L15 - L19) 

Repository Structure and Ignored Files

Understanding the project's file organization and what gets excluded from version control is essential for effective development.

Version Control Exclusions

The .gitignore configuration maintains a clean repository by excluding build artifacts and development-specific files:

PathDescriptionReason for Exclusion
/targetCargo build output directoryGenerated artifacts, platform-specific
/.vscodeVisual Studio Code workspace settingsIDE-specific, personal preferences
.DS_StoremacOS file system metadataPlatform-specific system files

Development File Structure

flowchart TD
subgraph subGraph2["Build Process"]
    CARGO_BUILD["cargo build"]
    CARGO_TEST["cargo test"]
    CARGO_DOC["cargo doc"]
end
subgraph subGraph1["Generated/Ignored Files"]
    TARGET["/targetBuild artifacts"]
    VSCODE["/.vscodeIDE settings"]
    DSSTORE[".DS_StoremacOS metadata"]
end
subgraph subGraph0["Version Controlled Files"]
    SRC["src/lib.rsCore implementation"]
    CARGO["Cargo.tomlCrate configuration"]
    LOCK["Cargo.lockDependency versions"]
    CI["/.github/workflows/ci.ymlCI configuration"]
    README["README.mdDocumentation"]
end

CARGO --> CARGO_BUILD
CARGO_BUILD --> TARGET
CARGO_DOC --> TARGET
CARGO_TEST --> TARGET
SRC --> CARGO_BUILD
TARGET --> DSSTORE
TARGET --> VSCODE

Sources: .gitignore(L1 - L4) 

Development Workflow and Code Quality

The development workflow is designed around the CI pipeline requirements, ensuring code quality and platform compatibility before integration.

Code Quality Pipeline

The development process follows a structured approach that mirrors the automated CI checks:

flowchart TD
subgraph subGraph1["Quality Gates"]
    FMT_CHECK["Formatting Check"]
    LINT_CHECK["Linting Check"]
    BUILD_CHECK["Build Check"]
    TEST_CHECK["Test Check"]
    DOC_CHECK["Documentation Check"]
end
subgraph subGraph0["Development Cycle"]
    EDIT["Edit Code"]
    FORMAT["cargo fmt --all --check"]
    LINT["cargo clippy --all-features"]
    BUILD_GNU["cargo build --target x86_64-unknown-linux-gnu"]
    BUILD_NONE["cargo build --target x86_64-unknown-none"]
    TEST["cargo test --target x86_64-unknown-linux-gnu"]
    DOC["cargo doc --no-deps --all-features"]
end

BUILD_CHECK --> TEST
BUILD_GNU --> BUILD_NONE
BUILD_NONE --> BUILD_CHECK
DOC --> DOC_CHECK
EDIT --> FORMAT
FMT_CHECK --> LINT
FORMAT --> FMT_CHECK
LINT --> LINT_CHECK
LINT_CHECK --> BUILD_GNU
TEST --> TEST_CHECK
TEST_CHECK --> DOC

Sources: .github/workflows/ci.yml(L22 - L30) 

Development Commands Reference

CommandPurposeTarget PlatformCI Equivalent
cargo fmt --all -- --checkVerify code formattingAllLine 23
cargo clippy --target --all-features -- -D warningsLint analysisBoth targetsLine 25
cargo build --target --all-featuresCompilation checkBoth targetsLine 27
cargo test --target x86_64-unknown-linux-gnu -- --nocaptureUnit testingLinux onlyLine 30
cargo doc --no-deps --all-featuresDocumentation generationDefaultLine 47

Local Testing Environment

Target-Specific Testing

Testing is platform-dependent due to the hardware-specific nature of the crate:

Test TypePlatformCapabilityLimitations
Unit Testsx86_64-unknown-linux-gnuFull test executionRequires hosted environment
Build Testsx86_64-unknown-noneCompilation verification onlyNo test execution in bare metal
DocumentationDefault targetAPI documentation generationNo hardware interaction

Hardware Testing Considerations

Since this crate provides hardware-level RTC access, comprehensive testing requires understanding of the limitations:

flowchart TD
subgraph subGraph2["Hardware Interaction"]
    CMOS_ACCESS["CMOS Register AccessRequires privileged mode"]
    RTC_OPS["RTC OperationsHardware-dependent"]
end
subgraph subGraph1["Test Capabilities"]
    UNIT_TESTS["Unit TestsAPI verification"]
    BUILD_TESTS["Build TestsCompilation validation"]
    DOC_TESTS["Documentation TestsExample validation"]
end
subgraph subGraph0["Testing Environments"]
    HOSTED["Hosted Environmentx86_64-unknown-linux-gnu"]
    BARE["Bare Metal Environmentx86_64-unknown-none"]
end

BARE --> BUILD_TESTS
CMOS_ACCESS --> HOSTED
HOSTED --> BUILD_TESTS
HOSTED --> DOC_TESTS
HOSTED --> UNIT_TESTS
RTC_OPS --> HOSTED
UNIT_TESTS --> CMOS_ACCESS
UNIT_TESTS --> RTC_OPS

Sources: .github/workflows/ci.yml(L28 - L30) 

Documentation Development

Local Documentation Generation

The project uses strict documentation standards enforced through RUSTDOCFLAGS:

# Generate documentation with strict checks
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" cargo doc --no-deps --all-features

The documentation generation process creates a redirect index for easy navigation:

# Generate redirect index (automated in CI)
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L47 - L48) 

Best Practices Summary

  1. Always use nightly toolchain - Required for hardware programming features
  2. Test on both targets - Ensure compatibility with hosted and bare metal environments
  3. Run full quality pipeline locally - Mirror CI checks before committing
  4. Maintain documentation standards - All public APIs must be documented
  5. Respect platform limitations - Understand hardware testing constraints
  6. Keep repository clean - Respect .gitignore patterns for collaborative development

Sources: .github/workflows/ci.yml(L1 - L56)  .gitignore(L1 - L4) 

Overview

Relevant source files

This document provides an overview of the percpu crate ecosystem, a Rust library system that provides efficient per-CPU data management for bare-metal and kernel development. The system consists of two main crates that work together to enable safe, efficient access to CPU-local data structures across multiple processor architectures.

The percpu system solves the fundamental problem of managing data that needs to be accessible per-CPU without synchronization overhead, critical for kernel and hypervisor development. For detailed API usage patterns, see Basic Usage Examples. For implementation details of the code generation pipeline, see Code Generation Pipeline.

System Architecture

The percpu ecosystem is built around two cooperating crates that provide compile-time code generation and runtime per-CPU data management:

flowchart TD
subgraph subGraph4["Architecture Registers"]
    X86_GS["x86_64: GS_BASE"]
    ARM_TPIDR["AArch64: TPIDR_EL1/EL2"]
    RISCV_GP["RISC-V: gp"]
    LOONG_R21["LoongArch: $r21"]
end
subgraph subGraph3["Memory Layout"]
    PERCPU_SECTION[".percpu section"]
    CPU_AREAS["Per-CPU data areasCPU 0, CPU 1, ..., CPU N"]
    LINKER_SCRIPT["_percpu_start_percpu_end_percpu_load_start"]
end
subgraph subGraph2["percpu Runtime Crate"]
    INIT_FUNC["init() -> usize"]
    INIT_REG["init_percpu_reg(cpu_id)"]
    AREA_ALLOC["percpu_area_base()"]
    REG_ACCESS["read_percpu_reg()write_percpu_reg()"]
end
subgraph subGraph1["percpu_macros Crate"]
    DEF_PERCPU["def_percpu proc macro"]
    CODE_GEN["Architecture-specificcode generation"]
    GENERATED["__PERCPU_CPU_IDCPU_ID_WRAPPERassembly blocks"]
end
subgraph subGraph0["Application Code"]
    USER_VAR["#[percpu::def_percpu]static CPU_ID: usize = 0"]
    USER_INIT["percpu::init()"]
    USER_ACCESS["CPU_ID.read_current()"]
end

AREA_ALLOC --> CPU_AREAS
ARM_TPIDR --> CPU_AREAS
CODE_GEN --> GENERATED
CPU_AREAS --> LINKER_SCRIPT
DEF_PERCPU --> CODE_GEN
GENERATED --> PERCPU_SECTION
GENERATED --> REG_ACCESS
INIT_FUNC --> AREA_ALLOC
INIT_REG --> ARM_TPIDR
INIT_REG --> LOONG_R21
INIT_REG --> RISCV_GP
INIT_REG --> X86_GS
LOONG_R21 --> CPU_AREAS
PERCPU_SECTION --> CPU_AREAS
REG_ACCESS --> ARM_TPIDR
REG_ACCESS --> LOONG_R21
REG_ACCESS --> RISCV_GP
REG_ACCESS --> X86_GS
RISCV_GP --> CPU_AREAS
USER_ACCESS --> GENERATED
USER_INIT --> INIT_FUNC
USER_INIT --> INIT_REG
USER_VAR --> DEF_PERCPU
X86_GS --> CPU_AREAS

Sources: README.md(L40 - L52)  Cargo.toml(L4 - L7) 

Code Generation and Runtime Flow

The system transforms user-defined per-CPU variables into architecture-specific implementations through a multi-stage process:

flowchart TD
subgraph subGraph3["Access Methods"]
    READ_CURRENT["read_current()"]
    WRITE_CURRENT["write_current()"]
    REMOTE_PTR["remote_ptr(cpu_id)"]
    REMOTE_REF["remote_ref(cpu_id)"]
end
subgraph subGraph2["Runtime Execution"]
    INIT_CALL["percpu::init()"]
    DETECT_CPUS["detect CPU count"]
    ALLOC_AREAS["allocate per-CPU areas"]
    COPY_TEMPLATE["copy .percpu templateto each CPU area"]
    SET_REGISTERS["init_percpu_reg(cpu_id)per CPU"]
end
subgraph subGraph1["Generated Artifacts"]
    STORAGE_VAR["__PERCPU_VARNAMEin .percpu section"]
    WRAPPER_STRUCT["VARNAME_WRAPPER"]
    PUBLIC_STATIC["VARNAME: VARNAME_WRAPPER"]
    ASM_BLOCKS["Architecture-specificinline assembly"]
end
subgraph subGraph0["Compile Time"]
    ATTR["#[percpu::def_percpu]"]
    PARSE["syn::parse_macro_input"]
    ARCH_DETECT["cfg(target_arch)"]
    QUOTE_GEN["quote! macro expansion"]
end

ALLOC_AREAS --> COPY_TEMPLATE
ARCH_DETECT --> QUOTE_GEN
ASM_BLOCKS --> READ_CURRENT
ASM_BLOCKS --> WRITE_CURRENT
ATTR --> PARSE
COPY_TEMPLATE --> SET_REGISTERS
DETECT_CPUS --> ALLOC_AREAS
INIT_CALL --> DETECT_CPUS
PARSE --> ARCH_DETECT
PUBLIC_STATIC --> READ_CURRENT
PUBLIC_STATIC --> REMOTE_PTR
PUBLIC_STATIC --> REMOTE_REF
PUBLIC_STATIC --> WRITE_CURRENT
QUOTE_GEN --> ASM_BLOCKS
QUOTE_GEN --> PUBLIC_STATIC
QUOTE_GEN --> STORAGE_VAR
QUOTE_GEN --> WRAPPER_STRUCT
SET_REGISTERS --> READ_CURRENT
SET_REGISTERS --> WRITE_CURRENT

Sources: README.md(L39 - L52)  CHANGELOG.md(L8 - L12) 

Platform Support Matrix

The percpu system provides cross-platform support through architecture-specific register handling:

ArchitectureRegister UsedAssembly InstructionsFeature Support
x86_64GS_BASEmov gs:[offset], regFull support
AArch64TPIDR_EL1/EL2mrs/msrinstructionsEL1/EL2 modes
RISC-Vgpmv gp, reg32-bit and 64-bit
LoongArch$r21lu12i.w/ori64-bit support

The system adapts its code generation based on cfg(target_arch) directives and provides feature flags for specialized environments:

  • sp-naive: Single-processor fallback using global variables
  • preempt: Preemption-safe access with NoPreemptGuard integration
  • arm-el2: Hypervisor mode support using TPIDR_EL2

Sources: README.md(L19 - L35)  README.md(L69 - L79) 

Integration Requirements

Linker Script Configuration

The system requires manual linker script modification to define the .percpu section layout:

_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
    _percpu_load_start = .;
    *(.percpu .percpu.*)
    _percpu_load_end = .;
    . = _percpu_load_start + ALIGN(64) * CPU_NUM;
}

Workspace Structure

The system is organized as a Cargo workspace containing:

  • percpu: Runtime implementation and API
  • percpu_macros: Procedural macro implementation

Both crates share common metadata and versioning through workspace.package configuration.

Sources: README.md(L54 - L67)  Cargo.toml(L1 - L25) 

Key API Entry Points

The primary user interface consists of:

  • percpu::def_percpu: Procedural macro for defining per-CPU variables
  • percpu::init(): Initialize per-CPU memory areas, returns CPU count
  • percpu::init_percpu_reg(cpu_id): Configure per-CPU register for specific CPU
  • .read_current() / .write_current(): Access current CPU's data
  • .remote_ptr(cpu_id) / .remote_ref(cpu_id): Access remote CPU data

The system generates wrapper types that provide these methods while maintaining type safety and architecture-specific optimization.

Sources: README.md(L40 - L52)  CHANGELOG.md(L8 - L12) 

System Components

Relevant source files

Purpose and Scope

This document details the architectural components of the percpu crate ecosystem, focusing on the two main crates (percpu and percpu_macros) and their integration with dependencies. It covers the workspace structure, feature configuration, and how the components collaborate to provide per-CPU data management functionality.

For platform-specific implementation details, see Supported Platforms. For detailed API usage, see API Reference.

Core Components Architecture

Workspace Component Structure

flowchart TD
subgraph percpu_workspace["percpu Workspace"]
    subgraph macro_deps["Macro Dependencies"]
        PROC_MACRO2["proc-macro2"]
        SYN["syn"]
        QUOTE["quote"]
    end
    subgraph dependencies["Core Dependencies"]
        CFG_IF["cfg-if"]
        KERNEL_GUARD["kernel_guard(optional)"]
        X86_DEP["x86(x86_64 only)"]
        SPIN_DEP["spin(non-none OS)"]
    end
    subgraph percpu_macros_crate["percpu_macros Crate"]
        MACRO_LIB["lib.rsProcedural Macros"]
        DEF_PERCPU["def_percpu macro"]
        CODE_GEN["Code Generation"]
    end
    subgraph percpu_crate["percpu Crate"]
        PERCPU_LIB["lib.rsRuntime Implementation"]
        PERCPU_ACCESS["Access Methods"]
        PERCPU_INIT["Initialization Functions"]
    end
end

CODE_GEN --> PERCPU_INIT
DEF_PERCPU --> PERCPU_ACCESS
MACRO_LIB --> PROC_MACRO2
MACRO_LIB --> QUOTE
MACRO_LIB --> SYN
PERCPU_LIB --> CFG_IF
PERCPU_LIB --> KERNEL_GUARD
PERCPU_LIB --> MACRO_LIB
PERCPU_LIB --> SPIN_DEP
PERCPU_LIB --> X86_DEP

Sources: Cargo.toml(L4 - L7)  percpu/Cargo.toml(L27 - L36)  percpu_macros/Cargo.toml(L27 - L30) 

Runtime Implementation (percpuCrate)

The percpu crate provides the runtime infrastructure for per-CPU data management. It handles memory allocation, initialization, and provides safe access methods to per-CPU variables.

Key Responsibilities

ComponentFunctionDependencies
InitializationAllocates per-CPU memory areas and configures CPU registerscfg-if, architecture-specific registers
Access MethodsProvides safe local and remote per-CPU data accessGenerated bypercpu_macros
Memory ManagementManages per-CPU memory layout and addressingLinker script integration
Preemption SafetyOptional preemption-safe operationskernel_guard(whenpreemptfeature enabled)

Feature Configuration

The runtime crate supports three main feature flags:

  • sp-naive: Single-processor mode using global variables instead of per-CPU storage
  • preempt: Enables preemption safety with kernel_guard integration
  • arm-el2: ARM-specific support for EL2 privilege level execution

Sources: percpu/Cargo.toml(L15 - L25) 

Macro Implementation (percpu_macrosCrate)

The percpu_macros crate provides compile-time code generation through procedural macros. It transforms user-defined per-CPU variable declarations into architecture-specific implementation code.

Macro Processing Pipeline

flowchart TD
subgraph output_components["Generated Components"]
    STORAGE_VAR["__PERCPU_VAR(.percpu section)"]
    WRAPPER_STRUCT["VarWrapper(access methods)"]
    PUBLIC_STATIC["VAR: VarWrapper(public interface)"]
end
subgraph code_generation["Code Generation"]
    ARCH_DETECT["Architecture Detection"]
    FEATURE_CHECK["Feature Flag Processing"]
    QUOTE_GEN["quote! Templates"]
end
subgraph input_parsing["Input Parsing"]
    USER_CODE["#[def_percpu]static VAR: Type = init;"]
    SYN_PARSER["syn::parse"]
    AST_TOKENS["TokenStream AST"]
end

ARCH_DETECT --> FEATURE_CHECK
AST_TOKENS --> ARCH_DETECT
FEATURE_CHECK --> QUOTE_GEN
QUOTE_GEN --> PUBLIC_STATIC
QUOTE_GEN --> STORAGE_VAR
QUOTE_GEN --> WRAPPER_STRUCT
SYN_PARSER --> AST_TOKENS
USER_CODE --> SYN_PARSER

Macro Dependencies

DependencyPurposeUsage
synAST parsing and manipulationParses#[def_percpu]macro input
quoteCode generation templatesGenerates architecture-specific access code
proc-macro2Token stream processingHandles procedural macro token manipulation

Sources: percpu_macros/Cargo.toml(L27 - L30)  percpu_macros/Cargo.toml(L32 - L33) 

Component Integration and Data Flow

Inter-Component Communication

flowchart TD
subgraph arch_specific["Architecture-Specific"]
    X86_SUPPORT["x86 crate(MSR operations)"]
    GS_BASE["GS_BASE register"]
    TPIDR_REGS["TPIDR_EL1/EL2"]
    GP_REG["gp register (RISC-V)"]
end
subgraph feature_integration["Feature Integration"]
    SP_NAIVE_MODE["sp-naive Mode(Global Variables)"]
    PREEMPT_GUARD["NoPreemptGuard(kernel_guard)"]
    ARM_EL2_REGS["EL2 Register Access(ARM)"]
end
subgraph runtime_components["Runtime Components"]
    INIT_FUNC["percpu::init()"]
    CPU_AREAS["Per-CPU Memory Areas"]
    ACCESS_METHODS["read_current/write_current"]
    REMOTE_ACCESS["remote_ptr/remote_ref"]
end
subgraph compile_time["Compile Time"]
    MACRO_INPUT["User Macro Invocation"]
    MACRO_PROC["percpu_macros Processing"]
    CODE_OUTPUT["Generated Runtime Code"]
end

ACCESS_METHODS --> GP_REG
ACCESS_METHODS --> TPIDR_REGS
ACCESS_METHODS --> X86_SUPPORT
CODE_OUTPUT --> ARM_EL2_REGS
CODE_OUTPUT --> INIT_FUNC
CODE_OUTPUT --> PREEMPT_GUARD
CODE_OUTPUT --> SP_NAIVE_MODE
CPU_AREAS --> ACCESS_METHODS
CPU_AREAS --> REMOTE_ACCESS
INIT_FUNC --> CPU_AREAS
MACRO_INPUT --> MACRO_PROC
MACRO_PROC --> CODE_OUTPUT
X86_SUPPORT --> GS_BASE

Workspace Configuration

The workspace is configured as a multi-crate project with shared metadata and dependency management:

  • Version: 0.2.0 across all crates
  • Edition: Rust 2021
  • License: Triple-licensed (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0)
  • Target Platforms: no-std embedded systems and kernel environments

Sources: Cargo.toml(L9 - L24) 

Feature Flag Synchronization

Both crates maintain identical feature flag definitions to ensure consistent behavior across the macro generation and runtime execution phases:

FeaturepercpuCratepercpu_macrosCrateEffect
sp-naiveEnables global variable fallbackControls code generation modeSingle-CPU optimization
preemptAddskernel_guarddependencyGenerates preemption-safe codeThread safety
arm-el2Architecture-specific configSelects EL2 register usageHypervisor support

This synchronization ensures that macro-generated code matches the runtime environment configuration.

Sources: percpu/Cargo.toml(L18 - L25)  percpu_macros/Cargo.toml(L18 - L25) 

Supported Platforms

Relevant source files

This document provides a comprehensive overview of the CPU architectures and platforms supported by the percpu crate ecosystem. It covers architecture-specific per-CPU register usage, implementation details, and platform-specific considerations for integrating per-CPU data management.

For information about setting up these platforms in your build environment, see Installation and Setup. For details about the architecture-specific code generation mechanisms, see Architecture-Specific Code Generation.

Supported Architecture Overview

The percpu crate supports four major CPU architectures, each utilizing different registers for per-CPU data access:

Architectureper-CPU RegisterRegister TypeSupported Variants
x86_64GS_BASESegment base registerStandard x86_64
AArch64TPIDR_ELxThread pointer registerEL1, EL2 modes
RISC-VgpGlobal pointer register32-bit, 64-bit
LoongArch$r21General purpose register64-bit

Platform Register Usage

flowchart TD
subgraph subGraph2["Access Methods"]
    X86_ASM["mov gs:[offset]"]
    ARM_ASM["mrs/msr instructions"]
    RISCV_ASM["lui/addi with gp"]
    LOONG_ASM["lu12i.w/ori with $r21"]
end
subgraph subGraph1["Per-CPU Registers"]
    GS["GS_BASESegment Register"]
    TPIDR["TPIDR_ELxThread Pointer"]
    GP["gpGlobal Pointer"]
    R21["$r21General Purpose"]
end
subgraph subGraph0["Architecture Support"]
    X86["x86_64"]
    ARM["AArch64"]
    RISCV["RISC-V"]
    LOONG["LoongArch"]
end

ARM --> TPIDR
GP --> RISCV_ASM
GS --> X86_ASM
LOONG --> R21
R21 --> LOONG_ASM
RISCV --> GP
TPIDR --> ARM_ASM
X86 --> GS

Sources: README.md(L19 - L31)  percpu_macros/src/arch.rs(L21 - L46) 

Platform-Specific Implementation Details

x86_64 Architecture

The x86_64 implementation uses the GS_BASE model-specific register to store the base address of the per-CPU data area. This approach leverages the x86_64 segment architecture for efficient per-CPU data access.

Key characteristics:

  • Uses GS_BASE MSR for per-CPU base address storage
  • Accesses data via gs:[offset] addressing mode
  • Requires offset values ≤ 0xffff_ffff for 32-bit displacement
  • Supports optimized single-instruction memory operations
flowchart TD
subgraph subGraph1["Generated Assembly"]
    MOV_READ["mov reg, gs:[offset VAR]Direct read"]
    MOV_WRITE["mov gs:[offset VAR], regDirect write"]
    ADDR_CALC["mov reg, offset VARadd reg, gs:[__PERCPU_SELF_PTR]"]
end
subgraph subGraph0["x86_64 Implementation"]
    GS_BASE["GS_BASE MSRBase Address"]
    OFFSET["Symbol OffsetCalculated at compile-time"]
    ACCESS["gs:[offset] AccessSingle instruction"]
end

ACCESS --> ADDR_CALC
ACCESS --> MOV_READ
ACCESS --> MOV_WRITE
GS_BASE --> ACCESS
OFFSET --> ACCESS

Sources: percpu_macros/src/arch.rs(L66 - L76)  percpu_macros/src/arch.rs(L131 - L150)  percpu_macros/src/arch.rs(L232 - L251) 

AArch64 Architecture

The AArch64 implementation uses thread pointer registers that vary based on the exception level. The system supports both EL1 (kernel) and EL2 (hypervisor) execution environments.

Key characteristics:

  • Uses TPIDR_EL1 by default, TPIDR_EL2 when arm-el2 feature is enabled
  • Requires mrs/msr instructions for register access
  • Offset calculations limited to 16-bit immediate values (≤ 0xffff)
  • Two-instruction sequence for per-CPU data access

The register selection is controlled at compile time:

flowchart TD
subgraph subGraph2["Access Pattern"]
    MRS["mrs reg, TPIDR_ELxGet base address"]
    CALC["add reg, offsetCalculate final address"]
    ACCESS["ldr/str operationsMemory access"]
end
subgraph subGraph1["Register Usage"]
    EL1["TPIDR_EL1Kernel Mode"]
    EL2["TPIDR_EL2Hypervisor Mode"]
end
subgraph subGraph0["AArch64 Register Selection"]
    FEATURE["arm-el2 feature"]
    DEFAULT["Default Mode"]
end

CALC --> ACCESS
DEFAULT --> EL1
EL1 --> MRS
EL2 --> MRS
FEATURE --> EL2
MRS --> CALC

Sources: percpu_macros/src/arch.rs(L55 - L62)  percpu_macros/src/arch.rs(L79 - L80)  README.md(L33 - L35) 

RISC-V Architecture

The RISC-V implementation uses the global pointer (gp) register for per-CPU data base addressing. This is a deviation from typical RISC-V conventions where gp is used for global data access.

Key characteristics:

  • Uses gp register instead of standard thread pointer (tp)
  • Supports both 32-bit and 64-bit variants
  • Uses lui/addi instruction sequence for address calculation
  • Offset values limited to 32-bit signed immediate range

Important note: The tp register remains available for thread-local storage, while gp is repurposed for per-CPU data.

flowchart TD
subgraph subGraph2["Memory Operations"]
    LOAD["ld/lw/lh/lb instructionsBased on data type"]
    STORE["sd/sw/sh/sb instructionsBased on data type"]
end
subgraph subGraph1["Address Calculation"]
    LUI["lui reg, %hi(VAR)Load upper immediate"]
    ADDI["addi reg, reg, %lo(VAR)Add lower immediate"]
    FINAL["add reg, reg, gpAdd base address"]
end
subgraph subGraph0["RISC-V Register Usage"]
    GP["gp registerPer-CPU base"]
    TP["tp registerThread-local storage"]
    OFFSET["Symbol offset%hi/%lo split"]
end

ADDI --> FINAL
FINAL --> LOAD
FINAL --> STORE
GP --> FINAL
LUI --> ADDI
OFFSET --> LUI

Sources: percpu_macros/src/arch.rs(L81 - L82)  percpu_macros/src/arch.rs(L33 - L39)  README.md(L28 - L31) 

LoongArch Architecture

The LoongArch implementation uses the $r21 general-purpose register for per-CPU data base addressing. This architecture provides native support for per-CPU data patterns.

Key characteristics:

  • Uses $r21 general-purpose register for base addressing
  • Supports 64-bit architecture (LoongArch64)
  • Uses lu12i.w/ori instruction sequence for address calculation
  • Provides specialized load/store indexed instructions
flowchart TD
subgraph subGraph1["Instruction Sequences"]
    LU12I["lu12i.w reg, %abs_hi20(VAR)Load upper 20 bits"]
    ORI["ori reg, reg, %abs_lo12(VAR)OR lower 12 bits"]
    LDX["ldx.d/ldx.w/ldx.h/ldx.buLoad indexed"]
    STX["stx.d/stx.w/stx.h/stx.bStore indexed"]
end
subgraph subGraph0["LoongArch Implementation"]
    R21["$r21 registerPer-CPU base"]
    CALC["Address calculationlu12i.w + ori"]
    INDEXED["Indexed operationsldx/stx instructions"]
end

CALC --> LU12I
LU12I --> ORI
ORI --> LDX
ORI --> STX
R21 --> INDEXED

Sources: percpu_macros/src/arch.rs(L83 - L84)  percpu_macros/src/arch.rs(L40 - L46)  percpu_macros/src/arch.rs(L114 - L129) 

Continuous Integration and Testing Coverage

The percpu crate maintains comprehensive testing across all supported platforms through automated CI/CD pipelines. The testing matrix ensures compatibility and correctness across different target environments.

CI Target Matrix:

TargetArchitectureEnvironmentTest Coverage
x86_64-unknown-linux-gnux86_64Linux userspaceFull tests + unit tests
x86_64-unknown-nonex86_64Bare metal/no_stdBuild + clippy only
riscv64gc-unknown-none-elfRISC-V 64Bare metal/no_stdBuild + clippy only
aarch64-unknown-none-softfloatAArch64Bare metal/no_stdBuild + clippy only
loongarch64-unknown-none-softfloatLoongArch64Bare metal/no_stdBuild + clippy only

Testing Strategy:

flowchart TD
subgraph subGraph2["Feature Combinations"]
    SP_NAIVE["sp-naive featureSingle-core testing"]
    PREEMPT["preempt featurePreemption safety"]
    ARM_EL2["arm-el2 featureHypervisor mode"]
end
subgraph subGraph1["Test Types"]
    BUILD["Build TestsAll targets"]
    UNIT["Unit Testsx86_64 Linux only"]
    LINT["Code QualityAll targets"]
end
subgraph subGraph0["CI Pipeline"]
    MATRIX["Target Matrix5 platforms tested"]
    FEATURES["Feature Testingpreempt, arm-el2"]
    TOOLS["Quality Toolsclippy, rustfmt"]
end

BUILD --> ARM_EL2
BUILD --> PREEMPT
BUILD --> SP_NAIVE
FEATURES --> ARM_EL2
FEATURES --> PREEMPT
FEATURES --> SP_NAIVE
MATRIX --> BUILD
MATRIX --> LINT
MATRIX --> UNIT

Sources: .github/workflows/ci.yml(L8 - L32) 

Platform-Specific Limitations and Considerations

macOS Development Limitation

The crate includes a development-time limitation for macOS hosts, where architecture-specific assembly code is disabled and replaced with unimplemented stubs. This affects local development but not target deployment.

Offset Size Constraints

Each architecture imposes different constraints on the maximum offset values for per-CPU variables:

  • x86_64: 32-bit signed displacement (≤ 0xffff_ffff)
  • AArch64: 16-bit immediate value (≤ 0xffff)
  • RISC-V: 32-bit signed immediate (split hi/lo)
  • LoongArch: 32-bit absolute address (split hi20/lo12)

Register Conflicts and Conventions

  • RISC-V: Repurposes gp register, deviating from standard ABI conventions
  • AArch64: Requires careful exception level management for register selection
  • x86_64: Depends on GS segment register configuration by system initialization

Sources: percpu_macros/src/arch.rs(L4 - L13)  percpu_macros/src/arch.rs(L23 - L46)  README.md(L28 - L35) 

Getting Started

Relevant source files

This page provides a step-by-step guide to integrating and using the percpu crate for per-CPU data management in your projects. It covers the essential workflow from dependency setup through basic usage patterns, serving as an entry point for developers new to the percpu system.

For detailed installation instructions and build environment configuration, see Installation and Setup. For comprehensive examples and advanced usage patterns, see Basic Usage Examples. For information about configuring features for different deployment scenarios, see Feature Flags Configuration.

Integration Workflow Overview

The percpu crate requires coordination between compile-time code generation, linker script configuration, and runtime initialization. The following diagram illustrates the complete integration workflow:

flowchart TD
subgraph subGraph2["Generated Artifacts"]
    SECTION[".percpu sectionin binary"]
    WRAPPER["Generated wrapper structswith access methods"]
    AREAS["Per-CPU memory areasat runtime"]
end
subgraph subGraph1["Code Development"]
    DEFINE["Define per-CPU variables#[percpu::def_percpu]"]
    INIT["Initialize systempercpu::init()"]
    SETUP["Configure per-CPU registerspercpu::init_percpu_reg()"]
    ACCESS["Access variablesread_current(), write_current()"]
end
subgraph subGraph0["Build Configuration"]
    CARGO["Add percpu dependencyto Cargo.toml"]
    FEATURES["Configure feature flagssp-naive, preempt, arm-el2"]
    LINKER["Modify linker scriptAdd .percpu section"]
end
START["Start Integration"]

AREAS --> ACCESS
CARGO --> FEATURES
DEFINE --> SECTION
DEFINE --> WRAPPER
FEATURES --> LINKER
INIT --> AREAS
LINKER --> DEFINE
SECTION --> AREAS
SETUP --> ACCESS
START --> CARGO
WRAPPER --> ACCESS

Sources: README.md(L39 - L52)  percpu/tests/test_percpu.rs(L35 - L50) 

Essential Components and Their Relationships

The percpu system consists of several key components that work together to provide efficient per-CPU data access. Understanding these relationships is crucial for proper integration:

flowchart TD
subgraph subGraph3["Linker Script Space"]
    SCRIPT["_percpu_start_percpu_end_percpu_load_start"]
    SECTION[".percpu 0x0 (NOLOAD)section definition"]
end
subgraph subGraph2["Runtime System Space"]
    AREAS["percpu_area_base()memory allocation"]
    REGISTER["read_percpu_reg()write_percpu_reg()"]
    OFFSETS["CPU_ID.offset()address calculation"]
end
subgraph subGraph1["Generated Code Space"]
    STORAGE["__PERCPU_CPU_IDin .percpu section"]
    WRAPPER["CPU_ID_WRAPPERaccess methods"]
    INTERFACE["CPU_ID: CPU_ID_WRAPPERpublic interface"]
end
subgraph subGraph0["User Code Space"]
    USRVAR["#[percpu::def_percpu]static CPU_ID: usize = 0"]
    USRINIT["percpu::init()"]
    USRSETUP["percpu::init_percpu_reg(0)"]
    USRACCESS["CPU_ID.read_current()"]
end

AREAS --> SCRIPT
INTERFACE --> USRACCESS
SCRIPT --> SECTION
STORAGE --> SCRIPT
USRACCESS --> OFFSETS
USRACCESS --> REGISTER
USRINIT --> AREAS
USRSETUP --> REGISTER
USRVAR --> STORAGE
USRVAR --> WRAPPER
WRAPPER --> INTERFACE

Sources: README.md(L40 - L51)  percpu/tests/test_percpu.rs(L7 - L31)  percpu/test_percpu.x(L6 - L14) 

Quick Start Example

The following table summarizes the minimal steps required to integrate percpu into a project:

StepActionCode ExamplePurpose
1Add dependencypercpu = "0.1"in Cargo.tomlEnable percpu macros and runtime
2Configure linkerAdd.percpusection to linker scriptReserve memory for per-CPU data
3Define variables#[percpu::def_percpu] static VAR: Type = init;Declare per-CPU storage
4Initialize systempercpu::init();Allocate per-CPU memory areas
5Setup registerspercpu::init_percpu_reg(cpu_id);Configure architecture registers
6Access dataVAR.read_current(),VAR.write_current(value)Use per-CPU variables

Here's a complete minimal example demonstrating the basic workflow:

// In your source code
#[percpu::def_percpu]
static CPU_COUNTER: usize = 0;

fn main() {
    // Initialize the per-CPU system
    percpu::init();
    
    // Configure per-CPU register for CPU 0
    percpu::init_percpu_reg(0);
    
    // Access per-CPU data
    println!("Initial value: {}", CPU_COUNTER.read_current()); // prints "0"
    CPU_COUNTER.write_current(42);
    println!("Updated value: {}", CPU_COUNTER.read_current()); // prints "42"
}

And the corresponding linker script addition:

. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
    _percpu_load_start = .;
    *(.percpu .percpu.*)
    _percpu_load_end = .;
    . = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;

Sources: README.md(L39 - L51)  README.md(L54 - L67)  percpu/test_percpu.x(L1 - L16) 

Architecture-Specific Considerations

The percpu system supports multiple architectures with different per-CPU register mechanisms. The following table shows the supported platforms and their register usage:

ArchitecturePer-CPU RegisterUsage Notes
x86_64GS_BASEStandard x86_64 per-CPU mechanism
AArch64TPIDR_EL1/TPIDR_EL2EL1 by default, EL2 witharm-el2feature
RISC-VgpGlobal pointer register (non-standard usage)
LoongArch$r21Architecture-specific register

The system automatically generates appropriate assembly code for each architecture during macro expansion. No manual architecture-specific code is required in user applications.

Sources: README.md(L19 - L35) 

Feature Configuration Overview

The percpu crate provides several feature flags to adapt to different deployment scenarios:

  • sp-naive: Single-processor mode using global variables instead of per-CPU mechanisms
  • preempt: Preemption-safe mode with automatic guard integration
  • arm-el2: Hypervisor mode support for AArch64 using EL2 registers

Choose features based on your target environment. For single-core systems, use sp-naive. For preemptible kernels, enable preempt. For ARM hypervisors, add arm-el2.

Sources: README.md(L69 - L79) 

Next Steps

After completing the basic integration:

  1. Detailed Setup: See Installation and Setup for comprehensive dependency management, build configuration, and linker script integration
  2. Advanced Usage: See Basic Usage Examples for complex data types, remote CPU access, and safety patterns
  3. Feature Configuration: See Feature Flags Configuration for choosing appropriate features for your deployment scenario
  4. Architecture Details: See Cross-Platform Abstraction for understanding platform-specific implementation details
  5. Safety Guidelines: See Safety and Preemption for proper usage patterns and avoiding common pitfalls

Sources: README.md percpu/tests/test_percpu.rs

Installation and Setup

Relevant source files

This document covers the installation and configuration requirements for integrating the percpu crate into your project. It focuses on dependency management, linker script configuration, and build system setup. For detailed usage examples, see Basic Usage Examples. For feature flag details, see Feature Flags Configuration. For understanding the underlying architecture, see Memory Layout and Initialization.

Overview of Setup Requirements

The percpu crate requires several configuration steps beyond typical Rust dependency management due to its low-level nature and custom memory layout requirements.

flowchart TD
CARGO["Add percpu to Cargo.toml"]
LINKER["Configure Linker Script"]
FEATURES["Select Feature Flags"]
BUILD["Build System Integration"]
INIT["Runtime Initialization"]
DEPS["Dependencies Added"]
MEMORY["Memory Layout"]
CONFIG["Platform Config"]
COMPILE["Compilation Setup"]
RUNTIME["Runtime Ready"]

BUILD --> COMPILE
BUILD --> INIT
CARGO --> DEPS
CARGO --> LINKER
FEATURES --> BUILD
FEATURES --> CONFIG
INIT --> RUNTIME
LINKER --> FEATURES
LINKER --> MEMORY

Setup Flow: Each step depends on the previous ones being completed correctly

Sources: README.md(L1 - L80) 

Adding Dependencies

Add the percpu crate to your Cargo.toml dependencies section:

[dependencies]
percpu = "0.1"
percpu_macros = "0.1"  # Optional: only if using macros directly

The core percpu crate provides runtime functionality, while percpu_macros contains the procedural macros (automatically included by the main crate).

CratePurposeRequired
percpuRuntime implementation, initialization functionsYes
percpu_macrosdef_percpumacro implementationIncluded automatically

Sources: README.md(L3 - L4) 

Linker Script Configuration

The percpu crate requires manual linker script modification to create the .percpu section. This is a mandatory step that cannot be automated.

Required Linker Script Sections

Add these sections to your linker script before the end of your existing sections:

flowchart TD
ALIGN["ALIGN(4K)"]
START["_percpu_start = ."]
END["_percpu_end = _percpu_start + SIZEOF(.percpu)"]
SECTION[".percpu 0x0 (NOLOAD) : AT(_percpu_start)"]
LOAD_START["_percpu_load_start = ."]
CONTENT["(.percpu .percpu.)"]
LOAD_END["_percpu_load_end = ."]
SIZE[". = _percpu_load_start + ALIGN(64) * CPU_NUM"]
FINAL[". = _percpu_end"]

ALIGN --> START
CONTENT --> LOAD_END
END --> SECTION
LOAD_END --> SIZE
LOAD_START --> CONTENT
SECTION --> LOAD_START
SIZE --> FINAL
START --> END

Linker Script Memory Layout: The .percpu section layout with required symbols

The complete linker script addition from percpu/test_percpu.x(L3 - L16) :

  • _percpu_start: Marks the beginning of the per-CPU template area
  • _percpu_end: Marks the end of all per-CPU areas
  • _percpu_load_start / _percpu_load_end: Template data boundaries
  • CPU_NUM: Must be defined as the maximum number of CPUs
  • ALIGN(64): Ensures proper cache line alignment for each CPU area

Example Integration

For a complete example, see the test linker script structure in percpu/test_percpu.x(L1 - L17) :

CPU_NUM = 4;

SECTIONS {
    /* Your existing sections */
    
    . = ALIGN(4K);
    _percpu_start = .;
    _percpu_end = _percpu_start + SIZEOF(.percpu);
    .percpu 0x0 (NOLOAD) : AT(_percpu_start) {
        _percpu_load_start = .;
        *(.percpu .percpu.*)
        _percpu_load_end = .;
        . = _percpu_load_start + ALIGN(64) * CPU_NUM;
    }
    . = _percpu_end;
}
INSERT AFTER .bss;

Sources: README.md(L54 - L67)  percpu/test_percpu.x(L1 - L17) 

Feature Flags Configuration

Configure feature flags in Cargo.toml based on your target environment:

FeatureUse CaseEffect
sp-naiveSingle-core systemsUses global variables instead of per-CPU registers
preemptPreemptible kernelsIntegrates withkernel_guardfor atomic operations
arm-el2ARM hypervisorsUsesTPIDR_EL2instead ofTPIDR_EL1

Feature Selection Matrix


Feature Selection Logic: Choose features based on your runtime environment

Example Configurations

# Single-core embedded system
[dependencies]
percpu = { version = "0.1", features = ["sp-naive"] }

# Multi-core preemptible kernel
[dependencies]
percpu = { version = "0.1", features = ["preempt"] }

# ARM hypervisor at EL2
[dependencies]
percpu = { version = "0.1", features = ["arm-el2"] }

# ARM hypervisor with preemption
[dependencies]
percpu = { version = "0.1", features = ["preempt", "arm-el2"] }

Sources: README.md(L69 - L79) 

Build System Integration

Conditional Build Configuration

The build system may require additional configuration for certain targets. The percpu/build.rs(L1 - L9)  shows platform-specific build requirements:


Build Configuration Logic: Platform-specific build steps for Linux targets

Required Build Steps

For Linux targets without sp-naive feature:

  • -no-pie: Disables position-independent executable
  • -T{script}: Specifies custom linker script path

The build system automatically handles:

  • Linker script path resolution
  • Test-specific linker arguments
  • Feature flag conditional compilation

Sources: percpu/build.rs(L1 - L9) 

Runtime Initialization

After build configuration, initialize the percpu system at runtime:

flowchart TD
START["Program Start"]
INIT["percpu::init()"]
REG_INIT["percpu::init_percpu_reg(cpu_id)"]
ACCESS["Per-CPU data access ready"]
AREAS["Per-CPU memory areas"]
TEMPLATE[".percpu section template"]
REGISTERS["Architecture-specific registers"]
X86["x86_64: GS_BASE"]
ARM["AArch64: TPIDR_ELx"]
RISCV["RISC-V: gp"]
LOONG["LoongArch: $r21"]

INIT --> AREAS
INIT --> REG_INIT
INIT --> TEMPLATE
REGISTERS --> ARM
REGISTERS --> LOONG
REGISTERS --> RISCV
REGISTERS --> X86
REG_INIT --> ACCESS
REG_INIT --> REGISTERS
START --> INIT

Initialization Sequence: Runtime setup steps for per-CPU data access

Initialization Functions

FunctionPurposeWhen to Call
percpu::init()Allocate and initialize per-CPU areasOnce during system startup
percpu::init_percpu_reg(cpu_id)Configure per-CPU registerOnce per CPU during CPU initialization

Basic Initialization Example

// System initialization
percpu::init();

// Per-CPU initialization (call for each CPU)
for cpu_id in 0..num_cpus {
    // Switch to CPU context, then:
    percpu::init_percpu_reg(cpu_id);
}

Sources: README.md(L43 - L46) 

Verification

Verify your setup by testing basic per-CPU variable access:

#[percpu::def_percpu]
static TEST_VAR: usize = 42;

// After initialization
assert_eq!(TEST_VAR.read_current(), 42);
TEST_VAR.write_current(100);
assert_eq!(TEST_VAR.read_current(), 100);

Common Setup Issues

IssueSymptomSolution
Missing linker scriptLink errors about.percpusectionAdd required linker script sections
Wrong feature flagsRuntime panics or incorrect behaviorReview target environment and feature selection
Missing initializationSegmentation faults on accessCallpercpu::init()before any per-CPU access
Register not setWrong CPU data accessedCallpercpu::init_percpu_reg()for each CPU

Sources: README.md(L39 - L52) 

Basic Usage Examples

Relevant source files

This section demonstrates the fundamental patterns for defining and accessing per-CPU variables using the def_percpu macro. It covers the essential initialization sequence, current CPU data access, and remote CPU data manipulation techniques. For detailed information about system architecture and memory layout, see Memory Layout and Initialization. For comprehensive API documentation, see def_percpu Macro.

Variable Definition and Initialization

The core workflow begins with defining per-CPU variables using the #[def_percpu] attribute macro, followed by system initialization to set up memory areas and CPU registers.

Basic Variable Definition

Per-CPU variables are defined using the #[percpu::def_percpu] attribute on static variables. The macro supports various primitive types and custom structures:

#[percpu::def_percpu]
static CPU_ID: usize = 0;

#[percpu::def_percpu]
static COUNTER: u64 = 0;

#[percpu::def_percpu]
static FLAG: bool = false;

Sources: README.md(L40 - L41)  percpu/tests/test_percpu.rs(L7 - L23) 

System Initialization Sequence

Before accessing per-CPU data, the system must be initialized with percpu::init() and per-CPU registers configured with percpu::init_percpu_reg():

// Initialize per-CPU data areas
percpu::init();

// Set the thread pointer register to per-CPU data area 0
percpu::init_percpu_reg(0);

Generated Code Structure

flowchart TD
subgraph subGraph2["Generated Methods"]
    OFFSET["offset() -> usize"]
    CURRENT_PTR["current_ptr() -> *const T"]
    READ["read_current() -> T"]
    WRITE["write_current(val: T)"]
    WITH["with_current(f: F)"]
    REMOTE_PTR["remote_ptr(cpu_id) -> *const T"]
end
subgraph subGraph1["Generated by def_percpu Macro"]
    INNER["__PERCPU_CPU_IDin .percpu section"]
    WRAPPER["CPU_ID_WRAPPERstruct with methods"]
    PUBLIC["CPU_ID: CPU_ID_WRAPPERpublic static instance"]
end
subgraph subGraph0["User Code"]
    USERVAR["#[def_percpu]static CPU_ID: usize = 0"]
end

USERVAR --> INNER
USERVAR --> PUBLIC
USERVAR --> WRAPPER
WRAPPER --> CURRENT_PTR
WRAPPER --> OFFSET
WRAPPER --> READ
WRAPPER --> REMOTE_PTR
WRAPPER --> WITH
WRAPPER --> WRITE

Sources: percpu_macros/src/lib.rs(L88 - L89)  percpu_macros/src/lib.rs(L149 - L159)  percpu_macros/src/lib.rs(L161 - L249) 

Current CPU Data Access

The generated wrapper provides several methods for accessing per-CPU data on the current CPU. For primitive integer types, direct read/write methods are available.

Direct Read/Write Operations

For primitive types (bool, u8, u16, u32, u64, usize), the macro generates optimized read and write methods:

// Reading current CPU values
let value = CPU_ID.read_current();
let flag_state = FLAG.read_current();

// Writing to current CPU
CPU_ID.write_current(1);
FLAG.write_current(true);
COUNTER.write_current(0xdead_beef);

Sources: README.md(L48 - L51)  percpu/tests/test_percpu.rs(L71 - L77)  percpu_macros/src/lib.rs(L129 - L139) 

Safe Reference-Based Access

For complex data types and when multiple operations are needed, use the with_current() method which provides safe mutable access:

#[percpu::def_percpu]
static STATS: Struct = Struct { foo: 0, bar: 0 };

// Safe manipulation with automatic preemption handling
STATS.with_current(|stats| {
    stats.foo = 0x2333;
    stats.bar = 100;
});

Sources: percpu/tests/test_percpu.rs(L78 - L81)  percpu_macros/src/lib.rs(L201 - L207) 

Unsafe Raw Access

For performance-critical scenarios where preemption is manually managed, raw access methods are available:

unsafe {
    // Caller must ensure preemption is disabled
    let ptr = CPU_ID.current_ptr();
    let value = CPU_ID.read_current_raw();
    CPU_ID.write_current_raw(42);
}

Sources: percpu_macros/src/lib.rs(L113 - L125)  percpu_macros/src/lib.rs(L174 - L197) 

Remote CPU Data Access

The system supports accessing per-CPU data from other CPUs using CPU ID-based addressing. This is essential for cross-CPU coordination and debugging.

Remote Pointer Access

unsafe {
    // Access data on CPU 1 from any CPU
    let remote_ptr = CPU_ID.remote_ptr(1);
    let remote_value = *remote_ptr;
    
    // Modify remote CPU data
    let remote_mut_ref = COUNTER.remote_ref_mut_raw(1);
    *remote_mut_ref = 0xfeed_feed_feed_feed;
}

Sources: percpu/tests/test_percpu.rs(L110 - L122)  percpu_macros/src/lib.rs(L217 - L246) 

CPU Context Switching

The per-CPU register can be updated to change the current CPU context:

unsafe {
    // Switch to CPU 1's context
    percpu::write_percpu_reg(percpu::percpu_area_base(1));
    
    // Now read_current() accesses CPU 1's data
    let cpu1_value = CPU_ID.read_current();
}

Sources: percpu/tests/test_percpu.rs(L139 - L154) 

Data Type Support Matrix

The def_percpu macro handles different data types with varying levels of optimization:

Data TypeDirect Read/Writewith_current()Remote AccessNotes
boolOptimized methods generated
u8,u16,u32Optimized methods generated
u64,usizeOptimized methods generated
Custom structsReference-based access only
ArraysReference-based access only

Per-CPU Data Type Access Patterns

flowchart TD
subgraph subGraph2["Access Methods"]
    DIRECT["read_current()write_current()"]
    WITH["with_current(|data| ...)"]
    RAW["current_ptr()current_ref_raw()"]
    REMOTE["remote_ptr(cpu_id)remote_ref_raw(cpu_id)"]
end
subgraph subGraph1["Complex Types"]
    COMPLEX["Custom structsArrays, etc."]
end
subgraph subGraph0["Primitive Types"]
    PRIM["bool, u8, u16, u32u64, usize"]
end

COMPLEX --> DIRECT
COMPLEX --> RAW
COMPLEX --> REMOTE
COMPLEX --> WITH
PRIM --> DIRECT
PRIM --> RAW
PRIM --> REMOTE
PRIM --> WITH

Sources: percpu_macros/src/lib.rs(L91 - L93)  percpu_macros/src/lib.rs(L100 - L145)  percpu/tests/test_percpu.rs(L25 - L31) 

Understanding Generated Methods

Each per-CPU variable generates a wrapper struct with a comprehensive set of access methods:

Core Methods (All Types)

  • offset() -> usize - Returns variable offset within per-CPU area
  • current_ptr() -> *const T - Raw pointer to current CPU's data
  • with_current<F>(f: F) - Safe closure-based access with preemption handling
  • remote_ptr(cpu_id: usize) -> *const T - Raw pointer to specified CPU's data

Primitive Type Methods

For bool, u8, u16, u32, u64, usize only:

  • read_current() -> T - Direct value read with preemption handling
  • write_current(val: T) - Direct value write with preemption handling
  • read_current_raw() -> T - Unsafe direct read (no preemption handling)
  • write_current_raw(val: T) - Unsafe direct write (no preemption handling)

Feature-Dependent Behavior

The generated code adapts based on enabled cargo features:

  • preempt feature: Adds NoPreemptGuard to safe methods
  • sp-naive feature: Uses global variables instead of per-CPU registers
  • arm-el2 feature: Uses TPIDR_EL2 instead of TPIDR_EL1 on AArch64

Sources: percpu_macros/src/lib.rs(L94 - L98)  README.md(L69 - L79) 

Complete Usage Example

This example demonstrates the full workflow from definition to access:

use percpu::*;

// Define various per-CPU variables
#[def_percpu]
static CPU_ID: usize = 0;

#[def_percpu]
static COUNTER: u64 = 0;

struct CpuStats {
    interrupts: u64,
    context_switches: u64,
}

#[def_percpu]
static STATS: CpuStats = CpuStats {
    interrupts: 0,
    context_switches: 0,
};

fn main() {
    // Initialize the per-CPU system
    percpu::init();
    percpu::init_percpu_reg(0);
    
    // Access current CPU data
    CPU_ID.write_current(0);
    COUNTER.write_current(100);
    
    STATS.with_current(|stats| {
        stats.interrupts += 1;
        stats.context_switches += 1;
    });
    
    // Read values
    println!("CPU ID: {}", CPU_ID.read_current());
    println!("Counter: {}", COUNTER.read_current());
    
    // Access remote CPU data (unsafe)
    unsafe {
        *COUNTER.remote_ref_mut_raw(1) = 200;
        println!("CPU 1 counter: {}", *COUNTER.remote_ptr(1));
    }
}

Sources: percpu/tests/test_percpu.rs(L34 - L105)  README.md(L39 - L52) 

Feature Flags Configuration

Relevant source files

This document explains how to configure the percpu crate's feature flags to optimize per-CPU data management for different system architectures and use cases. It covers the three main feature flags (sp-naive, preempt, arm-el2) and their interaction with dependencies and code generation.

For basic usage examples and integration steps, see Basic Usage Examples. For detailed implementation specifics of each feature, see Naive Implementation.

Overview of Feature Flags

The percpu crate provides three optional features that adapt the per-CPU data system to different operational environments and architectural requirements.

Feature Flag Architecture

flowchart TD
subgraph subGraph2["Dependencies Activated"]
    KERNEL_GUARD["kernel_guard cratePreemption control"]
    MACRO_FLAGS["percpu_macros featuresCode generation control"]
end
subgraph subGraph1["Generated Code Variants"]
    MULTI_CPU["Multi-CPU ImplementationArchitecture-specific registers"]
    GLOBAL_VAR["Global Variable ImplementationNo per-CPU isolation"]
    GUARDED["Preemption-Safe ImplementationNoPreemptGuard wrapper"]
    EL2_IMPL["EL2 Register ImplementationTPIDR_EL2 usage"]
end
subgraph subGraph0["Feature Configuration"]
    DEFAULT["default = []Standard multi-CPU mode"]
    SP_NAIVE["sp-naiveSingle-core fallback"]
    PREEMPT["preemptPreemption safety"]
    ARM_EL2["arm-el2Hypervisor mode"]
end

ARM_EL2 --> EL2_IMPL
ARM_EL2 --> MACRO_FLAGS
DEFAULT --> MULTI_CPU
PREEMPT --> GUARDED
PREEMPT --> KERNEL_GUARD
PREEMPT --> MACRO_FLAGS
SP_NAIVE --> GLOBAL_VAR
SP_NAIVE --> MACRO_FLAGS

Sources: percpu/Cargo.toml(L15 - L25)  percpu_macros/Cargo.toml(L15 - L25)  README.md(L69 - L79) 

Feature Flag Descriptions

sp-naive Feature

The sp-naive feature transforms per-CPU variables into standard global variables, eliminating per-CPU isolation for single-core systems.

AspectStandard Modesp-naive Mode
Variable StoragePer-CPU memory areasGlobal variables
Register UsageArchitecture-specific (GS_BASE, TPIDR_EL1, etc.)None
Memory Layout.percpu section with CPU multiplicationStandard .data/.bss sections
Access MethodCPU register + offset calculationDirect global access
Initializationpercpu::init()andpercpu::init_percpu_reg()Standard global initialization

Configuration:

[dependencies]
percpu = { version = "0.2", features = ["sp-naive"] }

Sources: percpu/Cargo.toml(L18 - L19)  percpu_macros/Cargo.toml(L18 - L19)  README.md(L71 - L73) 

preempt Feature

The preempt feature enables preemption safety by integrating with the kernel_guard crate to prevent data corruption during per-CPU access.

Feature Dependencies Chain

flowchart TD
subgraph subGraph3["Generated Access Methods"]
    READ_CURRENT["read_current()Preemption-safe read"]
    WRITE_CURRENT["write_current()Preemption-safe write"]
    NO_PREEMPT_GUARD["NoPreemptGuardRAII guard"]
end
subgraph subGraph2["percpu_macros Crate"]
    MACROS_PREEMPT["preempt featurepercpu_macros/Cargo.toml:22"]
    CODE_GEN["Code GenerationNoPreemptGuard integration"]
end
subgraph subGraph1["percpu Crate"]
    PERCPU_PREEMPT["preempt featurepercpu/Cargo.toml:22"]
    KERNEL_GUARD_DEP["kernel_guard dependencyConditionally enabled"]
end
subgraph subGraph0["User Configuration"]
    USER_TOML["Cargo.tomlfeatures = ['preempt']"]
end

CODE_GEN --> NO_PREEMPT_GUARD
CODE_GEN --> READ_CURRENT
CODE_GEN --> WRITE_CURRENT
KERNEL_GUARD_DEP --> NO_PREEMPT_GUARD
MACROS_PREEMPT --> CODE_GEN
PERCPU_PREEMPT --> KERNEL_GUARD_DEP
PERCPU_PREEMPT --> MACROS_PREEMPT
USER_TOML --> PERCPU_PREEMPT

Configuration:

[dependencies]
percpu = { version = "0.2", features = ["preempt"] }

Sources: percpu/Cargo.toml(L21 - L22)  percpu_macros/Cargo.toml(L21 - L22)  README.md(L74 - L76) 

arm-el2 Feature

The arm-el2 feature configures AArch64 systems to use the EL2 (Exception Level 2) thread pointer register for hypervisor environments.

Register UsageDefault (arm-el2 disabled)arm-el2 enabled
AArch64 RegisterTPIDR_EL1TPIDR_EL2
Privilege LevelEL1 (kernel mode)EL2 (hypervisor mode)
Use CaseStandard kernelHypervisor/VMM
Assembly Instructionsmrs/msrwithTPIDR_EL1mrs/msrwithTPIDR_EL2

Configuration:

[dependencies]
percpu = { version = "0.2", features = ["arm-el2"] }

Sources: percpu/Cargo.toml(L24 - L25)  percpu_macros/Cargo.toml(L24 - L25)  README.md(L33 - L35)  README.md(L77 - L79) 

Feature Combination Matrix

Compatible Feature Combinations

flowchart TD
subgraph subGraph1["Architecture Applicability"]
    X86_COMPAT["x86_64 CompatibleGS_BASE register"]
    ARM_COMPAT["AArch64 CompatibleTPIDR_ELx registers"]
    RISCV_COMPAT["RISC-V Compatiblegp register"]
    LOONG_COMPAT["LoongArch Compatible$r21 register"]
end
subgraph subGraph0["Valid Configurations"]
    DEFAULT_ONLY["DefaultMulti-CPU, no preemption"]
    SP_NAIVE_ONLY["sp-naiveSingle-core system"]
    PREEMPT_ONLY["preemptMulti-CPU with preemption"]
    ARM_EL2_ONLY["arm-el2AArch64 hypervisor"]
    PREEMPT_ARM["preempt + arm-el2Hypervisor with preemption"]
    SP_NAIVE_PREEMPT["sp-naive + preemptSingle-core with preemption"]
    SP_NAIVE_ARM["sp-naive + arm-el2Single-core hypervisor"]
    ALL_FEATURES["sp-naive + preempt + arm-el2Single-core hypervisor with preemption"]
end

ARM_EL2_ONLY --> ARM_COMPAT
DEFAULT_ONLY --> ARM_COMPAT
DEFAULT_ONLY --> LOONG_COMPAT
DEFAULT_ONLY --> RISCV_COMPAT
DEFAULT_ONLY --> X86_COMPAT
PREEMPT_ARM --> ARM_COMPAT
PREEMPT_ONLY --> ARM_COMPAT
PREEMPT_ONLY --> LOONG_COMPAT
PREEMPT_ONLY --> RISCV_COMPAT
PREEMPT_ONLY --> X86_COMPAT
SP_NAIVE_ONLY --> ARM_COMPAT
SP_NAIVE_ONLY --> LOONG_COMPAT
SP_NAIVE_ONLY --> RISCV_COMPAT
SP_NAIVE_ONLY --> X86_COMPAT

Sources: percpu/Cargo.toml(L15 - L25)  percpu_macros/Cargo.toml(L15 - L25) 

Configuration Examples

Bare Metal Single-Core System

For embedded systems or single-core environments:

[dependencies]
percpu = { version = "0.2", features = ["sp-naive"] }

This configuration eliminates per-CPU overhead and uses simple global variables.

Multi-Core Preemptible Kernel

For operating system kernels with preemptive scheduling:

[dependencies]
percpu = { version = "0.2", features = ["preempt"] }

This enables preemption safety with NoPreemptGuard wrappers around access operations.

AArch64 Hypervisor

For hypervisors running at EL2 privilege level:

[dependencies]
percpu = { version = "0.2", features = ["arm-el2"] }

This uses TPIDR_EL2 instead of TPIDR_EL1 for per-CPU base address storage.

Multi-Core Hypervisor with Preemption

For complex hypervisor environments:

[dependencies]
percpu = { version = "0.2", features = ["preempt", "arm-el2"] }

This combines EL2 register usage with preemption safety mechanisms.

Conditional Compilation Impact

Feature-Dependent Code Paths

flowchart TD
subgraph subGraph2["Generated Code Variants"]
    GLOBAL_IMPL["Global Variable ImplementationDirect access"]
    PERCPU_IMPL["Per-CPU ImplementationRegister-based access"]
    GUARDED_IMPL["Guarded ImplementationNoPreemptGuard wrapper"]
    EL2_IMPL["EL2 ImplementationTPIDR_EL2 usage"]
end
subgraph subGraph1["Feature Detection"]
    CFG_SP_NAIVE["#[cfg(feature = 'sp-naive')]"]
    CFG_PREEMPT["#[cfg(feature = 'preempt')]"]
    CFG_ARM_EL2["#[cfg(feature = 'arm-el2')]"]
end
subgraph subGraph0["Macro Expansion"]
    DEF_PERCPU["#[def_percpu]static VAR: T = init;"]
end

CFG_ARM_EL2 --> EL2_IMPL
CFG_PREEMPT --> GUARDED_IMPL
CFG_SP_NAIVE --> GLOBAL_IMPL
CFG_SP_NAIVE --> PERCPU_IMPL
DEF_PERCPU --> CFG_ARM_EL2
DEF_PERCPU --> CFG_PREEMPT
DEF_PERCPU --> CFG_SP_NAIVE
PERCPU_IMPL --> EL2_IMPL
PERCPU_IMPL --> GUARDED_IMPL

Sources: percpu_macros/Cargo.toml(L15 - L25) 

Architecture-Specific Considerations

x86_64 Systems

All feature combinations are supported. The arm-el2 feature has no effect on x86_64 targets.

Register Used: GS_BASE (unless sp-naive is enabled)

AArch64 Systems

The arm-el2 feature specifically affects AArch64 compilation:

  • Default: Uses TPIDR_EL1 register
  • With arm-el2: Uses TPIDR_EL2 register

RISC-V Systems

The arm-el2 feature has no effect. Uses gp register for per-CPU base address.

LoongArch Systems

The arm-el2 feature has no effect. Uses $r21 register for per-CPU base address.

Sources: README.md(L19 - L36) 

Dependencies and Build Impact

Conditional Dependencies

The feature flags control which dependencies are included in the build:

FeatureAdditional DependenciesPurpose
sp-naiveNoneSimplifies to global variables
preemptkernel_guard = "0.1"ProvidesNoPreemptGuard
arm-el2NoneOnly affects macro code generation

Build Optimization

When sp-naive is enabled, the generated code is significantly simpler and may result in better optimization for single-core systems since it eliminates the per-CPU indirection overhead.

Sources: percpu/Cargo.toml(L27 - L36) 

Architecture and Design

Relevant source files

This document provides a comprehensive overview of the percpu crate's system architecture, including memory layout strategies, cross-platform abstraction mechanisms, and the compile-time code generation pipeline. It focuses on the core design principles that enable efficient per-CPU data management across multiple architectures while maintaining a unified programming interface.

For implementation-specific details of individual architectures, see Architecture-Specific Code Generation. For basic usage patterns and examples, see Getting Started.

System Architecture Overview

The percpu system is built around a dual-crate architecture that separates compile-time code generation from runtime memory management. This design enables both high-performance per-CPU data access and flexible cross-platform support.

Core Architecture

flowchart TD
subgraph subGraph4["Hardware Registers"]
    X86_GS["x86_64: GS_BASE"]
    ARM_TPIDR["AArch64: TPIDR_ELx"]
    RISCV_GP["RISC-V: gp"]
    LOONG_R21["LoongArch: $r21"]
end
subgraph subGraph3["Memory Layout"]
    PERCPU_SECTION[".percpu section"]
    AREA_0["CPU 0 Data Area"]
    AREA_N["CPU N Data Area"]
end
subgraph subGraph2["percpu Runtime Crate"]
    INIT_FUNC["init()"]
    AREA_BASE["percpu_area_base()"]
    REG_FUNCS["read_percpu_reg()/write_percpu_reg()"]
    INIT_REG["init_percpu_reg()"]
end
subgraph subGraph1["percpu_macros Crate"]
    DEF_PERCPU["def_percpu macro"]
    ARCH_GEN["arch::gen_current_ptr()"]
    SYMBOL_OFFSET["percpu_symbol_offset!"]
end
subgraph subGraph0["User Code Layer"]
    USER_VAR["#[def_percpu] static VAR: T"]
    USER_ACCESS["VAR.read_current()"]
    USER_INIT["percpu::init()"]
end

ARCH_GEN --> REG_FUNCS
DEF_PERCPU --> PERCPU_SECTION
INIT_FUNC --> AREA_0
INIT_FUNC --> AREA_BASE
INIT_FUNC --> AREA_N
INIT_REG --> ARM_TPIDR
INIT_REG --> LOONG_R21
INIT_REG --> RISCV_GP
INIT_REG --> X86_GS
REG_FUNCS --> ARM_TPIDR
REG_FUNCS --> LOONG_R21
REG_FUNCS --> RISCV_GP
REG_FUNCS --> X86_GS
SYMBOL_OFFSET --> AREA_BASE
USER_ACCESS --> ARCH_GEN
USER_INIT --> INIT_FUNC
USER_VAR --> DEF_PERCPU

Sources: README.md(L9 - L17)  percpu/src/imp.rs(L1 - L179)  percpu_macros/src/arch.rs(L1 - L264) 

Component Responsibilities

ComponentPrimary ResponsibilityKey Functions
percpu_macrosCompile-time code generationdef_percpu,gen_current_ptr,gen_read_current_raw
percpuruntimeMemory management and register accessinit,percpu_area_base,read_percpu_reg
Linker integrationMemory layout definition_percpu_start,_percpu_end,.percpusection
Architecture abstractionPlatform-specific register handlingwrite_percpu_reg,init_percpu_reg

Sources: percpu/src/imp.rs(L46 - L86)  percpu_macros/src/arch.rs(L54 - L88) 

Memory Layout and Initialization Architecture

The percpu system implements a template-based memory layout where a single .percpu section in the binary serves as a template that gets replicated for each CPU at runtime.

Memory Organization

flowchart TD
subgraph subGraph5["Initialization Process"]
    INIT_START["init()"]
    SIZE_CALC["percpu_area_size()"]
    NUM_CALC["percpu_area_num()"]
    COPY_LOOP["copy_nonoverlapping loop"]
end
subgraph subGraph4["Runtime Memory Areas"]
    BASE_CALC["percpu_area_base(cpu_id)"]
    subgraph subGraph3["CPU N Area"]
        CPUN_BASE["Base: _percpu_start + N * align_up_64(size)"]
        CPUN_VAR1["Variable 1 (copy)"]
        CPUN_VAR2["Variable 2 (copy)"]
    end
    subgraph subGraph2["CPU 1 Area"]
        CPU1_BASE["Base: _percpu_start + align_up_64(size)"]
        CPU1_VAR1["Variable 1 (copy)"]
        CPU1_VAR2["Variable 2 (copy)"]
    end
    subgraph subGraph1["CPU 0 Area"]
        CPU0_BASE["Base: _percpu_start"]
        CPU0_VAR1["Variable 1"]
        CPU0_VAR2["Variable 2"]
    end
end
subgraph subGraph0["Binary Layout"]
    PERCPU_TEMPLATE[".percpu Section Template"]
    LOAD_START["_percpu_load_start"]
    LOAD_END["_percpu_load_end"]
end

BASE_CALC --> CPU0_BASE
BASE_CALC --> CPU1_BASE
BASE_CALC --> CPUN_BASE
COPY_LOOP --> CPU1_VAR1
COPY_LOOP --> CPU1_VAR2
COPY_LOOP --> CPUN_VAR1
COPY_LOOP --> CPUN_VAR2
INIT_START --> COPY_LOOP
INIT_START --> NUM_CALC
INIT_START --> SIZE_CALC
PERCPU_TEMPLATE --> CPU0_VAR1
PERCPU_TEMPLATE --> CPU0_VAR2

Sources: percpu/src/imp.rs(L46 - L86)  percpu/src/imp.rs(L21 - L44)  README.md(L54 - L67) 

Initialization Sequence

The initialization process follows a specific sequence to set up per-CPU memory areas:

  1. Size Calculation: The percpu_area_size() function calculates the template size using linker symbols percpu/src/imp.rs(L25 - L30) 
  2. Area Allocation: percpu_area_num() determines how many CPU areas can fit in the reserved space percpu/src/imp.rs(L21 - L23) 
  3. Template Copying: The init() function copies the template data to each CPU's area percpu/src/imp.rs(L76 - L84) 
  4. Alignment: Each area is aligned to 64-byte boundaries using align_up_64() percpu/src/imp.rs(L5 - L8) 

Sources: percpu/src/imp.rs(L46 - L86) 

Cross-Platform Register Abstraction

The system abstracts different CPU architectures' per-CPU register mechanisms through a unified interface while generating architecture-specific assembly code.

Register Mapping Strategy

flowchart TD
subgraph subGraph3["Unified Interface"]
    READ_REG["read_percpu_reg()"]
    WRITE_REG["write_percpu_reg()"]
    INIT_REG["init_percpu_reg()"]
end
subgraph subGraph2["Access Pattern Generation"]
    X86_ASM["mov gs:[offset VAR]"]
    ARM_ASM["mrs reg, TPIDR_ELx"]
    RISCV_ASM["mv reg, gp + offset"]
    LOONG_ASM["move reg, $r21 + offset"]
end
subgraph subGraph1["Register Assignment"]
    X86_MAPPING["x86_64 → GS_BASE (MSR)"]
    ARM_MAPPING["aarch64 → TPIDR_EL1/EL2"]
    RISCV_MAPPING["riscv → gp register"]
    LOONG_MAPPING["loongarch64 → $r21"]
end
subgraph subGraph0["Architecture Detection"]
    TARGET_ARCH["cfg!(target_arch)"]
    FEATURE_FLAGS["cfg!(feature)"]
end

ARM_ASM --> READ_REG
ARM_MAPPING --> ARM_ASM
FEATURE_FLAGS --> ARM_MAPPING
LOONG_ASM --> READ_REG
LOONG_MAPPING --> LOONG_ASM
READ_REG --> WRITE_REG
RISCV_ASM --> READ_REG
RISCV_MAPPING --> RISCV_ASM
TARGET_ARCH --> ARM_MAPPING
TARGET_ARCH --> LOONG_MAPPING
TARGET_ARCH --> RISCV_MAPPING
TARGET_ARCH --> X86_MAPPING
WRITE_REG --> INIT_REG
X86_ASM --> READ_REG
X86_MAPPING --> X86_ASM

Sources: percpu/src/imp.rs(L91 - L168)  percpu_macros/src/arch.rs(L54 - L88) 

Platform-Specific Implementation Details

ArchitectureRegisterAssembly PatternSpecial Considerations
x86_64GS_BASEmov gs:[offset VAR]MSR access,SELF_PTRindirection
AArch64TPIDR_EL1/EL2mrs reg, TPIDR_ELxEL1/EL2 mode detection viaarm-el2feature
RISC-Vgpmv reg, gpUsesgpinstead oftpregister
LoongArch$r21move reg, $r21Direct register access

Sources: README.md(L19 - L36)  percpu/src/imp.rs(L94 - L156) 

Code Generation Pipeline Architecture

The macro expansion system transforms high-level per-CPU variable definitions into optimized, architecture-specific access code through a multi-stage pipeline.

Macro Expansion Workflow

flowchart TD
subgraph subGraph4["Assembly Output"]
    X86_OUTPUT["x86: mov gs:[offset VAR]"]
    ARM_OUTPUT["arm: mrs + movz"]
    RISCV_OUTPUT["riscv: lui + addi"]
    LOONG_OUTPUT["loong: lu12i.w + ori"]
end
subgraph subGraph3["Architecture Code Gen"]
    GEN_OFFSET["gen_offset()"]
    GEN_CURRENT_PTR["gen_current_ptr()"]
    GEN_READ_RAW["gen_read_current_raw()"]
    GEN_WRITE_RAW["gen_write_current_raw()"]
end
subgraph subGraph2["Method Generation"]
    OFFSET_METHOD["offset() -> usize"]
    CURRENT_PTR["current_ptr() -> *const T"]
    READ_CURRENT["read_current() -> T"]
    WRITE_CURRENT["write_current(val: T)"]
    REMOTE_ACCESS["remote_ptr(), remote_ref()"]
end
subgraph subGraph1["Symbol Generation"]
    INNER_SYMBOL["__PERCPU_VAR"]
    WRAPPER_TYPE["VAR_WRAPPER"]
    PUBLIC_STATIC["VAR: VAR_WRAPPER"]
end
subgraph subGraph0["Input Processing"]
    USER_DEF["#[def_percpu] static VAR: T = init"]
    SYN_PARSE["syn::parse_macro_input"]
    EXTRACT["Extract: name, type, initializer"]
end

CURRENT_PTR --> GEN_CURRENT_PTR
EXTRACT --> INNER_SYMBOL
EXTRACT --> PUBLIC_STATIC
EXTRACT --> WRAPPER_TYPE
GEN_CURRENT_PTR --> X86_OUTPUT
GEN_OFFSET --> ARM_OUTPUT
GEN_OFFSET --> LOONG_OUTPUT
GEN_OFFSET --> RISCV_OUTPUT
GEN_OFFSET --> X86_OUTPUT
GEN_READ_RAW --> X86_OUTPUT
GEN_WRITE_RAW --> X86_OUTPUT
OFFSET_METHOD --> GEN_OFFSET
READ_CURRENT --> GEN_READ_RAW
SYN_PARSE --> EXTRACT
USER_DEF --> SYN_PARSE
WRAPPER_TYPE --> CURRENT_PTR
WRAPPER_TYPE --> OFFSET_METHOD
WRAPPER_TYPE --> READ_CURRENT
WRAPPER_TYPE --> REMOTE_ACCESS
WRAPPER_TYPE --> WRITE_CURRENT
WRITE_CURRENT --> GEN_WRITE_RAW

Sources: percpu_macros/src/arch.rs(L15 - L50)  percpu_macros/src/arch.rs(L90 - L181)  percpu_macros/src/arch.rs(L183 - L263) 

Generated Code Structure

For each #[def_percpu] declaration, the macro generates a complete wrapper structure:

// Generated inner symbol (placed in .percpu section)
#[link_section = ".percpu"]
static __PERCPU_VAR: T = init;

// Generated wrapper type with access methods
struct VAR_WRAPPER;

impl VAR_WRAPPER {
    fn offset(&self) -> usize { /* architecture-specific assembly */ }
    fn current_ptr(&self) -> *const T { /* architecture-specific assembly */ }
    fn read_current(&self) -> T { /* optimized direct access */ }
    fn write_current(&self, val: T) { /* optimized direct access */ }
    // ... additional methods
}

// Public interface
static VAR: VAR_WRAPPER = VAR_WRAPPER;

Sources: percpu_macros/src/arch.rs(L54 - L88)  percpu_macros/src/arch.rs(L90 - L181) 

Optimization Strategies

The code generation pipeline implements several optimization strategies:

  1. Direct Assembly Generation: For primitive types, direct assembly instructions bypass pointer indirection percpu_macros/src/arch.rs(L90 - L181) 
  2. Architecture-Specific Instruction Selection: Each platform uses optimal instruction sequences percpu_macros/src/arch.rs(L94 - L181) 
  3. Register Constraint Optimization: Assembly constraints are tailored to each architecture's capabilities percpu_macros/src/arch.rs(L131 - L150) 
  4. Compile-Time Offset Calculation: Variable offsets are resolved at compile time using linker symbols percpu_macros/src/arch.rs(L15 - L50) 

Sources: percpu_macros/src/arch.rs(L90 - L263) 

Feature Configuration Architecture

The system supports multiple operational modes through Cargo feature flags that modify both compile-time code generation and runtime behavior.

Feature Flag Impact Matrix

FeatureCode Generation ChangesRuntime ChangesUse Case
sp-naiveGlobal variables instead of per-CPUNo register usageSingle-core systems
preemptNoPreemptGuardintegrationPreemption disable/enablePreemptible kernels
arm-el2TPIDR_EL2instead ofTPIDR_EL1Hypervisor register accessARM hypervisors

Sources: README.md(L69 - L79)  percpu_macros/src/arch.rs(L55 - L61) 

This architecture enables the percpu system to maintain high performance across diverse deployment scenarios while providing a consistent programming interface that abstracts away platform-specific complexities.

Memory Layout and Initialization

Relevant source files

This document explains the per-CPU memory layout structure, initialization process, and linker script integration in the percpu crate. It covers how per-CPU data areas are organized in memory, the template-based initialization process, and the relationship between linker script symbols and runtime address calculations.

For architecture-specific register management details, see Cross-Platform Abstraction. For low-level memory management internals, see Memory Management Internals.

Memory Layout Structure

The percpu system organizes per-CPU data using a template-based approach where all per-CPU variables are first collected into a single template area, then replicated for each CPU with proper alignment.

Per-CPU Area Organization

flowchart TD
subgraph subGraph2["Runtime Functions"]
    AREA_SIZE["percpu_area_size()"]
    AREA_NUM["percpu_area_num()"]
    AREA_BASE["percpu_area_base(cpu_id)"]
    ALIGN_UP["align_up_64()"]
end
subgraph subGraph1["Memory Layout"]
    TEMPLATE["Template Area (CPU 0)Size: percpu_area_size()"]
    CPU1_AREA["CPU 1 Area64-byte aligned"]
    CPU2_AREA["CPU 2 Area64-byte aligned"]
    CPUN_AREA["CPU N Area64-byte aligned"]
end
subgraph subGraph0["Linker Script Symbols"]
    START["_percpu_start"]
    END["_percpu_end"]
    LOAD_START["_percpu_load_start"]
    LOAD_END["_percpu_load_end"]
end

ALIGN_UP --> CPU1_AREA
AREA_BASE --> CPU1_AREA
AREA_NUM --> END
AREA_SIZE --> TEMPLATE
CPU1_AREA --> CPU2_AREA
CPU2_AREA --> CPUN_AREA
END --> CPUN_AREA
LOAD_END --> TEMPLATE
LOAD_START --> TEMPLATE
START --> TEMPLATE
TEMPLATE --> CPU1_AREA

The memory layout uses several key components:

ComponentPurposeImplementation
Template AreaContains initial values for all per-CPU variablesDefined by.percpusection content
Per-CPU AreasIndividual copies for each CPUCreated byinit()function
64-byte AlignmentCache line optimizationalign_up_64()function
Address CalculationRuntime pointer arithmeticpercpu_area_base()function

Sources: percpu/src/imp.rs(L5 - L44)  percpu/test_percpu.x(L1 - L17)  README.md(L54 - L67) 

Address Calculation Functions

The system provides several functions for calculating memory layout parameters:

flowchart TD
subgraph Calculations["Calculations"]
    CALC1["size = load_end - load_start"]
    CALC2["num = (end - start) / align_up_64(size)"]
    CALC3["base = start + cpu_id * align_up_64(size)"]
end
subgraph subGraph1["Linker Symbols"]
    SYMBOLS["_percpu_start_percpu_end_percpu_load_startUnsupported markdown: br        _percpu_load_end"]
end
subgraph subGraph0["Core Functions"]
    SIZE["percpu_area_size()Returns template size"]
    NUM["percpu_area_num()Returns CPU count"]
    BASE["percpu_area_base(cpu_id)Returns CPU area address"]
    ALIGN["align_up_64(val)64-byte alignment"]
end

ALIGN --> CALC2
ALIGN --> CALC3
BASE --> CALC3
NUM --> CALC2
SIZE --> CALC1
SYMBOLS --> BASE
SYMBOLS --> NUM
SYMBOLS --> SIZE

Sources: percpu/src/imp.rs(L20 - L44) 

Initialization Process

The initialization process occurs in two phases: global area setup via init() and per-CPU register configuration via init_percpu_reg().

Global Initialization Flow

flowchart TD
subgraph subGraph0["init() Function Flow"]
    START_INIT["init() called"]
    CHECK_INIT["Check IS_INIT atomic flag"]
    ALREADY_INIT["Already initialized?"]
    RETURN_ZERO["Return 0"]
    PLATFORM_CHECK["target_os == linux?"]
    ALLOC_LINUX["Allocate memory with std::alloc"]
    GET_PARAMS["Get base, size, num parameters"]
    SET_BASE["Set PERCPU_AREA_BASE"]
    COPY_LOOP["For each CPU 1..num"]
    COPY_DATA["copy_nonoverlapping(base, secondary_base, size)"]
    RETURN_NUM["Return number of areas"]
end

ALLOC_LINUX --> SET_BASE
ALREADY_INIT --> PLATFORM_CHECK
ALREADY_INIT --> RETURN_ZERO
CHECK_INIT --> ALREADY_INIT
COPY_DATA --> RETURN_NUM
COPY_LOOP --> COPY_DATA
GET_PARAMS --> COPY_LOOP
PLATFORM_CHECK --> ALLOC_LINUX
PLATFORM_CHECK --> GET_PARAMS
SET_BASE --> GET_PARAMS
START_INIT --> CHECK_INIT

The init() function performs these key operations:

  1. Initialization Guard: Uses IS_INIT atomic boolean to prevent multiple initialization percpu/src/imp.rs(L58 - L63) 
  2. Platform-Specific Allocation: On Linux, allocates memory dynamically; on bare metal, uses linker-provided memory percpu/src/imp.rs(L65 - L71) 
  3. Template Replication: Copies CPU 0's template data to all other CPU areas percpu/src/imp.rs(L76 - L84) 

Sources: percpu/src/imp.rs(L46 - L86) 

Per-CPU Register Initialization

flowchart TD
subgraph subGraph1["Architecture-Specific Registers"]
    X86_REG["x86_64: GS_BASE via MSR or syscall"]
    ARM_REG["aarch64: TPIDR_EL1/EL2 via msr"]
    RISCV_REG["riscv: gp register via mv"]
    LOONG_REG["loongarch64: $r21 via move"]
end
subgraph init_percpu_reg(cpu_id)["init_percpu_reg(cpu_id)"]
    CALC_BASE["percpu_area_base(cpu_id)"]
    WRITE_REG["write_percpu_reg(tp)"]
end

CALC_BASE --> WRITE_REG
WRITE_REG --> ARM_REG
WRITE_REG --> LOONG_REG
WRITE_REG --> RISCV_REG
WRITE_REG --> X86_REG

The init_percpu_reg() function configures the architecture-specific register to point to the appropriate per-CPU area base address.

Sources: percpu/src/imp.rs(L158 - L168)  percpu/src/imp.rs(L119 - L156) 

Linker Script Integration

The percpu system requires specific linker script modifications to reserve memory for per-CPU areas and define necessary symbols.

Required Linker Script Structure

The linker script must define a .percpu section with specific symbols and layout:

. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
    _percpu_load_start = .;
    *(.percpu .percpu.*)
    _percpu_load_end = .;
    . = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;

Symbol Relationships

flowchart TD
subgraph subGraph2["Memory Sections"]
    TEMPLATE_SEC[".percpu section contentAll per-CPU variables"]
    RESERVED_SPACE["Reserved spaceALIGN(64) * CPU_NUM"]
end
subgraph subGraph1["Runtime Usage"]
    AREA_SIZE_CALC["percpu_area_size()= load_end - load_start"]
    AREA_NUM_CALC["percpu_area_num()= (end - start) / align_up_64(size)"]
    BASE_CALC["percpu_area_base(cpu_id)= start + cpu_id * align_up_64(size)"]
end
subgraph subGraph0["Linker Script Symbols"]
    PERCPU_START["_percpu_startPhysical memory start"]
    PERCPU_END["_percpu_endPhysical memory end"]
    LOAD_START["_percpu_load_startTemplate data start"]
    LOAD_END["_percpu_load_endTemplate data end"]
end

LOAD_END --> AREA_SIZE_CALC
LOAD_START --> AREA_SIZE_CALC
PERCPU_END --> AREA_NUM_CALC
PERCPU_START --> AREA_NUM_CALC
PERCPU_START --> BASE_CALC
RESERVED_SPACE --> PERCPU_END
TEMPLATE_SEC --> LOAD_END
TEMPLATE_SEC --> LOAD_START

Key linker script requirements:

SymbolPurposeUsage in Runtime
_percpu_startBase address of all per-CPU areaspercpu_area_base()calculations
_percpu_endEnd of reserved per-CPU memoryArea count calculations
_percpu_load_startStart of template dataTemplate size calculations
_percpu_load_endEnd of template dataTemplate size calculations

Sources: percpu/test_percpu.x(L1 - L17)  README.md(L54 - L67)  percpu/src/imp.rs(L13 - L18) 

Platform-Specific Considerations

The initialization process varies based on the target platform:

Bare Metal (target_os = "none")

  • Uses linker-provided memory directly via _percpu_start symbol
  • Memory layout is fixed at compile time
  • No dynamic allocation required

Linux Userspace (target_os = "linux")

  • Dynamically allocates memory using std::alloc::alloc()
  • Stores base address in PERCPU_AREA_BASE static variable
  • Uses Once synchronization for thread-safe initialization

Memory Alignment Strategy

The system uses 64-byte alignment for performance optimization:

flowchart TD
subgraph Usage["Usage"]
    AREA_SPACING["Per-CPU area spacing"]
    CACHE_OPT["Cache line optimization"]
end
subgraph subGraph0["Alignment Function"]
    INPUT["Input: val (area size)"]
    CONST["SIZE_64BIT = 0x40"]
    CALC["(val + 0x3f) & !0x3f"]
    OUTPUT["Output: 64-byte aligned size"]
end

CALC --> OUTPUT
CONST --> CALC
INPUT --> CALC
OUTPUT --> AREA_SPACING
OUTPUT --> CACHE_OPT

The align_up_64() function ensures each per-CPU area starts on a 64-byte boundary to optimize cache performance and prevent false sharing between CPUs.

Sources: percpu/src/imp.rs(L5 - L8)  percpu/src/imp.rs(L36 - L44)  percpu/src/imp.rs(L65 - L71) 

Cross-Platform Abstraction

Relevant source files

This document explains how the percpu crate provides a unified interface across different CPU architectures while leveraging each platform's specific per-CPU register mechanisms. The abstraction layer enables portable per-CPU data management by generating architecture-specific assembly code at compile time and providing runtime functions that adapt to each platform's register conventions.

For details about memory layout and initialization processes, see Memory Layout and Initialization. For implementation specifics of the code generation process, see Architecture-Specific Code Generation.

Architecture Support Matrix

The percpu system supports four major CPU architectures, each using different registers for per-CPU data access:

ArchitecturePer-CPU RegisterRegister PurposeFeature Requirements
x86_64GS_BASEMSR-based segment registerMSR read/write access
AArch64TPIDR_EL1/EL2Thread pointer registerEL1 or EL2 privilege
RISC-VgpGlobal pointer registerCustom convention
LoongArch64$r21General purpose registerNative ISA support

Architecture Register Abstraction

flowchart TD
subgraph subGraph2["Runtime Selection"]
    CFGIF["cfg_if! macrotarget_arch conditions"]
    ASM["Inline Assemblycore::arch::asm!"]
end
subgraph subGraph1["Architecture-Specific Implementation"]
    X86["x86_64IA32_GS_BASE MSRrdmsr/wrmsr"]
    ARM["AArch64TPIDR_EL1/EL2mrs/msr"]
    RISCV["RISC-Vgp registermv instruction"]
    LOONG["LoongArch64$r21 registermove instruction"]
end
subgraph subGraph0["Unified API"]
    API["read_percpu_reg()write_percpu_reg()init_percpu_reg()"]
end

API --> CFGIF
ARM --> ASM
CFGIF --> ARM
CFGIF --> LOONG
CFGIF --> RISCV
CFGIF --> X86
LOONG --> ASM
RISCV --> ASM
X86 --> ASM

Sources: README.md(L19 - L36)  percpu/src/imp.rs(L88 - L156) 

Runtime Register Management

The runtime system provides architecture-agnostic functions that internally dispatch to platform-specific register access code. Each architecture implements the same interface using its native register access mechanisms.

Runtime Register Access Functions

flowchart TD
subgraph subGraph4["LoongArch64 Implementation"]
    LA_READ["move {}, $r21"]
    LA_WRITE["move $r21, {}"]
end
subgraph subGraph3["RISC-V Implementation"]
    RV_READ["mv {}, gp"]
    RV_WRITE["mv gp, {}"]
end
subgraph subGraph2["AArch64 Implementation"]
    ARM_READ["mrs TPIDR_EL1/EL2"]
    ARM_WRITE["msr TPIDR_EL1/EL2"]
end
subgraph subGraph1["x86_64 Implementation"]
    X86_READ["rdmsr(IA32_GS_BASE)or SELF_PTR.read_current_raw()"]
    X86_WRITE["wrmsr(IA32_GS_BASE)+ SELF_PTR.write_current_raw()"]
end
subgraph subGraph0["Public API"]
    READ["read_percpu_reg()"]
    WRITE["write_percpu_reg()"]
    INIT["init_percpu_reg()"]
end

INIT --> WRITE
READ --> ARM_READ
READ --> LA_READ
READ --> RV_READ
READ --> X86_READ
WRITE --> ARM_WRITE
WRITE --> LA_WRITE
WRITE --> RV_WRITE
WRITE --> X86_WRITE

Sources: percpu/src/imp.rs(L91 - L168) 

Compile-Time Code Generation Abstraction

The macro system generates architecture-specific assembly code for accessing per-CPU variables. Each architecture requires different instruction sequences and addressing modes, which are abstracted through the code generation functions in percpu_macros/src/arch.rs.

Code Generation Pipeline by Architecture

flowchart TD
subgraph subGraph5["LoongArch64 Assembly"]
    LA_OFFSET["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})"]
    LA_PTR["move {}, $r21"]
    LA_READ["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})ldx.hu {0}, {0}, $r21"]
    LA_WRITE["lu12i.w {0}, %abs_hi20({VAR})ori {0}, {0}, %abs_lo12({VAR})stx.h {1}, {0}, $r21"]
end
subgraph subGraph4["RISC-V Assembly"]
    RV_OFFSET["lui {0}, %hi({VAR})addi {0}, {0}, %lo({VAR})"]
    RV_PTR["mv {}, gp"]
    RV_READ["lui {0}, %hi({VAR})add {0}, {0}, gplhu {0}, %lo({VAR})({0})"]
    RV_WRITE["lui {0}, %hi({VAR})add {0}, {0}, gpsh {1}, %lo({VAR})({0})"]
end
subgraph subGraph3["AArch64 Assembly"]
    ARM_OFFSET["movz {0}, #:abs_g0_nc:{VAR}"]
    ARM_PTR["mrs {}, TPIDR_EL1/EL2"]
    ARM_FALLBACK["*self.current_ptr()"]
end
subgraph subGraph2["x86_64 Assembly"]
    X86_OFFSET["mov {0:e}, offset {VAR}"]
    X86_PTR["mov {0}, gs:[offset __PERCPU_SELF_PTR]add {0}, offset {VAR}"]
    X86_READ["mov {0:x}, word ptr gs:[offset {VAR}]"]
    X86_WRITE["mov word ptr gs:[offset {VAR}], {0:x}"]
end
subgraph subGraph1["Generation Functions"]
    OFFSET["gen_offset()"]
    CURRENTPTR["gen_current_ptr()"]
    READRAW["gen_read_current_raw()"]
    WRITERAW["gen_write_current_raw()"]
end
subgraph subGraph0["Macro Input"]
    DEFPERCPU["#[def_percpu]static VAR: T = init;"]
end

CURRENTPTR --> ARM_PTR
CURRENTPTR --> LA_PTR
CURRENTPTR --> RV_PTR
CURRENTPTR --> X86_PTR
DEFPERCPU --> CURRENTPTR
DEFPERCPU --> OFFSET
DEFPERCPU --> READRAW
DEFPERCPU --> WRITERAW
OFFSET --> ARM_OFFSET
OFFSET --> LA_OFFSET
OFFSET --> RV_OFFSET
OFFSET --> X86_OFFSET
READRAW --> ARM_FALLBACK
READRAW --> LA_READ
READRAW --> RV_READ
READRAW --> X86_READ
WRITERAW --> ARM_FALLBACK
WRITERAW --> LA_WRITE
WRITERAW --> RV_WRITE
WRITERAW --> X86_WRITE

Sources: percpu_macros/src/arch.rs(L15 - L263) 

Feature Flag Configuration

The system uses Cargo features to adapt behavior for different deployment scenarios and platform capabilities:

FeaturePurposeEffect on Abstraction
sp-naiveSingle-core systemsDisables per-CPU registers, uses global variables
preemptPreemptible kernelsAddsNoPreemptGuardintegration
arm-el2ARM hypervisorsUsesTPIDR_EL2instead ofTPIDR_EL1

The arm-el2 feature specifically affects the AArch64 register selection:

// From percpu_macros/src/arch.rs:55-61
let aarch64_tpidr = if cfg!(feature = "arm-el2") {
    "TPIDR_EL2"
} else {
    "TPIDR_EL1"
};

Feature-Based Configuration Flow

flowchart TD
subgraph subGraph2["Implementation Selection"]
    REGSEL["Register Selection"]
    GUARDSEL["Guard Selection"]
    FALLBACK["Fallback Mechanisms"]
end
subgraph subGraph1["Feature Effects"]
    SPNAIVE["sp-naive→ Global variables→ No register access"]
    PREEMPT["preempt→ NoPreemptGuard→ kernel_guard crate"]
    ARMEL2["arm-el2→ TPIDR_EL2→ Hypervisor mode"]
end
subgraph subGraph0["Build Configuration"]
    FEATURES["Cargo.toml[features]"]
    CFGMACROS["cfg!() macros"]
    CONDITIONAL["Conditional compilation"]
end

ARMEL2 --> REGSEL
CFGMACROS --> CONDITIONAL
CONDITIONAL --> ARMEL2
CONDITIONAL --> PREEMPT
CONDITIONAL --> SPNAIVE
FEATURES --> CFGMACROS
PREEMPT --> GUARDSEL
SPNAIVE --> FALLBACK

Sources: README.md(L69 - L79)  percpu_macros/src/arch.rs(L55 - L61)  percpu/src/imp.rs(L105 - L108) 

Platform-Specific Implementation Details

Each architecture has unique characteristics that the abstraction layer must accommodate:

x86_64 Specifics

  • Uses Model-Specific Register (MSR) IA32_GS_BASE for per-CPU base pointer
  • Requires special handling for Linux userspace via arch_prctl syscall
  • Maintains SELF_PTR variable in per-CPU area for efficient access
  • Supports direct GS-relative addressing in assembly

AArch64 Specifics

  • Uses Thread Pointer Identification Register (TPIDR_EL1/EL2)
  • EL1 for kernel mode, EL2 for hypervisor mode (controlled by arm-el2 feature)
  • Limited offset range requires base+offset addressing for larger structures
  • Falls back to pointer arithmetic for complex access patterns

RISC-V Specifics

  • Repurposes Global Pointer (gp) register for per-CPU base
  • Thread Pointer (tp) remains available for thread-local storage
  • Uses lui/addi instruction pairs for address calculation
  • Supports direct load/store with calculated offsets

LoongArch64 Specifics

  • Uses general-purpose register $r21 by convention
  • Native instruction support with lu12i.w/ori for address formation
  • Indexed load/store instructions for efficient per-CPU access
  • Full 32-bit offset support for large per-CPU areas

Sources: percpu/src/imp.rs(L94 - L156)  percpu_macros/src/arch.rs(L21 - L263)  README.md(L28 - L35) 

Code Generation Pipeline

Relevant source files

This document details the compile-time transformation pipeline that converts user-defined per-CPU variable declarations into architecture-specific, optimized access code. The pipeline is implemented as a procedural macro system that analyzes user code, detects target platform characteristics, and generates efficient per-CPU data access methods.

For information about the runtime memory layout and initialization, see Memory Layout and Initialization. For details about the specific assembly code generated for each architecture, see Architecture-Specific Code Generation.

Pipeline Overview

The code generation pipeline transforms a simple user declaration into a comprehensive per-CPU data access system through multiple stages of analysis and code generation.

High-Level Transformation Flow

flowchart TD
subgraph subGraph2["Output Components"]
    OUTPUT["Generated Components"]
    STORAGE["__PERCPU_VAR in .percpu section"]
    WRAPPER["VAR_WRAPPER struct"]
    INTERFACE["VAR: VAR_WRAPPER static"]
end
subgraph subGraph1["Generation Phase"]
    GENERATE["Code Generation"]
    STORAGE_GEN["__PERCPU_VAR generation"]
    WRAPPER_GEN["VAR_WRAPPER generation"]
    METHOD_GEN["Method generation"]
    ARCH_GEN["Architecture-specific code"]
end
subgraph subGraph0["Analysis Phase"]
    ANALYZE["Type & Feature Analysis"]
    TYPE_CHECK["is_primitive_int check"]
    FEATURE_CHECK["Feature flag detection"]
    SYMBOL_GEN["Symbol name generation"]
end
INPUT["#[def_percpu] static VAR: T = init;"]
PARSE["syn::parse_macro_input"]

ANALYZE --> FEATURE_CHECK
ANALYZE --> GENERATE
ANALYZE --> SYMBOL_GEN
ANALYZE --> TYPE_CHECK
GENERATE --> ARCH_GEN
GENERATE --> METHOD_GEN
GENERATE --> OUTPUT
GENERATE --> STORAGE_GEN
GENERATE --> WRAPPER_GEN
INPUT --> PARSE
OUTPUT --> INTERFACE
OUTPUT --> STORAGE
OUTPUT --> WRAPPER
PARSE --> ANALYZE

Sources: percpu_macros/src/lib.rs(L72 - L252) 

Input Parsing and Analysis

The pipeline begins with the def_percpu procedural macro, which uses the syn crate to parse the user's static variable declaration into an Abstract Syntax Tree (AST).

Parsing Stage

flowchart TD
subgraph subGraph0["Extracted Components"]
    EXTRACT["Component Extraction"]
    ATTRS["attrs: &[Attribute]"]
    VIS["vis: &Visibility"]
    NAME["ident: &Ident"]
    TYPE["ty: &Type"]
    INIT["expr: &Expr"]
end
USER["#[def_percpu]static COUNTER: u64 = 0;"]
AST["ItemStatic AST"]

AST --> EXTRACT
EXTRACT --> ATTRS
EXTRACT --> INIT
EXTRACT --> NAME
EXTRACT --> TYPE
EXTRACT --> VIS
USER --> AST

The parsing logic extracts key components from the declaration:

ComponentPurposeExample
attrsPreserve original attributes#[no_mangle]
visMaintain visibilitypub
identVariable nameCOUNTER
tyType informationu64
exprInitialization expression0

Sources: percpu_macros/src/lib.rs(L80 - L86) 

Type Analysis and Symbol Generation

The pipeline performs type analysis to determine code generation strategy and generates internal symbol names:

flowchart TD
subgraph subGraph1["Generated Symbols"]
    SYMBOL_GEN["Symbol Name Generation"]
    INNER["_PERCPU{name}"]
    WRAPPER["{name}_WRAPPER"]
    PUBLIC["{name}"]
end
subgraph subGraph0["Primitive Type Detection"]
    PRIMITIVE_CHECK["is_primitive_int check"]
    BOOL["bool"]
    U8["u8"]
    U16["u16"]
    U32["u32"]
    U64["u64"]
    USIZE["usize"]
end
TYPE_ANALYSIS["Type Analysis"]

PRIMITIVE_CHECK --> BOOL
PRIMITIVE_CHECK --> U16
PRIMITIVE_CHECK --> U32
PRIMITIVE_CHECK --> U64
PRIMITIVE_CHECK --> U8
PRIMITIVE_CHECK --> USIZE
SYMBOL_GEN --> INNER
SYMBOL_GEN --> PUBLIC
SYMBOL_GEN --> WRAPPER
TYPE_ANALYSIS --> PRIMITIVE_CHECK
TYPE_ANALYSIS --> SYMBOL_GEN

The type analysis at percpu_macros/src/lib.rs(L91 - L92)  determines whether to generate optimized assembly access methods for primitive integer types.

Sources: percpu_macros/src/lib.rs(L88 - L92) 

Code Generation Stages

The pipeline generates three primary components for each per-CPU variable, with different methods and optimizations based on type and feature analysis.

Component Generation Structure

flowchart TD
subgraph subGraph2["Interface Component"]
    INTERFACE["VAR static variable"]
    PUBLIC_API["Public API access point"]
    ZERO_SIZED["Zero-sized type"]
end
subgraph subGraph1["Wrapper Component"]
    WRAPPER["VAR_WRAPPER struct"]
    BASIC_METHODS["Basic access methods"]
    ARCH_METHODS["Architecture-specific methods"]
    PRIMITIVE_METHODS["Primitive type methods"]
end
subgraph subGraph0["Storage Component"]
    STORAGE["__PERCPU_VAR"]
    SECTION[".percpu section placement"]
    ATTRS_PRESERVE["Preserve original attributes"]
    INIT_PRESERVE["Preserve initialization"]
end
GENERATION["Code Generation"]

GENERATION --> INTERFACE
GENERATION --> STORAGE
GENERATION --> WRAPPER
INTERFACE --> PUBLIC_API
INTERFACE --> ZERO_SIZED
STORAGE --> ATTRS_PRESERVE
STORAGE --> INIT_PRESERVE
STORAGE --> SECTION
WRAPPER --> ARCH_METHODS
WRAPPER --> BASIC_METHODS
WRAPPER --> PRIMITIVE_METHODS

Sources: percpu_macros/src/lib.rs(L149 - L251) 

Method Generation Logic

The wrapper struct receives different sets of methods based on type analysis and feature configuration:

Method CategoryConditionGenerated Methods
Basic AccessAlwaysoffset(),current_ptr(),current_ref_raw(),current_ref_mut_raw(),with_current(),remote_ptr(),remote_ref_raw(),remote_ref_mut_raw()
Primitive Optimizedis_primitive_int == trueread_current_raw(),write_current_raw(),read_current(),write_current()
Preemption Safetyfeature = "preempt"AutomaticNoPreemptGuardin safe methods

Sources: percpu_macros/src/lib.rs(L100 - L145)  percpu_macros/src/lib.rs(L161 - L249) 

Architecture-Specific Code Generation

The pipeline delegates architecture-specific code generation to specialized functions in the arch module, which produce optimized assembly code for each supported platform.

Architecture Detection and Code Selection

flowchart TD
subgraph subGraph2["Register Access Patterns"]
    X86_GS["x86_64: gs:[offset __PERCPU_SELF_PTR]"]
    ARM_TPIDR["aarch64: mrs TPIDR_EL1/EL2"]
    RISCV_GP["riscv: mv gp"]
    LOONG_R21["loongarch64: move $r21"]
end
subgraph subGraph1["Target Architecture Detection"]
    X86_OFFSET["x86_64: mov offset VAR"]
    ARM_OFFSET["aarch64: movz #:abs_g0_nc:VAR"]
    RISCV_OFFSET["riscv: lui %hi(VAR), addi %lo(VAR)"]
    LOONG_OFFSET["loongarch64: lu12i.w %abs_hi20(VAR)"]
end
subgraph subGraph0["Code Generation Functions"]
    ARCH_GEN["Architecture Code Generation"]
    OFFSET["gen_offset()"]
    CURRENT_PTR["gen_current_ptr()"]
    READ_RAW["gen_read_current_raw()"]
    WRITE_RAW["gen_write_current_raw()"]
end

ARCH_GEN --> CURRENT_PTR
ARCH_GEN --> OFFSET
ARCH_GEN --> READ_RAW
ARCH_GEN --> WRITE_RAW
CURRENT_PTR --> ARM_TPIDR
CURRENT_PTR --> LOONG_R21
CURRENT_PTR --> RISCV_GP
CURRENT_PTR --> X86_GS
OFFSET --> ARM_OFFSET
OFFSET --> LOONG_OFFSET
OFFSET --> RISCV_OFFSET
OFFSET --> X86_OFFSET

Sources: percpu_macros/src/arch.rs(L16 - L50)  percpu_macros/src/arch.rs(L54 - L88) 

Assembly Code Generation Patterns

The architecture-specific functions generate different assembly instruction sequences based on the target platform and operation type:

ArchitectureOffset CalculationCurrent Pointer AccessDirect Read/Write
x86_64mov {reg}, offset {symbol}mov {reg}, gs:[offset __PERCPU_SELF_PTR]mov gs:[offset {symbol}], {val}
AArch64movz {reg}, #:abs_g0_nc:{symbol}mrs {reg}, TPIDR_EL1Not implemented
RISC-Vlui {reg}, %hi({symbol})mv {reg}, gplui + add + load/store
LoongArchlu12i.w {reg}, %abs_hi20({symbol})move {reg}, $r21lu12i.w + ori + load/store

Sources: percpu_macros/src/arch.rs(L94 - L181)  percpu_macros/src/arch.rs(L187 - L263) 

Feature-Based Code Variations

The pipeline adapts code generation based on feature flags, creating different implementations for various use cases and platform configurations.

Feature Flag Processing

flowchart TD
subgraph subGraph2["Generated Code Variations"]
    GLOBAL_VAR["Global variable access"]
    SAFE_METHODS["Preemption-safe methods"]
    HYP_MODE["Hypervisor mode support"]
end
subgraph subGraph1["Code Generation Impact"]
    NAIVE_IMPL["naive.rs implementation"]
    GUARD_GEN["NoPreemptGuard generation"]
    TPIDR_EL2["TPIDR_EL2 register selection"]
end
subgraph subGraph0["Core Features"]
    FEATURES["Feature Configuration"]
    SP_NAIVE["sp-naive"]
    PREEMPT["preempt"]
    ARM_EL2["arm-el2"]
end

ARM_EL2 --> TPIDR_EL2
FEATURES --> ARM_EL2
FEATURES --> PREEMPT
FEATURES --> SP_NAIVE
GUARD_GEN --> SAFE_METHODS
NAIVE_IMPL --> GLOBAL_VAR
PREEMPT --> GUARD_GEN
SP_NAIVE --> NAIVE_IMPL
TPIDR_EL2 --> HYP_MODE

The feature-based variations are configured at percpu_macros/Cargo.toml(L15 - L25)  and affect code generation in several ways:

FeatureImpactCode Changes
sp-naiveSingle-processor fallbackUsesnaive.rsinstead ofarch.rs
preemptPreemption safetyGeneratesNoPreemptGuardin safe methods
arm-el2Hypervisor modeUsesTPIDR_EL2instead ofTPIDR_EL1

Sources: percpu_macros/src/lib.rs(L59 - L60)  percpu_macros/src/lib.rs(L94 - L98)  percpu_macros/src/arch.rs(L55 - L61) 

Final Code Generation and Output

The pipeline concludes by assembling all generated components into the final token stream using the quote! macro.

Output Structure Assembly

flowchart TD
subgraph subGraph1["Quote Assembly"]
    ASSEMBLY["Token Stream Assembly"]
    QUOTE_MACRO["quote! { ... }"]
    TOKEN_STREAM["proc_macro2::TokenStream"]
    PROC_MACRO["TokenStream::into()"]
end
subgraph subGraph0["Component Assembly"]
    COMPONENTS["Generated Components"]
    STORAGE_TOKENS["Storage variable tokens"]
    WRAPPER_TOKENS["Wrapper struct tokens"]
    IMPL_TOKENS["Implementation tokens"]
    INTERFACE_TOKENS["Interface tokens"]
end
OUTPUT["Final Output"]

ASSEMBLY --> OUTPUT
ASSEMBLY --> QUOTE_MACRO
COMPONENTS --> ASSEMBLY
COMPONENTS --> IMPL_TOKENS
COMPONENTS --> INTERFACE_TOKENS
COMPONENTS --> STORAGE_TOKENS
COMPONENTS --> WRAPPER_TOKENS
QUOTE_MACRO --> TOKEN_STREAM
TOKEN_STREAM --> PROC_MACRO

The final assembly process at percpu_macros/src/lib.rs(L149 - L251)  combines:

  1. Storage Declaration: static mut __PERCPU_{name}: {type} = {init}; with .percpu section attribute
  2. Wrapper Struct: Zero-sized struct with generated methods for access
  3. Implementation Block: All the generated methods for the wrapper
  4. Public Interface: static {name}: {name}_WRAPPER = {name}_WRAPPER {};

The complete transformation ensures that a simple #[def_percpu] static VAR: T = init; declaration becomes a comprehensive per-CPU data access system with architecture-optimized assembly code, type-safe interfaces, and optional preemption safety.

Sources: percpu_macros/src/lib.rs(L149 - L252) 

API Reference

Relevant source files

This document provides a comprehensive reference for all public APIs, macros, and functions in the percpu crate ecosystem. It covers the main user-facing interfaces for defining and accessing per-CPU data structures.

For detailed information about the def_percpu macro syntax and usage patterns, see def_percpu Macro. For runtime initialization and management functions, see Runtime Functions. For guidance on safe usage and preemption handling, see Safety and Preemption.

API Overview

The percpu crate provides a declarative interface for per-CPU data management through the def_percpu attribute macro and a set of runtime functions for system initialization.

Complete API Surface

flowchart TD
subgraph subGraph5["Internal APIs"]
    PERCPU_AREA_BASE["percpu_area_base(cpu_id) -> usize"]
    NOPREEMPT_GUARD["NoPreemptGuard"]
    PERCPU_SYMBOL_OFFSET["percpu_symbol_offset!"]
end
subgraph subGraph4["Generated Per-Variable APIs"]
    WRAPPER["VAR_WRAPPERZero-sized struct"]
    STATIC_VAR["VAR: VAR_WRAPPERStatic instance"]
    subgraph subGraph3["Primitive Type Methods"]
        READ_CURRENT_RAW["read_current_raw() -> T"]
        WRITE_CURRENT_RAW["write_current_raw(val: T)"]
        READ_CURRENT["read_current() -> T"]
        WRITE_CURRENT["write_current(val: T)"]
    end
    subgraph subGraph2["Raw Access Methods"]
        CURRENT_REF_RAW["current_ref_raw() -> &T"]
        CURRENT_REF_MUT_RAW["current_ref_mut_raw() -> &mut T"]
        REMOTE_PTR["remote_ptr(cpu_id) -> *const T"]
        REMOTE_REF_RAW["remote_ref_raw(cpu_id) -> &T"]
        REMOTE_REF_MUT_RAW["remote_ref_mut_raw(cpu_id) -> &mut T"]
    end
    subgraph subGraph1["Core Methods"]
        OFFSET["offset() -> usize"]
        CURRENT_PTR["current_ptr() -> *const T"]
        WITH_CURRENT["with_current(f: F) -> R"]
    end
end
subgraph subGraph0["User Interface"]
    DEF_PERCPU["#[def_percpu]Attribute Macro"]
    INIT["percpu::init()"]
    INIT_REG["percpu::init_percpu_reg(cpu_id)"]
end

DEF_PERCPU --> STATIC_VAR
DEF_PERCPU --> WRAPPER
READ_CURRENT --> NOPREEMPT_GUARD
REMOTE_PTR --> PERCPU_AREA_BASE
WITH_CURRENT --> NOPREEMPT_GUARD
WRAPPER --> CURRENT_PTR
WRAPPER --> CURRENT_REF_MUT_RAW
WRAPPER --> CURRENT_REF_RAW
WRAPPER --> OFFSET
WRAPPER --> READ_CURRENT
WRAPPER --> READ_CURRENT_RAW
WRAPPER --> REMOTE_PTR
WRAPPER --> REMOTE_REF_MUT_RAW
WRAPPER --> REMOTE_REF_RAW
WRAPPER --> WITH_CURRENT
WRAPPER --> WRITE_CURRENT
WRAPPER --> WRITE_CURRENT_RAW
WRITE_CURRENT --> NOPREEMPT_GUARD

Sources: percpu_macros/src/lib.rs(L66 - L252)  percpu/src/lib.rs(L5 - L17)  README.md(L39 - L52) 

Core API Components

Macro Definition Interface

The primary user interface is the def_percpu attribute macro that transforms static variable definitions into per-CPU data structures.

ComponentTypePurpose
#[def_percpu]Attribute MacroTransforms static variables into per-CPU data
percpu::init()FunctionInitializes per-CPU data areas
percpu::init_percpu_reg(cpu_id)FunctionSets up per-CPU register for given CPU

Sources: percpu_macros/src/lib.rs(L66 - L78)  percpu/src/lib.rs(L11)  README.md(L44 - L46) 

Generated Code Structure

For each variable defined with #[def_percpu], the macro generates multiple code entities that work together to provide per-CPU access.

flowchart TD
subgraph subGraph3["Method Categories"]
    OFFSET_METHODS["Offset Calculationoffset()"]
    POINTER_METHODS["Pointer Accesscurrent_ptr(), remote_ptr()"]
    REFERENCE_METHODS["Reference Accesscurrent_ref_raw(), remote_ref_raw()"]
    VALUE_METHODS["Value Accessread_current(), write_current()"]
    FUNCTIONAL_METHODS["Functional Accesswith_current()"]
end
subgraph subGraph2["Section Placement"]
    PERCPU_SECTION[".percpu sectionLinker managed"]
end
subgraph subGraph1["Generated Entities"]
    INNER_SYMBOL["__PERCPU_VARstatic mut __PERCPU_VAR: T"]
    WRAPPER_STRUCT["VAR_WRAPPERstruct VAR_WRAPPER {}"]
    PUBLIC_STATIC["VARstatic VAR: VAR_WRAPPER"]
end
subgraph subGraph0["User Declaration"]
    USER_VAR["#[def_percpu]static VAR: T = init;"]
end

INNER_SYMBOL --> PERCPU_SECTION
USER_VAR --> INNER_SYMBOL
USER_VAR --> PUBLIC_STATIC
USER_VAR --> WRAPPER_STRUCT
WRAPPER_STRUCT --> FUNCTIONAL_METHODS
WRAPPER_STRUCT --> OFFSET_METHODS
WRAPPER_STRUCT --> POINTER_METHODS
WRAPPER_STRUCT --> REFERENCE_METHODS
WRAPPER_STRUCT --> VALUE_METHODS

Sources: percpu_macros/src/lib.rs(L88 - L159)  percpu_macros/src/lib.rs(L33 - L51) 

Method Categories and Safety

The generated wrapper struct provides methods organized into distinct categories based on their safety requirements and access patterns.

Method Safety Hierarchy

flowchart TD
subgraph subGraph4["Preemption Guard Integration"]
    NOPREEMPT["NoPreemptGuardkernel_guard crate"]
end
subgraph subGraph3["Utility Methods (Always Safe)"]
    OFFSET_SAFE["offset() -> usizeAll types"]
end
subgraph subGraph2["Remote Access (Always Unsafe)"]
    REMOTE_PTR_UNSAFE["remote_ptr(cpu_id) -> *const TAll types"]
    REMOTE_REF_RAW_UNSAFE["remote_ref_raw(cpu_id) -> &TAll types"]
    REMOTE_REF_MUT_RAW_UNSAFE["remote_ref_mut_raw(cpu_id) -> &mut TAll types"]
end
subgraph subGraph1["Unsafe Methods (Manual Preemption Control)"]
    READ_CURRENT_RAW_UNSAFE["read_current_raw() -> TPrimitive types only"]
    WRITE_CURRENT_RAW_UNSAFE["write_current_raw(val: T)Primitive types only"]
    CURRENT_REF_RAW_UNSAFE["current_ref_raw() -> &TAll types"]
    CURRENT_REF_MUT_RAW_UNSAFE["current_ref_mut_raw() -> &mut TAll types"]
    CURRENT_PTR_UNSAFE["current_ptr() -> *const TAll types"]
end
subgraph subGraph0["Safe Methods (Preemption Handled)"]
    READ_CURRENT_SAFE["read_current() -> TPrimitive types only"]
    WRITE_CURRENT_SAFE["write_current(val: T)Primitive types only"]
    WITH_CURRENT_SAFE["with_current(f: F) -> RAll types"]
end

READ_CURRENT_SAFE --> NOPREEMPT
READ_CURRENT_SAFE --> READ_CURRENT_RAW_UNSAFE
REMOTE_PTR_UNSAFE --> OFFSET_SAFE
REMOTE_REF_MUT_RAW_UNSAFE --> REMOTE_PTR_UNSAFE
REMOTE_REF_RAW_UNSAFE --> REMOTE_PTR_UNSAFE
WITH_CURRENT_SAFE --> CURRENT_REF_MUT_RAW_UNSAFE
WITH_CURRENT_SAFE --> NOPREEMPT
WRITE_CURRENT_SAFE --> NOPREEMPT
WRITE_CURRENT_SAFE --> WRITE_CURRENT_RAW_UNSAFE

Sources: percpu_macros/src/lib.rs(L101 - L145)  percpu_macros/src/lib.rs(L161 - L248)  percpu/src/lib.rs(L14 - L17) 

Type-Specific API Variations

The generated API surface varies depending on the type of the per-CPU variable, with additional optimized methods for primitive integer types.

API Method Availability Matrix

Method CategoryAll TypesPrimitive Integer Types Only
offset()
current_ptr()
current_ref_raw()
current_ref_mut_raw()
with_current()
remote_ptr()
remote_ref_raw()
remote_ref_mut_raw()
read_current_raw()
write_current_raw()
read_current()
write_current()

Primitive integer types: bool, u8, u16, u32, u64, usize

Sources: percpu_macros/src/lib.rs(L91 - L93)  percpu_macros/src/lib.rs(L100 - L145) 

Feature-Dependent Behavior

The API behavior changes based on enabled Cargo features, affecting both code generation and runtime behavior.

Feature Impact on API

FeatureImpact on Generated CodeRuntime Behavior
sp-naiveUses global variables instead of per-CPU areasNo register setup required
preemptAddsNoPreemptGuardto safe methodsPreemption disabled during access
arm-el2Changes register selection for AArch64UsesTPIDR_EL2instead ofTPIDR_EL1

Sources: percpu_macros/src/lib.rs(L94 - L98)  percpu/src/lib.rs(L7 - L8)  README.md(L69 - L79) 

def_percpu Macro

Relevant source files

The def_percpu macro is the primary interface for defining per-CPU static variables. It transforms a standard static variable declaration into a per-CPU data structure with architecture-specific access methods and automatic memory layout management.

For information about the runtime initialization and register management, see Runtime Functions. For details about preemption safety and guard mechanisms, see Safety and Preemption.

Syntax and Basic Usage

The def_percpu macro is applied as an attribute to static variable declarations:

#[percpu::def_percpu]
static VARIABLE_NAME: Type = initial_value;

The macro accepts no parameters and must be applied to static variables only. The original variable name becomes the public interface, while the macro generates the underlying implementation.

Code Transformation Overview

flowchart TD
USER["#[def_percpu]static VAR: T = init;"]
PARSE["syn::ItemStaticParser"]
GEN["Code Generator"]
STORAGE["__PERCPU_VAR(in .percpu section)"]
WRAPPER["VAR_WRAPPER(access methods)"]
PUBLIC["VAR: VAR_WRAPPER(public interface)"]
METHODS["offset()current_ptr()with_current()read_current()write_current()"]

GEN --> PUBLIC
GEN --> STORAGE
GEN --> WRAPPER
PARSE --> GEN
PUBLIC --> METHODS
USER --> PARSE

Sources: percpu_macros/src/lib.rs(L71 - L91)  percpu_macros/src/lib.rs(L149 - L159) 

Generated Code Structure

For each variable X with type T, the macro generates three main components:

ComponentName PatternPurposeVisibility
Storage Variable__PERCPU_XActual data storage in.percpusectionPrivate
Wrapper StructX_WRAPPERAccess methods containerMatches original
Public InterfaceXUser-facing APIMatches original

Generated Items Relationship

flowchart TD
USER_VAR["User Definition:static VAR: T = init"]
STORAGE["__PERCPU_VAR: T(in .percpu section)"]
WRAPPER_STRUCT["VAR_WRAPPER {}(zero-sized struct)"]
PUBLIC_VAR["VAR: VAR_WRAPPER(public interface)"]
CORE_METHODS["offset()current_ptr()current_ref_raw()current_ref_mut_raw()with_current()"]
REMOTE_METHODS["remote_ptr()remote_ref_raw()remote_ref_mut_raw()"]
PRIMITIVE_METHODS["read_current()write_current()read_current_raw()write_current_raw()"]
OFFSET_CALC["Used for offset calculation"]

STORAGE --> OFFSET_CALC
USER_VAR --> PUBLIC_VAR
USER_VAR --> STORAGE
USER_VAR --> WRAPPER_STRUCT
WRAPPER_STRUCT --> CORE_METHODS
WRAPPER_STRUCT --> PRIMITIVE_METHODS
WRAPPER_STRUCT --> REMOTE_METHODS

Sources: percpu_macros/src/lib.rs(L33 - L52)  percpu_macros/src/lib.rs(L88 - L89)  percpu_macros/src/lib.rs(L149 - L159) 

Available Methods

The generated wrapper struct provides different sets of methods depending on the data type and enabled features.

Core Methods (All Types)

These methods are available for all per-CPU variables regardless of type:

MethodReturn TypeSafetyPurpose
offset()usizeSafeReturns offset from per-CPU area base
current_ptr()*const TUnsafeRaw pointer to current CPU's data
current_ref_raw()&TUnsafeReference to current CPU's data
current_ref_mut_raw()&mut TUnsafeMutable reference to current CPU's data
with_current<F, R>(f: F) -> RRSafeExecute closure with mutable reference

Remote Access Methods

These methods enable access to per-CPU data on other CPUs:

MethodParametersReturn TypeSafety Requirements
remote_ptr(cpu_id)usize*const TValid CPU ID, no data races
remote_ref_raw(cpu_id)usize&TValid CPU ID, no data races
remote_ref_mut_raw(cpu_id)usize&mut TValid CPU ID, no data races

Primitive Type Methods

Additional methods for primitive integer types (bool, u8, u16, u32, u64, usize):

MethodParametersSafetyPreemption Handling
read_current()NoneSafeAutomatic guard
write_current(val)TSafeAutomatic guard
read_current_raw()NoneUnsafeManual management
write_current_raw(val)TUnsafeManual management

Sources: percpu_macros/src/lib.rs(L161 - L248)  percpu_macros/src/lib.rs(L100 - L145) 

Preemption Safety Integration

The macro automatically integrates with the preemption safety system when the preempt feature is enabled:

Preemption Guard Integration

flowchart TD
READ_CALL["VAR.read_current()"]
GUARD_CHECK["preempt feature?"]
WRITE_CALL["VAR.write_current(val)"]
WITH_CALL["VAR.with_current(|v| {...})"]
CREATE_GUARD["NoPreemptGuard::new()"]
NO_GUARD["No guard"]
RAW_ACCESS["unsafe raw access"]
ARCH_SPECIFIC["Architecture-specificregister + offset access"]

CREATE_GUARD --> RAW_ACCESS
GUARD_CHECK --> CREATE_GUARD
GUARD_CHECK --> NO_GUARD
NO_GUARD --> RAW_ACCESS
RAW_ACCESS --> ARCH_SPECIFIC
READ_CALL --> GUARD_CHECK
WITH_CALL --> GUARD_CHECK
WRITE_CALL --> GUARD_CHECK

Sources: percpu_macros/src/lib.rs(L94 - L98)  percpu_macros/src/lib.rs(L129 - L139)  percpu_macros/src/lib.rs(L201 - L207) 

Data Type Support and Behavior

Primitive Integer Types

For bool, u8, u16, u32, u64, and usize, the macro generates optimized read/write methods:

// Example usage for primitive types
COUNTER.write_current(42);
let value = COUNTER.read_current();

The macro detects primitive types through string comparison of the type representation.

Custom Structures and Complex Types

For non-primitive types, only the core access methods are generated. Access requires using with_current() or the raw pointer methods:

// Example usage for custom types
CUSTOM_STRUCT.with_current(|s| {
    s.field1 = new_value;
    s.field2 += increment;
});

Sources: percpu_macros/src/lib.rs(L91 - L93)  percpu_macros/src/lib.rs(L100 - L145) 

Architecture-Specific Code Generation

The macro delegates architecture-specific code generation to the arch module, which varies based on the target platform:

Architecture Code Generation Pipeline

flowchart TD
MACRO["def_percpu macro"]
DETECT["Target Detection"]
X86_GEN["x86_64 GeneratorGS_BASE + offset"]
ARM_GEN["AArch64 GeneratorTPIDR_ELx + offset"]
RISCV_GEN["RISC-V Generatorgp + offset"]
LOONG_GEN["LoongArch Generator$r21 + offset"]
NAIVE_GEN["Naive GeneratorGlobal variables"]
INLINE_ASM["Inline Assemblymov gs:[offset]"]
GLOBAL_ACCESS["Direct global access"]
METHODS["Generated Methods"]

ARM_GEN --> INLINE_ASM
DETECT --> ARM_GEN
DETECT --> LOONG_GEN
DETECT --> NAIVE_GEN
DETECT --> RISCV_GEN
DETECT --> X86_GEN
GLOBAL_ACCESS --> METHODS
INLINE_ASM --> METHODS
LOONG_GEN --> INLINE_ASM
MACRO --> DETECT
NAIVE_GEN --> GLOBAL_ACCESS
RISCV_GEN --> INLINE_ASM
X86_GEN --> INLINE_ASM

Sources: percpu_macros/src/lib.rs(L59 - L60)  percpu_macros/src/lib.rs(L102 - L104)  percpu_macros/src/lib.rs(L147 - L148) 

Usage Examples

Basic Primitive Type

#[percpu::def_percpu]
static COUNTER: usize = 0;

// Safe access with automatic preemption handling
COUNTER.write_current(42);
let value = COUNTER.read_current();

Custom Structure

struct Stats {
    operations: u64,
    errors: u32,
}

#[percpu::def_percpu]
static CPU_STATS: Stats = Stats { operations: 0, errors: 0 };

// Access through closure
CPU_STATS.with_current(|stats| {
    stats.operations += 1;
    if error_occurred {
        stats.errors += 1;
    }
});

Remote CPU Access

// Access data on a specific CPU (unsafe)
unsafe {
    let remote_value = *COUNTER.remote_ptr(cpu_id);
    *COUNTER.remote_ref_mut_raw(cpu_id) = new_value;
}

Sources: README.md(L39 - L52)  percpu/tests/test_percpu.rs(L7 - L32)  percpu/tests/test_percpu.rs(L71 - L101) 

Error Conditions

The macro will generate compile-time errors in the following cases:

ConditionError Message
Non-empty attributeexpect an empty attribute: #[def_percpu]
Non-static itemParser error fromsyn::parse_macro_input!
Invalid syntaxVarious parser errors fromsyn

Sources: percpu_macros/src/lib.rs(L73 - L78)  percpu_macros/src/lib.rs(L80) 

Feature-Specific Behavior

The macro behavior changes based on enabled Cargo features:

  • sp-naive: Uses global variables instead of per-CPU areas
  • preempt: Automatically includes NoPreemptGuard in safe methods
  • arm-el2: Targets TPIDR_EL2 register instead of TPIDR_EL1

For detailed information about these features, see Feature Flags Configuration.

Sources: percpu_macros/src/lib.rs(L94 - L98)  README.md(L69 - L79) 

Runtime Functions

Relevant source files

This page documents the core runtime functions provided by the percpu crate for managing per-CPU data areas, initializing the system, and handling architecture-specific register operations. These functions form the low-level foundation that supports the high-level def_percpu macro interface.

For information about defining per-CPU variables, see def_percpu Macro. For safety considerations and preemption handling, see Safety and Preemption. For architectural details about memory layout, see Memory Layout and Initialization.

System Initialization

The percpu system requires explicit initialization before per-CPU variables can be accessed. The initialization process sets up memory areas for each CPU and prepares the system for per-CPU data access.

Core Initialization Function

The init() function is the primary entry point for initializing the per-CPU data system:

#![allow(unused)]
fn main() {
pub fn init() -> usize
}

This function performs the following operations:

  • Prevents re-initialization using atomic synchronization
  • Allocates per-CPU memory areas (on hosted platforms)
  • Copies template data from the .percpu section to each CPU's area
  • Returns the number of CPU areas initialized

The function uses an atomic boolean IS_INIT to ensure initialization occurs only once, returning 0 on subsequent calls.

Per-CPU Register Initialization

After system initialization, each CPU must initialize its architecture-specific register:

#![allow(unused)]
fn main() {
pub fn init_percpu_reg(cpu_id: usize)
}

This function sets the per-CPU register to point to the appropriate memory area for the specified CPU. It is equivalent to calling write_percpu_reg(percpu_area_base(cpu_id)).

flowchart TD
Start["System Boot"]
InitCall["percpu::init()"]
CheckInit["Already initialized?"]
SetFlag["Set IS_INIT atomic flag"]
Return0["Return 0"]
HostedCheck["Hosted platform?"]
AllocMem["Allocate memory with std::alloc"]
UseLinker["Use linker-provided _percpu_start"]
CopyLoop["Copy template data to each CPU area"]
ReturnNum["Return number of areas"]
PerCpuInit["Each CPU calls init_percpu_reg(cpu_id)"]
SetRegister["Set architecture register to area base"]
Ready["System ready for per-CPU access"]

AllocMem --> CopyLoop
CheckInit --> Return0
CheckInit --> SetFlag
CopyLoop --> ReturnNum
HostedCheck --> AllocMem
HostedCheck --> UseLinker
InitCall --> CheckInit
PerCpuInit --> SetRegister
Return0 --> Ready
ReturnNum --> PerCpuInit
SetFlag --> HostedCheck
SetRegister --> Ready
Start --> InitCall
UseLinker --> CopyLoop

Sources: percpu/src/imp.rs(L56 - L86)  percpu/src/imp.rs(L165 - L168) 

Memory Query Functions

The percpu system provides several functions for querying memory layout and dimensions of the per-CPU data areas.

Area Dimensions

FunctionReturn TypePurpose
percpu_area_num()usizeNumber of per-CPU areas reserved
percpu_area_size()usizeSize in bytes of each CPU's data area
percpu_area_base(cpu_id: usize)usizeBase address of the specified CPU's area

Memory Layout Calculation

The system calculates memory layout using several key components:

  • Template size: Determined by _percpu_load_end - _percpu_load_start
  • Alignment: Each area is aligned to 64-byte boundaries using align_up_64()
  • Total areas: Calculated as (_percpu_end - _percpu_start) / aligned_area_size
flowchart TD
LinkerSyms["Linker Symbols"]
SizeCalc["percpu_area_size()"]
NumCalc["percpu_area_num()"]
LoadStart["_percpu_load_start"]
LoadEnd["_percpu_load_end"]
Start["_percpu_start"]
End["_percpu_end"]
AlignUp["align_up_64()"]
BaseCalc["percpu_area_base(cpu_id)"]
CPUBase["CPU area base = base + (cpu_id * aligned_size)"]

AlignUp --> BaseCalc
BaseCalc --> CPUBase
LinkerSyms --> NumCalc
LinkerSyms --> SizeCalc
NumCalc --> AlignUp
NumCalc --> BaseCalc
NumCalc --> End
NumCalc --> Start
SizeCalc --> BaseCalc
SizeCalc --> LoadEnd
SizeCalc --> LoadStart

Sources: percpu/src/imp.rs(L21 - L44)  percpu/src/imp.rs(L5 - L8) 

Register Management Functions

Per-CPU data access relies on architecture-specific registers that hold the base address of the current CPU's data area. The system provides functions to read and write these registers.

Register Access Functions

#![allow(unused)]
fn main() {
pub fn read_percpu_reg() -> usize
pub unsafe fn write_percpu_reg(tp: usize)
}

The read_percpu_reg() function returns the current value of the per-CPU register, while write_percpu_reg() sets it to a new value. The write function is marked unsafe because it directly manipulates low-level CPU registers.

Architecture-Specific Register Mapping

Different CPU architectures use different registers and instruction sequences for per-CPU data access:

flowchart TD
RegAccess["Per-CPU Register Access"]
X86["x86_64"]
ARM["AArch64"]
RISCV["RISC-V"]
LOONG["LoongArch64"]
X86Read["Read: rdmsr(IA32_GS_BASE)"]
X86Write["Write: wrmsr(IA32_GS_BASE)"]
X86Linux["Linux: arch_prctl syscall"]
ARMRead["Read: mrs TPIDR_EL1/EL2"]
ARMWrite["Write: msr TPIDR_EL1/EL2"]
ARMEL2["EL2 mode via arm-el2 feature"]
RVRead["Read: mv reg, gp"]
RVWrite["Write: mv gp, reg"]
LRead["Read: move reg, $r21"]
LWrite["Write: move $r21, reg"]

ARM --> ARMEL2
ARM --> ARMRead
ARM --> ARMWrite
LOONG --> LRead
LOONG --> LWrite
RISCV --> RVRead
RISCV --> RVWrite
RegAccess --> ARM
RegAccess --> LOONG
RegAccess --> RISCV
RegAccess --> X86
X86 --> X86Linux
X86 --> X86Read
X86 --> X86Write

Sources: percpu/src/imp.rs(L91 - L156) 

Architecture-Specific Behavior

The percpu system adapts to different target platforms and privilege levels through conditional compilation and feature flags.

Platform-Specific Implementations

PlatformMemory SourceRegister Access Method
target_os = "none"Linker-provided.percpusectionDirect MSR/register access
target_os = "linux"std::allocallocated memorySystem calls (x86_64)
Other hostedPlatform-specific allocationPlatform-specific methods

Feature Flag Behavior

The system behavior changes based on enabled feature flags:

  • arm-el2: Uses TPIDR_EL2 instead of TPIDR_EL1 on AArch64
  • sp-naive: Uses alternative implementation (see Naive Implementation)
  • preempt: Integrates with kernel_guard for preemption safety
flowchart TD
PlatformCheck["Target Platform"]
Bare["target_os = none"]
Linux["target_os = linux"]
Other["Other hosted"]
LinkerMem["Use _percpu_start from linker"]
AllocMem["Allocate with std::alloc"]
PlatformMem["Platform-specific allocation"]
ArchCheck["Target Architecture"]
X86Arch["x86_64"]
ARMArch["aarch64"]
RVArch["riscv32/64"]
LoongArch["loongarch64"]
EL2Check["arm-el2 feature?"]
EL2Reg["Use TPIDR_EL2"]
EL1Reg["Use TPIDR_EL1"]
X86Reg["Use GS_BASE"]
GPReg["Use gp register"]
R21Reg["Use $r21 register"]

ARMArch --> EL2Check
ArchCheck --> ARMArch
ArchCheck --> LoongArch
ArchCheck --> RVArch
ArchCheck --> X86Arch
Bare --> LinkerMem
EL2Check --> EL1Reg
EL2Check --> EL2Reg
Linux --> AllocMem
LoongArch --> R21Reg
Other --> PlatformMem
PlatformCheck --> Bare
PlatformCheck --> Linux
PlatformCheck --> Other
RVArch --> GPReg
X86Arch --> X86Reg

Sources: percpu/src/imp.rs(L36 - L44)  percpu/src/imp.rs(L94 - L155) 

Usage Patterns

Runtime functions are typically used in specific patterns during system initialization and operation.

Initialization Sequence

// 1. Initialize the per-CPU system
let num_cpus = percpu::init();

// 2. Each CPU initializes its register
// (usually called in per-CPU startup code)
percpu::init_percpu_reg(cpu_id);

// 3. Query system information if needed
let area_size = percpu::percpu_area_size();
let cpu_base = percpu::percpu_area_base(cpu_id);

Register Management

// Read current per-CPU register value
let current_base = percpu::read_percpu_reg();

// Manually set per-CPU register (unsafe)
unsafe {
    percpu::write_percpu_reg(new_base_address);
}

Integration with Generated Code

The runtime functions work together with the code generated by the def_percpu macro. The macro-generated code uses these functions internally to calculate variable addresses and manage access patterns.

Sources: percpu/src/imp.rs(L1 - L179)  percpu/src/lib.rs(L7 - L17) 

Safety and Preemption

Relevant source files

This document covers the safety mechanisms and preemption handling in the percpu crate system. It explains how per-CPU data access is protected from preemption-related data races and provides guidance on choosing between different safety models. For information about memory layout and initialization safety, see Memory Layout and Initialization. For details about architecture-specific implementation safety, see Architecture-Specific Code Generation.

Preemption Safety Concepts

Per-CPU data access presents unique safety challenges in preemptible systems. When a task accesses per-CPU data and is then preempted and migrated to another CPU, it may inadvertently access data belonging to a different CPU, leading to data races and inconsistent state.

sequenceDiagram
    participant Task as "Task"
    participant CPU0 as "CPU 0"
    participant CPU1 as "CPU 1"
    participant PerCPUData as "Per-CPU Data"

    Task ->> CPU0: "Access per-CPU variable"
    CPU0 ->> PerCPUData: "Read CPU 0 data area"
    Note over Task,CPU0: "Task gets preempted"
    Task ->> CPU1: "Task migrated to CPU 1"
    Task ->> CPU1: "Continue per-CPU access"
    CPU1 ->> PerCPUData: "Access CPU 1 data area"
    Note over Task,PerCPUData: "Data inconsistency!"

The percpu crate provides two safety models to address this challenge:

Safety ModelDescriptionUse Case
Raw MethodsRequire manual preemption controlPerformance-critical code with existing preemption management
Safe MethodsAutomatic preemption disablingGeneral application code requiring safety guarantees

Sources: percpu_macros/src/lib.rs(L94 - L98)  percpu_macros/src/lib.rs(L109 - L140) 

Safety Models and Method Types

Raw Methods (*_rawfunctions)

Raw methods provide direct access to per-CPU data without automatic preemption control. These methods are marked unsafe and require the caller to ensure preemption is disabled.

flowchart TD
subgraph Examples["Examples"]
    ReadRaw["read_current_raw()"]
    WriteRaw["write_current_raw()"]
    RefRaw["current_ref_raw()"]
    MutRefRaw["current_ref_mut_raw()"]
end
RawAccess["Raw Method Call"]
PreemptCheck["Preemption Disabled?"]
DataAccess["Direct Data Access"]
UnsafeBehavior["Undefined Behavior"]

PreemptCheck --> DataAccess
PreemptCheck --> UnsafeBehavior
RawAccess --> PreemptCheck

Key raw methods include:

  • read_current_raw() - Returns value without preemption protection
  • write_current_raw() - Sets value without preemption protection
  • current_ref_raw() - Returns reference without preemption protection
  • current_ref_mut_raw() - Returns mutable reference without preemption protection

Sources: percpu_macros/src/lib.rs(L113 - L125)  percpu_macros/src/lib.rs(L184 - L197) 

Safe Methods (Automatic Preemption Control)

Safe methods automatically disable preemption during the operation using the NoPreemptGuard mechanism when the preempt feature is enabled.

flowchart TD
subgraph subGraph0["Safe Method Examples"]
    ReadCurrent["read_current()"]
    WriteCurrent["write_current()"]
    WithCurrent["with_current(closure)"]
end
SafeCall["Safe Method Call"]
GuardCreate["NoPreemptGuard::new()"]
PreemptDisabled["Preemption Disabled"]
RawCall["Call corresponding _raw method"]
GuardDrop["Guard dropped (RAII)"]
PreemptReenabled["Preemption Re-enabled"]

GuardCreate --> PreemptDisabled
GuardDrop --> PreemptReenabled
PreemptDisabled --> RawCall
RawCall --> GuardDrop
SafeCall --> GuardCreate

The code generation logic determines preemption handling based on feature flags:

flowchart TD
FeatureCheck["preempt feature enabled?"]
GuardGen["Generate NoPreemptGuard"]
NoGuard["Generate empty block"]
SafeMethod["Safe method implementation"]

FeatureCheck --> GuardGen
FeatureCheck --> NoGuard
GuardGen --> SafeMethod
NoGuard --> SafeMethod

Sources: percpu_macros/src/lib.rs(L129 - L139)  percpu_macros/src/lib.rs(L201 - L207) 

NoPreemptGuard Integration

The NoPreemptGuard provides RAII-based preemption control through integration with the kernel_guard crate. This integration is enabled by the preempt feature flag.

Feature Configuration

Feature StateBehaviorDependencies
preemptenabledAutomatic preemption guardskernel_guardcrate
preemptdisabledNo preemption protectionNo additional dependencies

Guard Implementation

flowchart TD
UserCall["User calls safe method"]
GuardCreation["let _guard = NoPreemptGuard::new()"]
PreemptOff["Preemption disabled"]
DataOperation["Per-CPU data access"]
ScopeEnd["Method scope ends"]
GuardDrop["_guard dropped (RAII)"]
PreemptOn["Preemption re-enabled"]

DataOperation --> ScopeEnd
GuardCreation --> PreemptOff
GuardDrop --> PreemptOn
PreemptOff --> DataOperation
ScopeEnd --> GuardDrop
UserCall --> GuardCreation

The guard is implemented as a module re-export that conditionally includes the kernel_guard::NoPreempt type:

Sources: percpu/src/lib.rs(L14 - L17)  percpu/Cargo.toml(L21 - L22)  percpu_macros/src/lib.rs(L94 - L98) 

Remote Access Safety

Remote CPU access methods (remote_*) require additional safety considerations beyond preemption control, as they access data belonging to other CPUs.

Remote Access Safety Requirements

MethodSafety Requirements
remote_ptr(cpu_id)Valid CPU ID + No data races
remote_ref_raw(cpu_id)Valid CPU ID + No data races
remote_ref_mut_raw(cpu_id)Valid CPU ID + Exclusive access
flowchart TD
subgraph subGraph0["Remote Methods"]
    RemotePtr["remote_ptr(cpu_id)"]
    RemoteRef["remote_ref_raw(cpu_id)"]
    RemoteMut["remote_ref_mut_raw(cpu_id)"]
end
RemoteCall["Remote access method"]
CPUValidation["CPU ID valid?"]
RaceCheck["Data races possible?"]
SafeAccess["Safe remote access"]
UnsafeBehavior["Undefined behavior"]

CPUValidation --> RaceCheck
CPUValidation --> UnsafeBehavior
RaceCheck --> SafeAccess
RaceCheck --> UnsafeBehavior
RemoteCall --> CPUValidation

Sources: percpu_macros/src/lib.rs(L209 - L246) 

Best Practices

Choosing Safety Models

  1. Use safe methods by default - Automatic preemption protection prevents common bugs
  2. Use raw methods for performance-critical paths - When preemption is already managed externally
  3. Enable preempt feature in production - Unless running on single-CPU systems

Safe Usage Patterns

flowchart TD
subgraph Recommendations["Recommendations"]
    Rec1["Prefer safe methods"]
    Rec2["Use with_current for atomicity"]
    Rec3["Minimize raw method usage"]
end
Pattern1["Single Operation"]
Pattern2["Multiple Operations"]
Pattern3["Complex Logic"]
Method1["read_current() / write_current()"]
Method2["with_current(closure)"]
Method3["Manual guard management"]

Pattern1 --> Method1
Pattern2 --> Method2
Pattern3 --> Method3

Feature Flag Configuration

For different deployment scenarios:

ScenarioFeature ConfigurationRationale
Production kernelpreemptenabledFull safety guarantees
Single-CPU embeddedsp-naiveenabledNo preemption needed
Performance testingpreemptdisabledMeasure raw performance

Sources: percpu/Cargo.toml(L15 - L25)  percpu_macros/src/lib.rs(L100 - L145) 

Implementation Details

Relevant source files

This page provides an overview of the internal implementation details of the percpu crate ecosystem for maintainers and advanced users. It covers the core implementation strategies, key code entities, and how the compile-time macro generation integrates with the runtime per-CPU data management system.

For detailed architecture-specific code generation, see Architecture-Specific Code Generation. For the simplified single-CPU implementation, see Naive Implementation. For low-level memory management specifics, see Memory Management Internals.

Core Implementation Strategy

The percpu system implements per-CPU data management through a two-phase approach: compile-time code generation via procedural macros and runtime memory area management. The system places all per-CPU variables in a special .percpu linker section, then creates per-CPU memory areas by copying this template section for each CPU.

flowchart TD
subgraph subGraph2["Linker Integration"]
    PERCPU_SEC[".percpu section"]
    PERCPU_START["_percpu_start"]
    PERCPU_END["_percpu_end"]
    LOAD_START["_percpu_load_start"]
    LOAD_END["_percpu_load_end"]
end
subgraph subGraph1["Runtime Phase"]
    INIT_FUNC["init()"]
    AREA_ALLOC["percpu_area_base()"]
    REG_INIT["init_percpu_reg()"]
    READ_REG["read_percpu_reg()"]
end
subgraph subGraph0["Compile-Time Phase"]
    DEF_PERCPU["def_percpu macro"]
    GEN_CODE["Code Generation Pipeline"]
    INNER_SYM["_PERCPU* symbols"]
    WRAPPER["*_WRAPPER structs"]
end

AREA_ALLOC --> REG_INIT
DEF_PERCPU --> GEN_CODE
GEN_CODE --> INNER_SYM
GEN_CODE --> WRAPPER
INIT_FUNC --> AREA_ALLOC
INNER_SYM --> PERCPU_SEC
LOAD_END --> AREA_ALLOC
LOAD_START --> AREA_ALLOC
PERCPU_END --> AREA_ALLOC
PERCPU_SEC --> INIT_FUNC
PERCPU_START --> AREA_ALLOC
REG_INIT --> READ_REG
WRAPPER --> AREA_ALLOC
WRAPPER --> READ_REG

Sources: percpu_macros/src/lib.rs(L54 - L262)  percpu/src/imp.rs(L1 - L179) 

Runtime Implementation Architecture

The runtime implementation in imp.rs manages per-CPU memory areas and provides architecture-specific register access. The core functions handle initialization, memory layout calculation, and register manipulation across different CPU architectures.

flowchart TD
subgraph subGraph4["x86 Self-Pointer"]
    SELF_PTR["SELF_PTR: def_percpu static"]
end
subgraph subGraph3["Architecture-Specific Registers"]
    X86_GS["x86_64: IA32_GS_BASE"]
    ARM_TPIDR["aarch64: TPIDR_EL1/EL2"]
    RISCV_GP["riscv: gp register"]
    LOONG_R21["loongarch: $r21 register"]
end
subgraph subGraph2["Register Management"]
    READ_REG["read_percpu_reg()"]
    WRITE_REG["write_percpu_reg()"]
    INIT_REG["init_percpu_reg()"]
end
subgraph subGraph1["Memory Layout Functions"]
    AREA_SIZE["percpu_area_size()"]
    AREA_NUM["percpu_area_num()"]
    AREA_BASE["percpu_area_base(cpu_id)"]
    ALIGN_UP["align_up_64()"]
end
subgraph subGraph0["Initialization Functions"]
    INIT["init()"]
    IS_INIT["IS_INIT: AtomicBool"]
    PERCPU_AREA_BASE_STATIC["PERCPU_AREA_BASE: Once"]
end

AREA_BASE --> ALIGN_UP
AREA_NUM --> ALIGN_UP
AREA_SIZE --> ALIGN_UP
INIT --> AREA_BASE
INIT --> AREA_SIZE
INIT --> IS_INIT
INIT --> PERCPU_AREA_BASE_STATIC
INIT_REG --> AREA_BASE
INIT_REG --> WRITE_REG
READ_REG --> ARM_TPIDR
READ_REG --> LOONG_R21
READ_REG --> RISCV_GP
READ_REG --> X86_GS
WRITE_REG --> ARM_TPIDR
WRITE_REG --> LOONG_R21
WRITE_REG --> RISCV_GP
WRITE_REG --> X86_GS
X86_GS --> SELF_PTR

Sources: percpu/src/imp.rs(L3 - L179) 

Compile-Time Code Generation Architecture

The macro system in percpu_macros transforms user-defined per-CPU variables into architecture-optimized access code. The def_percpu macro generates wrapper structs with methods for safe and unsafe access patterns.

flowchart TD
subgraph subGraph3["Feature Configuration"]
    SP_NAIVE_FEATURE["sp-naive feature"]
    PREEMPT_FEATURE["preempt feature"]
    ARM_EL2_FEATURE["arm-el2 feature"]
    NO_PREEMPT_GUARD["NoPreemptGuard"]
end
subgraph subGraph2["Generated Code Entities"]
    INNER_SYMBOL["_PERCPU{name}"]
    WRAPPER_STRUCT["{name}_WRAPPER"]
    WRAPPER_STATIC["{name}: {name}_WRAPPER"]
    OFFSET_METHOD["offset()"]
    CURRENT_PTR_METHOD["current_ptr()"]
    WITH_CURRENT_METHOD["with_current()"]
    READ_WRITE_METHODS["read/write_current*()"]
    REMOTE_METHODS["remote_*_raw()"]
end
subgraph subGraph1["Code Generation Functions"]
    GEN_OFFSET["arch::gen_offset()"]
    GEN_CURRENT_PTR["arch::gen_current_ptr()"]
    GEN_READ_RAW["arch::gen_read_current_raw()"]
    GEN_WRITE_RAW["arch::gen_write_current_raw()"]
end
subgraph subGraph0["Input Processing"]
    DEF_PERCPU_ATTR["def_percpu attribute"]
    ITEM_STATIC["ItemStatic AST"]
    PARSE_INPUT["syn::parse_macro_input"]
end

ARM_EL2_FEATURE --> GEN_CURRENT_PTR
DEF_PERCPU_ATTR --> PARSE_INPUT
GEN_CURRENT_PTR --> WRAPPER_STRUCT
GEN_OFFSET --> INNER_SYMBOL
GEN_READ_RAW --> READ_WRITE_METHODS
GEN_WRITE_RAW --> READ_WRITE_METHODS
ITEM_STATIC --> PARSE_INPUT
NO_PREEMPT_GUARD --> READ_WRITE_METHODS
NO_PREEMPT_GUARD --> WITH_CURRENT_METHOD
PARSE_INPUT --> GEN_CURRENT_PTR
PARSE_INPUT --> GEN_OFFSET
PARSE_INPUT --> GEN_READ_RAW
PARSE_INPUT --> GEN_WRITE_RAW
PREEMPT_FEATURE --> NO_PREEMPT_GUARD
SP_NAIVE_FEATURE --> GEN_OFFSET
WRAPPER_STATIC --> WRAPPER_STRUCT
WRAPPER_STRUCT --> CURRENT_PTR_METHOD
WRAPPER_STRUCT --> OFFSET_METHOD
WRAPPER_STRUCT --> READ_WRITE_METHODS
WRAPPER_STRUCT --> REMOTE_METHODS
WRAPPER_STRUCT --> WITH_CURRENT_METHOD

Sources: percpu_macros/src/lib.rs(L66 - L252)  percpu_macros/src/arch.rs(L1 - L264) 

Architecture-Specific Code Generation

The system generates different assembly code for each supported architecture to access per-CPU data efficiently. Each architecture uses different registers and instruction sequences for optimal performance.

ArchitectureRegisterOffset CalculationAccess Pattern
x86_64GS_BASE(IA32_GS_BASE)offset symbolmov gs:[offset VAR]
AArch64TPIDR_EL1/TPIDR_EL2#:abs_g0_nc:symbolmrs TPIDR_ELx+ offset
RISC-Vgpregister%hi(symbol)+%lo(symbol)lui+addi+gp
LoongArch$r21register%abs_hi20+%abs_lo12lu12i.w+ori+$r21
flowchart TD
subgraph subGraph5["LoongArch Assembly"]
    LOONG_OFFSET["lu12i.w %abs_hi20 + ori %abs_lo12"]
    LOONG_R21_READ["move {}, $r21"]
    LOONG_R21_WRITE["move $r21, {}"]
    LOONG_LOAD_STORE["ldx./stx.with $r21"]
end
subgraph subGraph4["RISC-V Assembly"]
    RISCV_OFFSET["lui %hi + addi %lo"]
    RISCV_GP_READ["mv {}, gp"]
    RISCV_GP_WRITE["mv gp, {}"]
    RISCV_LOAD_STORE["ld/sd with gp offset"]
end
subgraph subGraph3["AArch64 Assembly"]
    ARM_OFFSET["movz #:abs_g0_nc:{VAR}"]
    ARM_TPIDR_READ["mrs TPIDR_EL1/EL2"]
    ARM_TPIDR_WRITE["msr TPIDR_EL1/EL2"]
end
subgraph subGraph2["x86_64 Assembly"]
    X86_OFFSET["mov {0:e}, offset {VAR}"]
    X86_READ["mov gs:[offset {VAR}]"]
    X86_WRITE["mov gs:[offset {VAR}], value"]
    X86_GS_BASE["IA32_GS_BASE MSR"]
end
subgraph subGraph1["Code Generation Functions"]
    GEN_OFFSET_IMPL["gen_offset()"]
    GEN_CURRENT_PTR_IMPL["gen_current_ptr()"]
    GEN_READ_IMPL["gen_read_current_raw()"]
    GEN_WRITE_IMPL["gen_write_current_raw()"]
end
subgraph subGraph0["Architecture Detection"]
    TARGET_ARCH["cfg!(target_arch)"]
    X86_64["x86_64"]
    AARCH64["aarch64"]
    RISCV["riscv32/riscv64"]
    LOONGARCH["loongarch64"]
end

AARCH64 --> GEN_OFFSET_IMPL
GEN_CURRENT_PTR_IMPL --> ARM_TPIDR_READ
GEN_CURRENT_PTR_IMPL --> LOONG_R21_READ
GEN_CURRENT_PTR_IMPL --> RISCV_GP_READ
GEN_OFFSET_IMPL --> ARM_OFFSET
GEN_OFFSET_IMPL --> LOONG_OFFSET
GEN_OFFSET_IMPL --> RISCV_OFFSET
GEN_OFFSET_IMPL --> X86_OFFSET
GEN_READ_IMPL --> LOONG_LOAD_STORE
GEN_READ_IMPL --> RISCV_LOAD_STORE
GEN_READ_IMPL --> X86_READ
GEN_WRITE_IMPL --> LOONG_LOAD_STORE
GEN_WRITE_IMPL --> RISCV_LOAD_STORE
GEN_WRITE_IMPL --> X86_WRITE
LOONGARCH --> GEN_OFFSET_IMPL
RISCV --> GEN_OFFSET_IMPL
TARGET_ARCH --> AARCH64
TARGET_ARCH --> LOONGARCH
TARGET_ARCH --> RISCV
TARGET_ARCH --> X86_64

Sources: percpu_macros/src/arch.rs(L15 - L264) 

Integration Between Runtime and Macros

The compile-time macros and runtime functions work together through shared conventions and generated code that calls runtime functions. The macros generate code that uses runtime functions for memory calculations and remote access.

flowchart TD
subgraph subGraph3["Memory Layout"]
    TEMPLATE_AREA["Template in .percpu"]
    CPU0_AREA["CPU 0 Data Area"]
    CPU1_AREA["CPU 1 Data Area"]
    CPUN_AREA["CPU N Data Area"]
end
subgraph subGraph2["Shared Data Structures"]
    PERCPU_SECTION[".percpu section"]
    LINKER_SYMBOLS["_percpu_start/_percpu_end"]
    INNER_SYMBOLS["_PERCPU* symbols"]
    CPU_REGISTERS["Architecture registers"]
end
subgraph subGraph1["Runtime Function Calls"]
    PERCPU_AREA_BASE_CALL["percpu::percpu_area_base(cpu_id)"]
    SYMBOL_OFFSET_CALL["percpu_symbol_offset! macro"]
    READ_PERCPU_REG_CALL["read_percpu_reg()"]
end
subgraph subGraph0["Macro-Generated Code"]
    WRAPPER_METHODS["Wrapper Methods"]
    OFFSET_CALC["self.offset()"]
    CURRENT_PTR_CALC["self.current_ptr()"]
    REMOTE_PTR_CALC["self.remote_ptr(cpu_id)"]
    ASSEMBLY_ACCESS["Architecture-specific assembly"]
end

ASSEMBLY_ACCESS --> CPU_REGISTERS
ASSEMBLY_ACCESS --> READ_PERCPU_REG_CALL
CURRENT_PTR_CALC --> ASSEMBLY_ACCESS
INNER_SYMBOLS --> PERCPU_SECTION
LINKER_SYMBOLS --> PERCPU_SECTION
OFFSET_CALC --> SYMBOL_OFFSET_CALL
PERCPU_AREA_BASE_CALL --> CPU0_AREA
PERCPU_AREA_BASE_CALL --> CPU1_AREA
PERCPU_AREA_BASE_CALL --> CPUN_AREA
PERCPU_AREA_BASE_CALL --> LINKER_SYMBOLS
PERCPU_SECTION --> TEMPLATE_AREA
REMOTE_PTR_CALC --> PERCPU_AREA_BASE_CALL
SYMBOL_OFFSET_CALL --> INNER_SYMBOLS
TEMPLATE_AREA --> CPU0_AREA
TEMPLATE_AREA --> CPU1_AREA
TEMPLATE_AREA --> CPUN_AREA

Sources: percpu_macros/src/lib.rs(L216 - L221)  percpu/src/imp.rs(L32 - L44)  percpu_macros/src/lib.rs(L255 - L261) 

Architecture-Specific Code Generation

Relevant source files

This document covers the architecture-specific assembly code generation system used by the percpu_macros crate. The code generation pipeline transforms high-level per-CPU variable access patterns into optimized assembly instructions tailored for each supported CPU architecture.

For information about the macro expansion pipeline and user-facing API, see Code Generation Pipeline. For details about the naive single-CPU implementation, see Naive Implementation.

Overview

The architecture-specific code generation system consists of four main code generation functions that produce inline assembly blocks optimized for each target architecture. These functions are called during macro expansion to generate efficient per-CPU data access patterns.


Sources: percpu_macros/src/arch.rs(L16 - L50)  percpu_macros/src/arch.rs(L54 - L88)  percpu_macros/src/arch.rs(L94 - L181)  percpu_macros/src/arch.rs(L187 - L263) 

Offset Calculation Generation

The gen_offset function generates architecture-specific assembly code to calculate the offset of a per-CPU variable within the .percpu section. This offset is used for both local and remote CPU access patterns.

ArchitectureInstruction PatternOffset LimitRegister Usage
x86_64mov {0:e}, offset VAR≤ 0xffff_ffff32-bit register
AArch64movz {0}, #:abs_g0_nc:VAR≤ 0xffff64-bit register
RISC-Vlui {0}, %hi(VAR)+addi {0}, {0}, %lo(VAR)≤ 0xffff_ffff64-bit register
LoongArch64lu12i.w {0}, %abs_hi20(VAR)+ori {0}, {0}, %abs_lo12(VAR)≤ 0xffff_ffff64-bit register
flowchart TD
subgraph subGraph4["LoongArch Generation"]
    LA_INST1["lu12i.w {0}, %abs_hi20({VAR})"]
    LA_INST2["ori {0}, {0}, %abs_lo12({VAR})"]
    LA_OUT["out(reg) value"]
end
subgraph subGraph3["RISC-V Generation"]
    RV_INST1["lui {0}, %hi({VAR})"]
    RV_INST2["addi {0}, {0}, %lo({VAR})"]
    RV_OUT["out(reg) value"]
end
subgraph subGraph2["AArch64 Generation"]
    ARM_INST["movz {0}, #:abs_g0_nc:{VAR}"]
    ARM_OUT["out(reg) value"]
end
subgraph subGraph1["x86_64 Generation"]
    X86_INST["mov {0:e}, offset {VAR}"]
    X86_OUT["out(reg) value"]
end
subgraph subGraph0["Symbol Processing"]
    SYMBOL["symbol: &Ident"]
    VAR["VAR = sym #symbol"]
end

ARM_INST --> ARM_OUT
LA_INST1 --> LA_INST2
LA_INST2 --> LA_OUT
RV_INST1 --> RV_INST2
RV_INST2 --> RV_OUT
SYMBOL --> VAR
VAR --> ARM_INST
VAR --> LA_INST1
VAR --> RV_INST1
VAR --> X86_INST
X86_INST --> X86_OUT

Sources: percpu_macros/src/arch.rs(L16 - L50) 

Current CPU Pointer Generation

The gen_current_ptr function generates code to obtain a pointer to a per-CPU variable on the currently executing CPU. Each architecture uses a different approach based on available per-CPU base pointer registers.

Architecture-Specific Register Usage


The x86_64 architecture uses a special approach where the per-CPU base address is stored in the GS segment at a fixed offset (__PERCPU_SELF_PTR), allowing direct addressing with a single instruction that combines base retrieval and offset addition.

Sources: percpu_macros/src/arch.rs(L54 - L88)  percpu_macros/src/arch.rs(L55 - L62) 

Optimized Read Operations

The gen_read_current_raw function generates type-specific optimized read operations for primitive integer types. This avoids the overhead of pointer dereferencing for simple data types.

Type-Specific Assembly Generation

Typex86_64 InstructionRISC-V InstructionLoongArch Instruction
bool,u8mov byte ptr gs:[offset VAR]lbuldx.bu
u16mov word ptr gs:[offset VAR]lhuldx.hu
u32mov dword ptr gs:[offset VAR]lwuldx.wu
u64,usizemov qword ptr gs:[offset VAR]ldldx.d

The boolean type receives special handling by reading as u8 and converting the result to boolean through a != 0 comparison.

Sources: percpu_macros/src/arch.rs(L94 - L181)  percpu_macros/src/arch.rs(L96 - L102)  percpu_macros/src/arch.rs(L114 - L129)  percpu_macros/src/arch.rs(L131 - L150) 

Optimized Write Operations

The gen_write_current_raw function generates type-specific optimized write operations that directly store values to per-CPU variables without intermediate pointer operations.

Write Instruction Mapping

flowchart TD
subgraph subGraph4["Store Instructions"]
    subgraph LoongArch["LoongArch"]
        LA_STB["stx.b val, addr, $r21"]
        LA_STH["stx.h val, addr, $r21"]
        LA_STW["stx.w val, addr, $r21"]
        LA_STD["stx.d val, addr, $r21"]
    end
    subgraph RISC-V["RISC-V"]
        RV_SB["sb val, %lo(VAR)(addr)"]
        RV_SH["sh val, %lo(VAR)(addr)"]
        RV_SW["sw val, %lo(VAR)(addr)"]
        RV_SD["sd val, %lo(VAR)(addr)"]
    end
    subgraph x86_64["x86_64"]
        X86_BYTE["mov byte ptr gs:[offset VAR], val"]
        X86_WORD["mov word ptr gs:[offset VAR], val"]
        X86_DWORD["mov dword ptr gs:[offset VAR], val"]
        X86_QWORD["mov qword ptr gs:[offset VAR], val"]
    end
end
subgraph subGraph0["Input Processing"]
    VAL["val: &Ident"]
    TYPE["ty: &Type"]
    FIXUP["bool -> u8 conversion"]
end

FIXUP --> LA_STB
FIXUP --> LA_STD
FIXUP --> LA_STH
FIXUP --> LA_STW
FIXUP --> RV_SB
FIXUP --> RV_SD
FIXUP --> RV_SH
FIXUP --> RV_SW
FIXUP --> X86_BYTE
FIXUP --> X86_DWORD
FIXUP --> X86_QWORD
FIXUP --> X86_WORD
TYPE --> FIXUP
VAL --> FIXUP

Sources: percpu_macros/src/arch.rs(L187 - L263)  percpu_macros/src/arch.rs(L195 - L211)  percpu_macros/src/arch.rs(L214 - L230)  percpu_macros/src/arch.rs(L232 - L251) 

Platform Compatibility Layer

The code generation system includes a compatibility layer for platforms that don't support inline assembly or per-CPU mechanisms. The macos_unimplemented function wraps generated assembly with conditional compilation directives.

PlatformBehaviorFallback
Non-macOSFull assembly implementationN/A
macOSCompile-time unimplemented panicPointer-based access
Unsupported architecturesFallback to pointer dereferencing*self.current_ptr()

Sources: percpu_macros/src/arch.rs(L4 - L13)  percpu_macros/src/arch.rs(L64)  percpu_macros/src/arch.rs(L171)  percpu_macros/src/arch.rs(L253) 

Integration with Macro System

The architecture-specific code generation functions are called from the main def_percpu macro implementation, which determines whether to generate optimized assembly based on the variable type and enabled features.


Sources: percpu_macros/src/lib.rs(L59 - L60)  percpu_macros/src/lib.rs(L92)  percpu_macros/src/lib.rs(L101 - L105)  percpu_macros/src/lib.rs(L147 - L148) 

Naive Implementation

Relevant source files

Purpose and Scope

This document covers the sp-naive feature implementation of the percpu crate, which provides a simplified fallback for single-CPU systems. This implementation treats all per-CPU variables as regular global variables, eliminating the need for architecture-specific per-CPU registers and memory area management.

For information about the full multi-CPU implementation details, see Memory Management Internals. For architecture-specific code generation in multi-CPU scenarios, see Architecture-Specific Code Generation.

Overview

The naive implementation is activated when the sp-naive cargo feature is enabled. It provides a drastically simplified approach where:

  • Per-CPU variables become standard global variables
  • No per-CPU memory areas are allocated
  • Architecture-specific registers (GS_BASE, TPIDR_ELx, gp, $r21) are not used
  • All runtime functions become no-ops or return constant values

This approach is suitable for single-core embedded systems, testing environments, or scenarios where the overhead of true per-CPU data management is unnecessary.

Runtime Implementation

Naive Runtime Functions

The naive implementation provides stub versions of all runtime functions that would normally manage per-CPU memory areas and registers:

flowchart TD
subgraph subGraph1["Return Values"]
    ZERO1["0"]
    ONE1["1"]
    ZERO2["0"]
    ZERO3["0"]
    NOOP1["no-op"]
    NOOP2["no-op"]
    ONE2["1"]
end
subgraph subGraph0["Naive Runtime Functions"]
    PSZ["percpu_area_size"]
    PNUM["percpu_area_num"]
    PBASE["percpu_area_base"]
    READ["read_percpu_reg"]
    WRITE["write_percpu_reg"]
    INIT_REG["init_percpu_reg"]
    INIT["init"]
end

INIT --> ONE2
INIT_REG --> NOOP2
PBASE --> ZERO2
PNUM --> ONE1
PSZ --> ZERO1
READ --> ZERO3
WRITE --> NOOP1

Function Stub Implementations

FunctionPurposeNaive Return Value
percpu_area_size()Get per-CPU area sizeAlways0
percpu_area_num()Get number of per-CPU areasAlways1
percpu_area_base(cpu_id)Get base address for CPUAlways0
read_percpu_reg()Read per-CPU registerAlways0
write_percpu_reg(tp)Write per-CPU registerNo effect
init_percpu_reg(cpu_id)Initialize CPU registerNo effect
init()Initialize per-CPU systemReturns1

Sources: percpu/src/naive.rs(L3 - L54) 

Code Generation Differences

Macro Implementation Comparison

The naive implementation generates fundamentally different code compared to the standard multi-CPU implementation:

flowchart TD
subgraph subGraph2["Naive Implementation"]
    NAV_OFFSET["gen_offsetaddr_of!(symbol)"]
    NAV_PTR["gen_current_ptraddr_of!(symbol)"]
    NAV_READ["gen_read_current_raw*self.current_ptr()"]
    NAV_WRITE["gen_write_current_rawdirect assignment"]
end
subgraph subGraph1["Standard Implementation"]
    STD_OFFSET["gen_offsetregister + offset"]
    STD_PTR["gen_current_ptrregister-based access"]
    STD_READ["gen_read_current_rawregister + offset"]
    STD_WRITE["gen_write_current_rawregister + offset"]
end
subgraph subGraph0["Source Code"]
    DEF["#[def_percpu]static VAR: T = init;"]
end

DEF --> NAV_OFFSET
DEF --> STD_OFFSET
NAV_OFFSET --> NAV_PTR
NAV_PTR --> NAV_READ
NAV_READ --> NAV_WRITE
STD_OFFSET --> STD_PTR
STD_PTR --> STD_READ
STD_READ --> STD_WRITE

Key Code Generation Functions

The naive macro implementation in percpu_macros/src/naive.rs(L6 - L28)  provides these core functions:

  • gen_offset(): Returns ::core::ptr::addr_of!(symbol) as usize instead of calculating register-relative offsets
  • gen_current_ptr(): Returns ::core::ptr::addr_of!(symbol) for direct global access
  • gen_read_current_raw(): Uses *self.current_ptr() for simple dereference
  • gen_write_current_raw(): Uses direct assignment *(self.current_ptr() as *mut T) = val

Sources: percpu_macros/src/naive.rs(L6 - L28) 

Memory Model Comparison

Standard vs Naive Memory Layout

flowchart TD
subgraph subGraph0["Standard Multi-CPU Model"]
    TEMPLATE["Template .percpu Section"]
    CPU1_AREA["CPU 1 Data Area"]
    CPUN_AREA["CPU N Data Area"]
    CPU1_REG["CPU 1 Register"]
    CPUN_REG["CPU N Register"]
    subgraph subGraph1["Naive Single-CPU Model"]
        GLOBAL_VAR["Global Variable Storage"]
        DIRECT_ACCESS["Direct Memory Access"]
        CPU0_AREA["CPU 0 Data Area"]
        CPU0_REG["CPU 0 Register"]
    end
end

CPU0_REG --> CPU0_AREA
CPU1_REG --> CPU1_AREA
CPUN_REG --> CPUN_AREA
GLOBAL_VAR --> DIRECT_ACCESS
TEMPLATE --> CPU0_AREA
TEMPLATE --> CPU1_AREA
TEMPLATE --> CPUN_AREA

Memory Allocation Differences

AspectStandard ImplementationNaive Implementation
Memory AreasMultiple per-CPU areasSingle global variables
InitializationCopy template to each areaNo copying required
Register UsagePer-CPU register per coreNo registers used
Address CalculationBase + offsetDirect symbol address
Memory Overheadarea_size * num_cpusSize of global variables only

Sources: percpu/src/naive.rs(L1 - L54)  README.md(L71 - L73) 

Feature Integration

Build Configuration

The naive implementation is enabled through the sp-naive cargo feature:

[dependencies]
percpu = { version = "0.1", features = ["sp-naive"] }

When this feature is active:

  • All architecture-specific code paths are bypassed
  • No linker script modifications are required for per-CPU sections
  • The system operates as if there is only one CPU

Compatibility with Other Features

Feature CombinationBehavior
sp-naivealonePure global variable mode
sp-naive+preemptGlobal variables with preemption guards
sp-naive+arm-el2Feature ignored, global variables used

Sources: README.md(L69 - L79) 

Use Cases and Limitations

Appropriate Use Cases

The naive implementation is suitable for:

  • Single-core embedded systems: Where true per-CPU isolation is unnecessary
  • Testing and development: Simplified debugging without architecture concerns
  • Prototype development: Quick implementation without per-CPU complexity
  • Resource-constrained environments: Minimal memory overhead requirements

Limitations

  • No CPU isolation: All "per-CPU" variables are shared globally
  • No scalability: Cannot be extended to multi-CPU systems without feature changes
  • Limited performance benefits: No per-CPU cache locality optimizations
  • Testing coverage gaps: May not expose multi-CPU race conditions during development

Sources: README.md(L71 - L73)  percpu/src/naive.rs(L1 - L2) 

Memory Management Internals

Relevant source files

This document covers the low-level memory management implementation within the percpu crate, focusing on per-CPU data area allocation, address calculation, and register management. This section details the internal mechanisms that support the per-CPU data abstraction layer.

For high-level memory layout concepts, see Architecture and Design. For architecture-specific code generation details, see Architecture-Specific Code Generation. For the single-CPU fallback implementation, see Naive Implementation.

Memory Area Lifecycle

The per-CPU memory management system follows a well-defined lifecycle from initialization through runtime access. The process begins with linker script integration and proceeds through dynamic allocation and template copying.

Initialization Process

The initialization process is controlled by the init() function which ensures single initialization and handles platform-specific memory allocation:

flowchart TD
START["init()"]
CHECK["IS_INIT.compare_exchange()"]
RETURN_ZERO["return 0"]
PLATFORM_CHECK["Platform Check"]
ALLOC["std::alloc::alloc()"]
USE_LINKER["Use _percpu_start"]
SET_BASE["PERCPU_AREA_BASE.call_once()"]
COPY_LOOP["Copy Template Loop"]
COPY_PRIMARY["copy_nonoverlapping(base, secondary_base, size)"]
RETURN_NUM["return num"]
END["END"]

ALLOC --> SET_BASE
CHECK --> PLATFORM_CHECK
CHECK --> RETURN_ZERO
COPY_LOOP --> COPY_PRIMARY
COPY_PRIMARY --> COPY_LOOP
COPY_PRIMARY --> RETURN_NUM
PLATFORM_CHECK --> ALLOC
PLATFORM_CHECK --> USE_LINKER
RETURN_NUM --> END
RETURN_ZERO --> END
SET_BASE --> COPY_LOOP
START --> CHECK
USE_LINKER --> COPY_LOOP

Memory Area Initialization Sequence Sources: percpu/src/imp.rs(L56 - L86) 

The IS_INIT atomic boolean prevents re-initialization using compare-and-swap semantics. On Linux platforms, the system dynamically allocates memory since the .percpu section is not loaded in ELF files. On bare metal (target_os = "none"), the linker-defined symbols provide the memory area directly.

Template Copying Mechanism

Per-CPU areas are initialized by copying from a primary template area to secondary CPU areas:

flowchart TD
TEMPLATE["Primary CPU Areabase = percpu_area_base(0)"]
CPU1["CPU 1 Areapercpu_area_base(1)"]
CPU2["CPU 2 Areapercpu_area_base(2)"]
CPUN["CPU N Areapercpu_area_base(N)"]
COPY1["copy_nonoverlapping(base, base+offset1, size)"]
COPY2["copy_nonoverlapping(base, base+offset2, size)"]
COPYN["copy_nonoverlapping(base, base+offsetN, size)"]

CPU1 --> COPY1
CPU2 --> COPY2
CPUN --> COPYN
TEMPLATE --> CPU1
TEMPLATE --> CPU2
TEMPLATE --> CPUN

Template to Per-CPU Area Copying Sources: percpu/src/imp.rs(L76 - L84) 

Address Calculation and Layout

The memory layout uses 64-byte aligned areas calculated through several key functions that work together to provide consistent addressing across different platforms.

Address Calculation Functions

flowchart TD
subgraph subGraph0["Platform Specific"]
    LINUX_BASE["PERCPU_AREA_BASE.get()"]
    BAREMETAL_BASE["_percpu_start as usize"]
end
START_SYM["_percpu_start"]
AREA_BASE["percpu_area_base(cpu_id)"]
SIZE_CALC["percpu_area_size()"]
ALIGN["align_up_64()"]
FINAL_ADDR["Final Address"]
LOAD_START["_percpu_load_start"]
LOAD_END["_percpu_load_end"]
OFFSET["percpu_symbol_offset!()"]
NUM_CALC["percpu_area_num()"]
END_SYM["_percpu_end"]

ALIGN --> AREA_BASE
AREA_BASE --> FINAL_ADDR
AREA_BASE --> NUM_CALC
BAREMETAL_BASE --> AREA_BASE
END_SYM --> NUM_CALC
LINUX_BASE --> AREA_BASE
LOAD_END --> SIZE_CALC
LOAD_START --> SIZE_CALC
SIZE_CALC --> ALIGN
SIZE_CALC --> OFFSET
START_SYM --> AREA_BASE

Address Calculation Dependencies Sources: percpu/src/imp.rs(L21 - L44) 

The align_up_64() function ensures all per-CPU areas are aligned to 64-byte boundaries for cache line optimization:

FunctionPurposeReturn Value
percpu_area_size()Template area sizeSize in bytes from linker symbols
percpu_area_num()Number of CPU areasTotal section size / aligned area size
percpu_area_base(cpu_id)CPU-specific base addressBase + (cpu_id * aligned_size)
align_up_64(val)64-byte alignment(val + 63) & !63

Sources: percpu/src/imp.rs(L5 - L8)  percpu/src/imp.rs(L25 - L30)  percpu/src/imp.rs(L32 - L44)  percpu/src/imp.rs(L20 - L23) 

Register Management Internals

Each CPU architecture uses a dedicated register to hold the per-CPU data base address. The register management system provides unified access across different instruction sets.

Architecture-Specific Register Access

flowchart TD
READ_REG["read_percpu_reg()"]
ARCH_DETECT["Architecture Detection"]
WRITE_REG["write_percpu_reg(tp)"]
X86["target_arch = x86_64"]
ARM64["target_arch = aarch64"]
RISCV["target_arch = riscv32/64"]
LOONG["target_arch = loongarch64"]
X86_READ["rdmsr(IA32_GS_BASE)or SELF_PTR.read_current_raw()"]
X86_WRITE["wrmsr(IA32_GS_BASE, tp)or arch_prctl(ARCH_SET_GS, tp)"]
ARM_READ["mrs TPIDR_EL1/EL2"]
ARM_WRITE["msr TPIDR_EL1/EL2, tp"]
RISCV_READ["mv tp, gp"]
RISCV_WRITE["mv gp, tp"]
LOONG_READ["move tp, $r21"]
LOONG_WRITE["move $r21, tp"]

ARCH_DETECT --> ARM64
ARCH_DETECT --> LOONG
ARCH_DETECT --> RISCV
ARCH_DETECT --> X86
ARM64 --> ARM_READ
ARM64 --> ARM_WRITE
LOONG --> LOONG_READ
LOONG --> LOONG_WRITE
READ_REG --> ARCH_DETECT
RISCV --> RISCV_READ
RISCV --> RISCV_WRITE
WRITE_REG --> ARCH_DETECT
X86 --> X86_READ
X86 --> X86_WRITE

Per-CPU Register Access by Architecture Sources: percpu/src/imp.rs(L88 - L117)  percpu/src/imp.rs(L119 - L156) 

Register Initialization Process

The init_percpu_reg(cpu_id) function combines address calculation with register writing:

flowchart TD
INIT_CALL["init_percpu_reg(cpu_id)"]
CALC_BASE["percpu_area_base(cpu_id)"]
GET_TP["tp = calculated_address"]
WRITE_REG["write_percpu_reg(tp)"]
REG_SET["CPU register updated"]

CALC_BASE --> GET_TP
GET_TP --> WRITE_REG
INIT_CALL --> CALC_BASE
WRITE_REG --> REG_SET

Register Initialization Flow Sources: percpu/src/imp.rs(L158 - L168) 

The x86_64 architecture requires special handling with the SELF_PTR variable that stores the per-CPU base address within the per-CPU area itself:

flowchart TD
X86_SPECIAL["x86_64 Special Case"]
SELF_PTR_DEF["SELF_PTR: usize = 0"]
PERCPU_MACRO["#[def_percpu] static"]
GS_ACCESS["gs:SELF_PTR access"]
CIRCULAR["Circular reference for self-location"]

GS_ACCESS --> CIRCULAR
PERCPU_MACRO --> GS_ACCESS
SELF_PTR_DEF --> PERCPU_MACRO
X86_SPECIAL --> SELF_PTR_DEF

x86_64 Self-Pointer Mechanism Sources: percpu/src/imp.rs(L174 - L178) 

Linker Integration Details

The linker script integration creates the necessary memory layout for per-CPU data areas through carefully designed section definitions.

Linker Script Structure

flowchart TD
LINKER_START[".percpu section definition"]
SYMBOLS["Symbol Generation"]
START_SYM["_percpu_start = ."]
END_SYM["_percpu_end = _percpu_start + SIZEOF(.percpu)"]
SECTION_DEF[".percpu 0x0 (NOLOAD) : AT(_percpu_start)"]
LOAD_START["_percpu_load_start = ."]
CONTENT["(.percpu .percpu.)"]
LOAD_END["_percpu_load_end = ."]
ALIGN_EXPAND[". = _percpu_load_start + ALIGN(64) * CPU_NUM"]
VARS["Per-CPU Variables"]
SPACE_RESERVE["Reserved space for all CPUs"]

ALIGN_EXPAND --> SPACE_RESERVE
CONTENT --> VARS
LINKER_START --> SECTION_DEF
LINKER_START --> SYMBOLS
SECTION_DEF --> ALIGN_EXPAND
SECTION_DEF --> CONTENT
SECTION_DEF --> LOAD_END
SECTION_DEF --> LOAD_START
SYMBOLS --> END_SYM
SYMBOLS --> START_SYM

Linker Script Memory Layout Sources: percpu/test_percpu.x(L1 - L16) 

SymbolPurposeUsage
_percpu_startSection start addressBase address calculation
_percpu_endSection end addressTotal size calculation
_percpu_load_startTemplate startSize calculation for copying
_percpu_load_endTemplate endSize calculation for copying

The NOLOAD directive ensures the section occupies space but isn't loaded from the ELF file, while AT(_percpu_start) specifies the load address for the template data.

Build System Integration

The build system conditionally applies linker scripts based on target platform:

flowchart TD
BUILD_CHECK["build.rs"]
LINUX_CHECK["cfg!(target_os = linux)"]
FEATURE_CHECK["cfg!(not(feature = sp-naive))"]
APPLY_SCRIPT["rustc-link-arg-tests=-T test_percpu.x"]
NO_PIE["rustc-link-arg-tests=-no-pie"]

BUILD_CHECK --> LINUX_CHECK
FEATURE_CHECK --> APPLY_SCRIPT
FEATURE_CHECK --> NO_PIE
LINUX_CHECK --> FEATURE_CHECK

Build System Linker Integration Sources: percpu/build.rs(L3 - L9) 

Platform-Specific Considerations

Different target platforms require distinct memory management strategies due to varying levels of hardware access and memory management capabilities.

Platform Memory Allocation Matrix

PlatformAllocation MethodBase Address SourceRegister Access
Linux userspacestd::alloc::alloc()PERCPU_AREA_BASEarch_prctl()syscall
Bare metalLinker symbols_percpu_startDirect MSR/register
Kernel/HypervisorLinker symbols_percpu_startPrivileged instructions

The PERCPU_AREA_BASE is a spin::once::Once<usize> that ensures thread-safe initialization of the dynamically allocated base address on platforms where the linker section is not available.

Error Handling and Validation

The system includes several validation mechanisms:

  • Atomic initialization checking with IS_INIT
  • Address range validation on bare metal platforms
  • Alignment verification through align_up_64()
  • Platform capability detection through conditional compilation

Sources: percpu/src/imp.rs(L1 - L179)  percpu/test_percpu.x(L1 - L16)  percpu/build.rs(L1 - L9) 

Development and Testing

Relevant source files

This document provides an overview of the development workflows, testing infrastructure, and continuous integration setup for the percpu crate ecosystem. It covers the automated testing pipeline, cross-platform validation, and contributor guidelines for maintaining code quality across multiple CPU architectures.

For detailed testing procedures and test case development, see Testing Guide. For build system configuration and cross-compilation setup, see Build System. For contribution guidelines and development environment setup, see Contributing.

Development Workflow Overview

The percpu project follows a comprehensive development workflow that ensures code quality and cross-platform compatibility through automated testing and validation. The system is designed to support multiple CPU architectures and feature configurations while maintaining strict quality standards.

Testing Infrastructure

The project uses a multi-layered testing approach that validates functionality across different target platforms and feature combinations:


Sources: percpu/tests/test_percpu.rs(L1 - L163)  .github/workflows/ci.yml(L10 - L32) 

CI/CD Pipeline Architecture

The continuous integration system validates all changes across multiple dimensions using GitHub Actions:

flowchart TD
subgraph subGraph4["Target Matrix"]
    T1["x86_64-unknown-linux-gnu"]
    T2["x86_64-unknown-none"]
    T3["riscv64gc-unknown-none-elf"]
    T4["aarch64-unknown-none-softfloat"]
    T5["loongarch64-unknown-none-softfloat"]
end
subgraph Documentation["Documentation"]
    DOC_BUILD["cargo doc --no-deps"]
    PAGES_DEPLOY["GitHub Pages Deploy"]
end
subgraph subGraph2["Quality Checks"]
    FORMAT["cargo fmt --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test"]
end
subgraph subGraph1["CI Matrix Strategy"]
    NIGHTLY["nightly Toolchain"]
    TARGETS["Matrix Targets"]
    FEATURES["Feature Combinations"]
end
subgraph subGraph0["GitHub Actions Workflow"]
    TRIGGER["Push/PR Trigger"]
    CI_JOB["ci Job"]
    DOC_JOB["doc Job"]
end

BUILD --> TEST
CI_JOB --> FEATURES
CI_JOB --> NIGHTLY
CI_JOB --> TARGETS
CLIPPY --> BUILD
DOC_BUILD --> PAGES_DEPLOY
DOC_JOB --> DOC_BUILD
FEATURES --> BUILD
FEATURES --> CLIPPY
FEATURES --> TEST
FORMAT --> CLIPPY
NIGHTLY --> FORMAT
TARGETS --> T1
TARGETS --> T2
TARGETS --> T3
TARGETS --> T4
TARGETS --> T5
TRIGGER --> CI_JOB
TRIGGER --> DOC_JOB

Sources: .github/workflows/ci.yml(L1 - L56) 

Test Execution Flow

The testing system follows a structured approach that validates both basic functionality and advanced features like remote CPU access:


Sources: percpu/tests/test_percpu.rs(L34 - L105)  percpu/tests/test_percpu.rs(L107 - L162) 

Cross-Platform Validation

The build system validates functionality across all supported architectures using a comprehensive matrix strategy:

Target PlatformBuildClippyTestsFeatures
x86_64-unknown-linux-gnusp-naive, default
x86_64-unknown-none-preempt,arm-el2
riscv64gc-unknown-none-elf-preempt,arm-el2
aarch64-unknown-none-softfloat-preempt,arm-el2
loongarch64-unknown-none-softfloat-preempt,arm-el2

The CI pipeline ensures that all changes maintain compatibility across the entire target matrix before merging.

Sources: .github/workflows/ci.yml(L10 - L32) 

Quality Assurance Tools

The project enforces code quality through multiple automated tools:

Formatting and Linting

  • Rustfmt: Enforces consistent code formatting across the codebase [cargo fmt --all -- --check](https://github.com/arceos-org/percpu/blob/89c8a54c/cargo fmt --all -- --check)()
  • Clippy: Provides additional linting and best practice enforcement <FileRef file-url="[https://github.com/arceos-org/percpu/blob/89c8a54c/cargo](https://github.com/arceos-org/percpu/blob/89c8a54c/%60cargo) clippy --target ${{ matrix.targets }} --features "preempt,arm-el2"" undefined file-path="cargo clippy --target ${{ matrix.targets }} --features "preempt,arm-el2"">Hii()

Documentation Generation

  • Rust Documentation: Automatically builds and deploys API documentation [cargo doc --no-deps](https://github.com/arceos-org/percpu/blob/89c8a54c/cargo doc --no-deps)()
  • GitHub Pages: Hosts documentation with automatic updates on main branch changes [.github/workflows/ci.yml(L49 - L55)&emsp;](https://github.com/arceos-org/percpu/blob/89c8a54c/.github/workflows/ci.yml#L49-L55)

Feature Flag Testing

The system validates different feature combinations to ensure compatibility:

  • sp-naive: Single-processor fallback mode testing <FileRef file-url="[https://github.com/arceos-org/percpu/blob/89c8a54c/cargo](https://github.com/arceos-org/percpu/blob/89c8a54c/%60cargo) test --target ${{ matrix.targets }} --features "sp-naive"" undefined file-path="cargo test --target ${{ matrix.targets }} --features "sp-naive"">Hii()
  • preempt and arm-el2: Advanced feature testing across all build targets [.github/workflows/ci.yml(L25 - L27)&emsp;](https://github.com/arceos-org/percpu/blob/89c8a54c/.github/workflows/ci.yml#L25-L27)

Sources: .github/workflows/ci.yml(L20 - L32)  .github/workflows/ci.yml(L40 - L55) 

Testing Guide

Relevant source files

This document explains how to run tests, understand the test structure, and add new test cases for the percpu crate ecosystem. The testing framework validates per-CPU data management across multiple architectures and feature configurations.

For information about the build system and CI/CD pipeline details, see Build System. For general development guidelines, see Contributing.

Test Structure Overview

The percpu crate uses a comprehensive testing strategy that validates functionality across different architectures, feature flags, and execution modes. The test suite is designed to work with both real per-CPU implementations and the single-CPU fallback mode.

Test Architecture


Sources: percpu/tests/test_percpu.rs(L1 - L163) 

Linker Script Configuration

The test environment uses a custom linker script to set up the .percpu section for testing with multiple simulated CPUs.

flowchart TD
subgraph subGraph1["Memory Layout"]
    TEMPLATE["Template data"]
    CPU0_AREA["CPU 0 area"]
    CPU1_AREA["CPU 1 area"]
    CPU2_AREA["CPU 2 area"]
    CPU3_AREA["CPU 3 area"]
end
subgraph subGraph0["Linker Script Structure"]
    CPU_NUM["CPU_NUM = 4"]
    PERCPU_START["_percpu_start symbol"]
    PERCPU_SECTION[".percpu section"]
    PERCPU_END["_percpu_end symbol"]
    ALIGNMENT["64-byte alignment"]
end

ALIGNMENT --> CPU0_AREA
ALIGNMENT --> CPU1_AREA
ALIGNMENT --> CPU2_AREA
ALIGNMENT --> CPU3_AREA
CPU0_AREA --> CPU1_AREA
CPU1_AREA --> CPU2_AREA
CPU2_AREA --> CPU3_AREA
CPU_NUM --> PERCPU_SECTION
PERCPU_END --> CPU3_AREA
PERCPU_START --> TEMPLATE
TEMPLATE --> CPU0_AREA

Sources: percpu/test_percpu.x(L1 - L17) 

Running Tests Locally

Basic Test Execution

To run the standard test suite on x86_64 Linux:

# Run with default features
cargo test -- --nocapture

# Run with sp-naive feature (single-CPU mode)
cargo test --features "sp-naive" -- --nocapture

The --nocapture flag ensures that debug output from the tests is displayed, which includes offset information and per-CPU area details.

Architecture-Specific Testing

Tests can be run on different target architectures, though unit tests only execute on x86_64-unknown-linux-gnu:

# Cross-compile for other architectures (build only)
cargo build --target riscv64gc-unknown-none-elf --features "preempt,arm-el2"
cargo build --target aarch64-unknown-none-softfloat --features "preempt,arm-el2"
cargo build --target loongarch64-unknown-none-softfloat --features "preempt,arm-el2"

Feature Flag Testing

The test suite validates different feature combinations:

FeaturePurposeTest Impact
sp-naiveSingle-CPU fallbackDisables remote CPU access tests
preemptPreemption safetyEnablesNoPreemptGuardintegration
arm-el2AArch64 EL2 supportUsesTPIDR_EL2instead ofTPIDR_EL1

Sources: .github/workflows/ci.yml(L25 - L32) 

Test Coverage and Scenarios

Data Type Coverage

The test suite validates per-CPU variables of different types and sizes:

flowchart TD
subgraph subGraph2["Test Operations"]
    OFFSET_CALC["offset() method"]
    CURRENT_PTR["current_ptr() validation"]
    READ_CURRENT["read_current() values"]
    WRITE_CURRENT["write_current() updates"]
    WITH_CURRENT_OP["with_current() closures"]
end
subgraph subGraph1["Complex Types"]
    STRUCT_TEST["Struct: composite"]
    STRUCT_FIELDS["foo: usize, bar: u8"]
end
subgraph subGraph0["Primitive Types"]
    BOOL_TEST["bool: 1 byte"]
    U8_TEST["u8: 1 byte"]
    U16_TEST["u16: 2 bytes"]
    U32_TEST["u32: 4 bytes"]
    U64_TEST["u64: 8 bytes"]
    USIZE_TEST["usize: arch-dependent"]
end

BOOL_TEST --> OFFSET_CALC
CURRENT_PTR --> READ_CURRENT
OFFSET_CALC --> CURRENT_PTR
READ_CURRENT --> WRITE_CURRENT
STRUCT_FIELDS --> WITH_CURRENT_OP
STRUCT_TEST --> OFFSET_CALC
STRUCT_TEST --> STRUCT_FIELDS
U16_TEST --> OFFSET_CALC
U32_TEST --> OFFSET_CALC
U64_TEST --> OFFSET_CALC
U8_TEST --> OFFSET_CALC
USIZE_TEST --> OFFSET_CALC
WRITE_CURRENT --> WITH_CURRENT_OP

Sources: percpu/tests/test_percpu.rs(L7 - L31)  percpu/tests/test_percpu.rs(L52 - L58) 

Remote CPU Access Testing

When not using the sp-naive feature, the test suite validates remote CPU access patterns:

flowchart TD
subgraph subGraph2["CPU Simulation"]
    CPU0_BASE["percpu_area_base(0)"]
    CPU1_BASE["percpu_area_base(1)"]
    REG_SWITCH["write_percpu_reg()"]
end
subgraph subGraph1["Test Sequence"]
    WRITE_REMOTE["Write to CPU 1"]
    READ_REMOTE["Read from CPU 1"]
    SWITCH_CPU["Switch to CPU 1"]
    VERIFY_LOCAL["Verify local access"]
end
subgraph subGraph0["Remote Access Methods"]
    REMOTE_PTR["remote_ptr(cpu_id)"]
    REMOTE_REF["remote_ref_raw(cpu_id)"]
    REMOTE_MUT["remote_ref_mut_raw(cpu_id)"]
end

CPU1_BASE --> REG_SWITCH
READ_REMOTE --> SWITCH_CPU
REG_SWITCH --> SWITCH_CPU
REMOTE_MUT --> WRITE_REMOTE
REMOTE_PTR --> READ_REMOTE
SWITCH_CPU --> VERIFY_LOCAL

Sources: percpu/tests/test_percpu.rs(L107 - L162) 

CI/CD Pipeline

Architecture Matrix Testing

The GitHub Actions CI pipeline tests across multiple architectures and configurations:

flowchart TD
subgraph subGraph3["Feature Testing"]
    PREEMPT_FEAT["preempt feature"]
    ARM_EL2_FEAT["arm-el2 feature"]
    SP_NAIVE_FEAT["sp-naive feature"]
end
subgraph subGraph2["CI Steps"]
    TOOLCHAIN["Setup Rust toolchain"]
    FORMAT_CHECK["Format check"]
    CLIPPY_CHECK["Clippy linting"]
    BUILD_STEP["Build step"]
    UNIT_TEST["Unit tests"]
end
subgraph subGraph1["Target Matrix"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    AARCH64["aarch64-unknown-none-softfloat"]
    LOONGARCH["loongarch64-unknown-none-softfloat"]
end
subgraph subGraph0["CI Jobs"]
    CI_JOB["ci job"]
    DOC_JOB["doc job"]
end

AARCH64 --> BUILD_STEP
ARM_EL2_FEAT --> CLIPPY_CHECK
BUILD_STEP --> UNIT_TEST
CI_JOB --> TOOLCHAIN
CLIPPY_CHECK --> BUILD_STEP
FORMAT_CHECK --> CLIPPY_CHECK
LOONGARCH --> BUILD_STEP
PREEMPT_FEAT --> CLIPPY_CHECK
RISCV --> BUILD_STEP
SP_NAIVE_FEAT --> UNIT_TEST
TOOLCHAIN --> FORMAT_CHECK
X86_LINUX --> UNIT_TEST
X86_NONE --> BUILD_STEP

Sources: .github/workflows/ci.yml(L1 - L56) 

Test Execution Strategy

TargetFormatClippyBuildUnit Tests
x86_64-unknown-linux-gnu
x86_64-unknown-none
riscv64gc-unknown-none-elf
aarch64-unknown-none-softfloat
loongarch64-unknown-none-softfloat

Unit tests only run on x86_64-unknown-linux-gnu because they require userspace Linux environment for per-CPU area simulation.

Sources: .github/workflows/ci.yml(L29 - L32) 

Platform-Specific Considerations

Linux Testing Requirements

The test suite has specific platform requirements:

  • Excluded platforms: macOS is explicitly excluded via #![cfg(not(target_os = "macos"))]
  • Linux-only tests: Main test function uses #[cfg(target_os = "linux")]
  • Userspace simulation: Tests simulate per-CPU areas in userspace using init() and manual register management

Test Environment Setup

The test environment requires:

  1. Per-CPU area initialization: Calls init() to allocate per-CPU memory areas
  2. Register simulation: Uses write_percpu_reg() and read_percpu_reg() to simulate CPU switching
  3. Memory validation: Validates that calculated pointers match expected base + offset calculations

Architecture-Specific Limitations

Different architectures have varying test coverage:

  • x86_64: Full unit test coverage including remote access
  • AArch64/RISC-V/LoongArch: Build-time validation only
  • Bare metal targets: No userspace test execution

Sources: percpu/tests/test_percpu.rs(L1)  percpu/tests/test_percpu.rs(L33 - L34) 

Adding New Test Cases

Test Structure Guidelines

When adding new test cases, follow the established patterns:

  1. Variable definition: Use #[def_percpu] with zero-initialized values
  2. Offset validation: Test the offset() method for memory layout
  3. Pointer validation: Verify current_ptr() calculations
  4. Value operations: Test read_current(), write_current(), and with_current()
  5. Remote access: Include remote CPU access tests for non-naive mode

Example Test Pattern

New test cases should follow this structure:

// Define per-CPU variable
#[def_percpu]
static NEW_VAR: NewType = NewType::default();

// Test offset and pointer calculations
assert_eq!(base + NEW_VAR.offset(), NEW_VAR.current_ptr() as usize);

// Test value operations
NEW_VAR.write_current(test_value);
assert_eq!(NEW_VAR.read_current(), test_value);

// Test remote access (non-naive only)
#[cfg(not(feature = "sp-naive"))]
unsafe {
    *NEW_VAR.remote_ref_mut_raw(1) = remote_value;
    assert_eq!(*NEW_VAR.remote_ptr(1), remote_value);
}

Feature-Specific Testing

New tests should account for different feature configurations:

  • Use cfg!(feature = "sp-naive") for conditional logic
  • Wrap remote access tests with #[cfg(not(feature = "sp-naive"))]
  • Consider preemption safety when adding tests for the preempt feature

Sources: percpu/tests/test_percpu.rs(L61 - L105)  percpu/tests/test_percpu.rs(L107 - L162) 

Build System

Relevant source files

This document covers the build process, CI/CD pipeline, and cross-compilation setup for the percpu crate ecosystem. The build system is designed to support multiple CPU architectures and testing environments while maintaining compatibility across bare-metal and hosted environments.

For information about feature flag configuration, see Feature Flags Configuration. For testing procedures and test structure, see Testing Guide.

Overview

The percpu build system consists of several components working together to support cross-platform compilation and testing:

  • Cargo workspace with two crates: percpu (runtime) and percpu_macros (procedural macros)
  • Multi-target CI/CD pipeline testing across five different architectures
  • Custom build scripts for linker integration and test configuration
  • Feature-based conditional compilation for different deployment scenarios

Sources: .github/workflows/ci.yml(L1 - L56)  Cargo.lock(L1 - L149)  percpu/build.rs(L1 - L10) 

Cross-Compilation Architecture

The build system supports a comprehensive matrix of target platforms, each requiring specific toolchain and runtime configurations:

flowchart TD
subgraph subGraph2["Build Configurations"]
    FEATURES_PREEMPT["preempt,arm-el2Full Feature Set"]
    FEATURES_NAIVE["sp-naiveSingle-CPU Fallback"]
    CUSTOM_LINKER["Custom Linker Scriptstest_percpu.x"]
end
subgraph subGraph1["Toolchain Requirements"]
    NIGHTLY["Rust Nightly Toolchain"]
    COMPONENTS["rust-src, clippy, rustfmt"]
    TARGETS["Cross-compilation Targets"]
end
subgraph subGraph0["Target Platforms"]
    LINUX["x86_64-unknown-linux-gnuHosted Environment"]
    BARE_X86["x86_64-unknown-noneBare Metal x86_64"]
    BARE_RISCV["riscv64gc-unknown-none-elfBare Metal RISC-V"]
    BARE_ARM["aarch64-unknown-none-softfloatBare Metal AArch64"]
    BARE_LOONG["loongarch64-unknown-none-softfloatBare Metal LoongArch"]
end

COMPONENTS --> NIGHTLY
CUSTOM_LINKER --> LINUX
FEATURES_NAIVE --> LINUX
FEATURES_PREEMPT --> BARE_ARM
FEATURES_PREEMPT --> BARE_LOONG
FEATURES_PREEMPT --> BARE_RISCV
FEATURES_PREEMPT --> BARE_X86
NIGHTLY --> BARE_ARM
NIGHTLY --> BARE_LOONG
NIGHTLY --> BARE_RISCV
NIGHTLY --> BARE_X86
NIGHTLY --> LINUX
TARGETS --> NIGHTLY

Sources: .github/workflows/ci.yml(L12 - L19)  .github/workflows/ci.yml(L25 - L27) 

CI/CD Pipeline

The automated build and test pipeline runs on every push and pull request, ensuring code quality and cross-platform compatibility:

Build Matrix Configuration

Target PlatformTest EnvironmentFeature SetSpecial Requirements
x86_64-unknown-linux-gnuUbuntu 22.04sp-naive+ defaultUnit tests enabled
x86_64-unknown-noneUbuntu 22.04preempt,arm-el2Bare metal
riscv64gc-unknown-none-elfUbuntu 22.04preempt,arm-el2Bare metal
aarch64-unknown-none-softfloatUbuntu 22.04preempt,arm-el2Bare metal
loongarch64-unknown-none-softfloatUbuntu 22.04preempt,arm-el2Bare metal

Pipeline Stages

flowchart TD
subgraph subGraph0["CI Job"]
    CLIPPY["cargo clippy --target TARGET --features preempt,arm-el2"]
    BUILD["cargo build --target TARGET --features preempt,arm-el2"]
    TEST["cargo test --target x86_64-unknown-linux-gnu"]
    subgraph subGraph1["Doc Job"]
        DOC_CHECKOUT["actions/checkout@v4"]
        DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
        DOC_BUILD["cargo doc --no-deps"]
        DEPLOY["JamesIves/github-pages-deploy-action@v4"]
        CHECKOUT["actions/checkout@v4"]
        TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
        VERSION_CHECK["rustc --version --verbose"]
        FORMAT["cargo fmt --all --check"]
    end
end

BUILD --> TEST
CHECKOUT --> TOOLCHAIN
CLIPPY --> BUILD
DOC_BUILD --> DEPLOY
DOC_CHECKOUT --> DOC_TOOLCHAIN
DOC_TOOLCHAIN --> DOC_BUILD
FORMAT --> CLIPPY
TOOLCHAIN --> VERSION_CHECK
VERSION_CHECK --> FORMAT

Sources: .github/workflows/ci.yml(L6 - L33)  .github/workflows/ci.yml(L34 - L56) 

Build Script Integration

The percpu crate includes a custom build script that handles linker configuration for testing environments:

Conditional Linker Configuration

The build script in percpu/build.rs(L1 - L10)  performs platform-specific setup:

// Pseudo-code representation of build.rs logic
if target_os == "linux" && !feature("sp-naive") {
    // Add custom linker script for per-CPU section testing
    add_link_arg("-no-pie");
    add_link_arg("-T test_percpu.x");
}

This configuration ensures:

  • Position-independent execution disabled (-no-pie) for consistent memory layout
  • Custom linker script (test_percpu.x) defines .percpu section placement
  • Conditional application only for Linux userspace testing without naive fallback

Sources: percpu/build.rs(L4 - L8) 

Dependency Management

The build system manages a carefully curated set of dependencies optimized for cross-platform compatibility:

Core Runtime Dependencies

CrateVersionPurposePlatform Support
cfg-if1.0.0Conditional compilationAll platforms
kernel_guard0.1.2Preemption safetyno_std compatible
spin0.9.8Synchronization primitivesno_std compatible
x860.52.0x86-specific operationsx86_64 only

Macro Dependencies

CrateVersionPurpose
proc-macro21.0.93Token manipulation
quote1.0.38Code generation
syn2.0.96AST parsing

Sources: Cargo.lock(L61 - L78) 

Documentation Generation

The build system includes automated documentation generation and deployment:

Documentation Pipeline

flowchart TD
subgraph Deployment["Deployment"]
    PAGES["GitHub Pages"]
    BRANCH["gh-pages Branch"]
    DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph0["Documentation Build"]
    SOURCE["Source Code + Doc Comments"]
    RUSTDOC["cargo doc --no-deps"]
    FLAGS["RUSTDOCFLAGS Environment"]
    OUTPUT["target/doc Directory"]
end

BRANCH --> PAGES
DEPLOY_ACTION --> BRANCH
FLAGS --> RUSTDOC
OUTPUT --> DEPLOY_ACTION
RUSTDOC --> OUTPUT
SOURCE --> RUSTDOC

Documentation Configuration

The documentation build uses specific flags for enhanced output:

  • -Zunstable-options --enable-index-page - Enables unified index page
  • -D rustdoc::broken_intra_doc_links - Treats broken links as errors
  • -D missing-docs - Requires documentation for all public items

Sources: .github/workflows/ci.yml(L42 - L56) 

Feature-Based Build Variants

The build system supports multiple feature combinations for different deployment scenarios:

Feature Matrix

Feature SetTarget Use CasePlatformsBuild Behavior
DefaultMulti-CPU systemsBare metal targetsFull per-CPU implementation
sp-naiveSingle-CPU systemsLinux userspaceGlobal variable fallback
preemptPreemption-awareAll platformsNoPreemptGuard integration
arm-el2Hypervisor modeAArch64 onlyEL2 register usage

Build Commands by Use Case

# Bare metal development
cargo build --target x86_64-unknown-none --features "preempt,arm-el2"

# Linux userspace testing
cargo test --target x86_64-unknown-linux-gnu --features "sp-naive"

# Documentation generation
cargo doc --no-deps

Sources: .github/workflows/ci.yml(L25 - L32) 

Contributing

Relevant source files

This page provides comprehensive guidelines for contributing to the percpu crate ecosystem, including development environment setup, code standards, testing requirements, and the pull request workflow. The information covers both runtime implementation contributions to the main percpu crate and compile-time macro contributions to the percpu_macros crate.

For information about the testing infrastructure and test execution, see Testing Guide. For details about the build system and CI/CD pipeline implementation, see Build System.

Development Environment Setup

Prerequisites and Toolchain

The percpu crate requires a nightly Rust toolchain due to its use of unstable features for low-level per-CPU data management. The development environment must support cross-compilation to multiple target architectures.

flowchart TD
subgraph subGraph2["Development Tools"]
    FMT["cargo fmt --check"]
    CLIPPY["cargo clippy"]
    BUILD["cargo build"]
    TEST["cargo test"]
    DOC["cargo doc"]
end
subgraph subGraph1["Required Targets"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    AARCH64["aarch64-unknown-none-softfloat"]
    LOONG["loongarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Development Environment"]
    RUST["nightly toolchain"]
    COMPONENTS["rust-src, clippy, rustfmt"]
    TARGETS["Multi-target support"]
end

COMPONENTS --> CLIPPY
COMPONENTS --> FMT
RUST --> BUILD
RUST --> COMPONENTS
RUST --> DOC
RUST --> TARGETS
RUST --> TEST
TARGETS --> AARCH64
TARGETS --> LOONG
TARGETS --> RISCV
TARGETS --> X86_LINUX
TARGETS --> X86_NONE

Required Installation Commands:

rustup toolchain install nightly
rustup component add --toolchain nightly rust-src clippy rustfmt
rustup target add x86_64-unknown-linux-gnu x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat
rustup target add loongarch64-unknown-none-softfloat

Sources: .github/workflows/ci.yml(L15 - L19) 

Workspace Structure

The percpu workspace contains two main crates with distinct development considerations:

CratePurposeDevelopment Focus
percpuRuntime implementationArchitecture-specific assembly, memory management, register access
percpu_macrosProcedural macrosCode generation, AST manipulation, cross-platform abstraction

Contributors must understand both the runtime behavior and compile-time code generation when making changes that affect the public API.

Sources: Based on system architecture diagrams

Code Quality Standards

Formatting and Linting Requirements

All contributions must pass automated code quality checks enforced by the CI pipeline:

flowchart TD
subgraph subGraph1["Quality Gates"]
    FMT_PASS["Formatting Compliant"]
    CLIPPY_PASS["Lint-free Code"]
    BUILD_PASS["All Targets Compile"]
    TEST_PASS["Tests Pass"]
end
subgraph subGraph0["Code Quality Pipeline"]
    SUBMIT["Code Submission"]
    FMT_CHECK["cargo fmt --check"]
    CLIPPY_CHECK["cargo clippy"]
    BUILD_CHECK["Multi-target Build"]
    TEST_CHECK["Test Execution"]
end

BUILD_CHECK --> BUILD_PASS
BUILD_PASS --> TEST_CHECK
CLIPPY_CHECK --> CLIPPY_PASS
CLIPPY_PASS --> BUILD_CHECK
FMT_CHECK --> FMT_PASS
FMT_PASS --> CLIPPY_CHECK
SUBMIT --> FMT_CHECK
TEST_CHECK --> TEST_PASS

Pre-submission Checklist:

  • Run cargo fmt --all -- --check to verify formatting
  • Run cargo clippy --target <TARGET> --features "preempt,arm-el2" for all supported targets
  • Ensure all builds pass: cargo build --target <TARGET> --features "preempt,arm-el2"
  • Execute tests for applicable targets

Sources: .github/workflows/ci.yml(L23 - L27) 

Documentation Standards

The crate enforces strict documentation requirements through RUSTDOCFLAGS configuration. All public APIs must include comprehensive documentation with examples.

Documentation Requirements:

  • All public functions, structs, and macros must have doc comments
  • Examples should demonstrate real-world usage patterns
  • Architecture-specific behavior must be clearly documented
  • Breaking changes require CHANGELOG.md updates

Sources: .github/workflows/ci.yml(L42) 

Architecture-Specific Contribution Guidelines

Cross-Platform Code Generation

When contributing to architecture-specific functionality, developers must understand the code generation pipeline for each supported platform:

flowchart TD
subgraph subGraph2["Feature Variations"]
    NAIVE["sp-naive: Global variables"]
    PREEMPT["preempt: NoPreemptGuard"]
    ARM_EL2["arm-el2: EL2 registers"]
end
subgraph subGraph1["Supported Architectures"]
    X86_IMPL["x86_64: GS_BASE register"]
    ARM_IMPL["AArch64: TPIDR_EL1/EL2"]
    RISCV_IMPL["RISC-V: gp register"]
    LOONG_IMPL["LoongArch: r21 register"]
end
subgraph subGraph0["Architecture Contributions"]
    MACRO_GEN["Macro Code Generation"]
    ASM_GEN["Assembly Code Generation"]
    REG_ACCESS["Register Access Patterns"]
end

ASM_GEN --> ARM_IMPL
ASM_GEN --> LOONG_IMPL
ASM_GEN --> RISCV_IMPL
ASM_GEN --> X86_IMPL
MACRO_GEN --> ARM_IMPL
MACRO_GEN --> LOONG_IMPL
MACRO_GEN --> RISCV_IMPL
MACRO_GEN --> X86_IMPL
REG_ACCESS --> ARM_EL2
REG_ACCESS --> NAIVE
REG_ACCESS --> PREEMPT

Architecture Addition Requirements:

  • Implement register access patterns in percpu_macros/src/lib.rs
  • Add target-specific assembly generation
  • Update feature flag handling for new architecture
  • Add comprehensive tests for the new platform
  • Update CI configuration to include new target

Sources: Based on system architecture and CHANGELOG.md(L25) 

Feature Flag Considerations

New features must integrate with the existing feature flag system:

FeatureImpactTesting Requirements
sp-naiveSingle-CPU fallback implementationTest with and without feature
preemptPreemption-safe operationsTest atomic behavior
arm-el2AArch64 EL2 privilege levelTest on EL2-capable systems

Sources: .github/workflows/ci.yml(L25)  CHANGELOG.md(L43) 

Testing and Validation

Multi-Target Testing Strategy

Contributors must validate changes across all supported target architectures. The CI system provides the definitive testing matrix:

flowchart TD
subgraph subGraph2["Validation Criteria"]
    LINUX_NATIVE["Linux Native Execution"]
    CROSS_COMPILE["Cross-compilation Success"]
    FEATURE_COMPAT["Feature Compatibility"]
end
subgraph subGraph1["Test Execution"]
    UNIT["Unit Tests"]
    INTEGRATION["Integration Tests"]
    BUILD["Build Verification"]
end
subgraph subGraph0["Testing Matrix"]
    TARGETS["Target Architectures"]
    FEATURES["Feature Combinations"]
    ENVS["Execution Environments"]
end

BUILD --> FEATURE_COMPAT
ENVS --> UNIT
FEATURES --> UNIT
INTEGRATION --> CROSS_COMPILE
TARGETS --> UNIT
UNIT --> LINUX_NATIVE

Testing Commands:

  • Linux native: cargo test --target x86_64-unknown-linux-gnu --features "sp-naive"
  • Standard features: cargo test --target x86_64-unknown-linux-gnu
  • Cross-compilation: cargo build --target <TARGET> --features "preempt,arm-el2"

Sources: .github/workflows/ci.yml(L29 - L32) 

Test Coverage Requirements

New functionality requires comprehensive test coverage including:

  • Unit tests for core functionality
  • Integration tests for cross-component interactions
  • Architecture-specific validation where applicable
  • Feature flag combination testing

Pull Request Workflow

Submission Requirements

Pull requests must satisfy all CI checks before review consideration:

  1. Automated Checks: All CI jobs must pass successfully
  2. Code Review: At least one maintainer approval required
  3. Testing: Comprehensive test coverage for new functionality
  4. Documentation: Updated documentation for API changes
  5. Changelog: Version-appropriate changelog entries

API Evolution Guidelines

Based on the changelog history, API changes follow specific patterns:

Breaking Changes (Major Version):

  • Function signature modifications
  • Public API restructuring
  • Initialization process changes

Compatible Changes (Minor Version):

  • New architecture support
  • Additional feature flags
  • New accessor methods

Patch Changes:

  • Bug fixes
  • Internal optimizations
  • Documentation improvements

Sources: CHANGELOG.md(L1 - L48) 

Review Criteria

Maintainers evaluate contributions based on:

  • Correctness: Proper per-CPU data isolation
  • Safety: Memory safety and race condition prevention
  • Performance: Minimal overhead for per-CPU access
  • Portability: Cross-platform compatibility
  • Maintainability: Clear, well-documented code

Documentation and Release Process

Documentation Generation

The project automatically generates and deploys documentation to GitHub Pages for the main branch. Contributors should ensure their changes integrate properly with the documentation pipeline.

Documentation Commands:

  • Local generation: cargo doc --no-deps
  • Link checking: Automated through RUSTDOCFLAGS
  • Deployment: Automatic on main branch merge

Sources: .github/workflows/ci.yml(L34 - L55) 

Release Coordination

Version releases require careful coordination across both crates in the workspace. Contributors should coordinate with maintainers for:

  • API stability guarantees
  • Cross-crate compatibility
  • Architecture support matrix updates
  • Feature flag deprecation schedules

This ensures the percpu ecosystem maintains consistent behavior across all supported platforms and use cases.

Sources: CHANGELOG.md(L1 - L48)  .github/workflows/ci.yml(L1 - L56) 

Overview

Relevant source files

Purpose and Scope

The axdriver_crates repository provides a comprehensive device driver framework designed specifically for the ArceOS operating system in no_std environments. This workspace contains modular driver abstractions, concrete hardware implementations, and infrastructure components that enable unified device management across block storage, networking, graphics, and virtualized devices.

This document covers the overall workspace structure, architectural patterns, and relationships between the driver crates. For detailed information about specific driver interfaces, see Foundation Layer (axdriver_base), Network Drivers, Block Storage Drivers, Display Drivers, and VirtIO Integration.

Sources: Cargo.toml(L1 - L29)  README.md(L1 - L11) 

Workspace Architecture

The axdriver_crates workspace is organized as a hierarchical collection of six core crates, each serving distinct roles in the driver ecosystem:

Workspace Crate Structure

flowchart TD
subgraph subGraph3["axdriver_crates Workspace"]
    WS["axdriver_cratesCargo Workspace"]
    subgraph Infrastructure["Infrastructure"]
        PCI["axdriver_pciPCI Bus Operations"]
        VIRTIO["axdriver_virtioVirtIO Device Wrappers"]
    end
    subgraph subGraph1["Device Abstractions"]
        BLOCK["axdriver_blockBlockDriverOps"]
        NET["axdriver_netNetDriverOps, NetBuf"]
        DISPLAY["axdriver_displayDisplayDriverOps"]
    end
    subgraph Foundation["Foundation"]
        BASE["axdriver_baseBaseDriverOps, DeviceType"]
    end
end

BASE --> BLOCK
BASE --> DISPLAY
BASE --> NET
BASE --> PCI
BASE --> VIRTIO
WS --> BASE
WS --> BLOCK
WS --> DISPLAY
WS --> NET
WS --> PCI
WS --> VIRTIO

Sources: Cargo.toml(L4 - L11)  README.md(L5 - L10) 

Crate Dependencies and Responsibilities

CratePrimary Traits/TypesPurpose
axdriver_baseBaseDriverOps,DeviceType,DevResultFoundation interfaces and error handling
axdriver_blockBlockDriverOpsBlock storage device abstractions
axdriver_netNetDriverOps,NetBuf,EthernetAddressNetwork device interfaces and buffer management
axdriver_displayDisplayDriverOpsGraphics and display device abstractions
axdriver_pciPCI device enumerationPCI bus operations and device discovery
axdriver_virtioVirtIoBlkDev,VirtIoNetDev,VirtIoGpuDevVirtIO device wrapper implementations

Sources: Cargo.toml(L22 - L28)  README.md(L5 - L10) 

Driver Framework Architecture

The framework implements a trait-based architecture where all drivers extend common base functionality while implementing device-specific operations:

Trait Hierarchy and Implementation Pattern

flowchart TD
subgraph subGraph2["Implementation Examples"]
    RAMDISK["RamDisk"]
    IXGBE["IxgbeNic"]
    VIRTIO_IMPL["VirtIoBlkDevVirtIoNetDevVirtIoGpuDev"]
end
subgraph subGraph1["Device-Specific Traits"]
    BLOCKOPS["BlockDriverOpsread_block()write_block()num_blocks()"]
    NETOPS["NetDriverOpsmac_address()transmit()receive()"]
    DISPLAYOPS["DisplayDriverOpsinfo()framebuffer()flush()"]
end
subgraph subGraph0["Core Foundation Types"]
    BASEOPS["BaseDriverOps"]
    DEVTYPE["DeviceType{Block, Net, Display, Char}"]
    DEVRESULT["DevResult"]
    DEVERROR["DevError"]
end

BASEOPS --> BLOCKOPS
BASEOPS --> DISPLAYOPS
BASEOPS --> NETOPS
BLOCKOPS --> RAMDISK
BLOCKOPS --> VIRTIO_IMPL
DEVERROR --> DEVRESULT
DEVRESULT --> BLOCKOPS
DEVRESULT --> DISPLAYOPS
DEVRESULT --> NETOPS
DEVTYPE --> BASEOPS
DISPLAYOPS --> VIRTIO_IMPL
NETOPS --> IXGBE
NETOPS --> VIRTIO_IMPL

Sources: Cargo.toml(L22 - L28)  README.md(L5 - L10) 

Compilation and Feature Management

The workspace is designed for selective compilation through Cargo features, enabling minimal deployments for resource-constrained environments:

Build Configuration Structure

flowchart TD
subgraph subGraph2["Version Management"]
    VERSION["workspace.packageversion = '0.1.2'resolver = '2'"]
end
subgraph subGraph1["Target Environments"]
    EMBEDDED["Embedded SystemsCore traits only"]
    DESKTOP["Desktop/ServerFull driver set"]
    VIRTUALIZED["VM EnvironmentVirtIO focus"]
end
subgraph subGraph0["Workspace Dependencies"]
    WORKSPACE_DEPS["[workspace.dependencies]axdriver_base = {path, version}axdriver_block = {path, version}axdriver_net = {path, version}"]
end

VERSION --> WORKSPACE_DEPS
WORKSPACE_DEPS --> DESKTOP
WORKSPACE_DEPS --> EMBEDDED
WORKSPACE_DEPS --> VIRTUALIZED

The workspace uses Cargo resolver version 2 and maintains consistent versioning across all member crates at version 0.1.2. Each crate can be compiled independently or as part of larger driver collections based on deployment requirements.

Sources: Cargo.toml(L2)  Cargo.toml(L13 - L14)  Cargo.toml(L22 - L28) 

Project Metadata and Licensing

The repository supports multiple licensing options (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) and targets the categories ["os", "no-std", "hardware-support"], indicating its focus on operating system development in embedded and systems programming contexts.

Sources: Cargo.toml(L13 - L21) 

Architecture and Design

Relevant source files

Purpose and Scope

This document describes the architectural foundations and design principles that unify the axdriver_crates framework. It covers the trait hierarchy, modular organization, error handling patterns, and compilation strategies that enable building device drivers for ArceOS across different hardware platforms and virtualized environments.

For detailed information about specific device types, see Foundation Layer (axdriver_base), Network Drivers, Block Storage Drivers, Display Drivers, and VirtIO Integration. For build system details, see Development and Build Configuration.

Core Architectural Principles

The axdriver_crates framework follows several key design principles that promote modularity, type safety, and cross-platform compatibility:

Trait-Based Driver Interface

All device drivers implement a common trait hierarchy starting with BaseDriverOps. This provides a uniform interface for device management while allowing device-specific functionality through specialized traits.

Zero-Cost Abstractions

The framework leverages Rust's type system and compilation features to provide high-level abstractions without runtime overhead. Device-specific functionality is conditionally compiled based on cargo features.

Hardware Abstraction Layering

The framework separates device logic from hardware-specific operations through abstraction layers, enabling the same driver interface to work with both native hardware and virtualized devices.

Trait Hierarchy and Inheritance Model

The framework establishes a clear inheritance hierarchy where all drivers share common operations while extending with device-specific capabilities.

Base Driver Operations

flowchart TD
BaseDriverOps["BaseDriverOps• device_name()• device_type()"]
DeviceType["DeviceType enum• Block• Char• Net• Display"]
DevResult["DevResult<T>Result<T, DevError>"]
DevError["DevError enum• AlreadyExists• Again• BadState• InvalidParam• Io• NoMemory• ResourceBusy• Unsupported"]
NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• rx/tx_queue_size()• transmit/receive()• alloc_tx_buffer()"]
BlockDriverOps["BlockDriverOps• num_blocks()• block_size()• read_block/write_block()• flush()"]
DisplayDriverOps["DisplayDriverOps• info/framebuffer• flush display"]

BaseDriverOps --> BlockDriverOps
BaseDriverOps --> DevResult
BaseDriverOps --> DeviceType
BaseDriverOps --> DisplayDriverOps
BaseDriverOps --> NetDriverOps
DevResult --> DevError

Diagram: Core Trait Hierarchy and Type System

Sources: axdriver_base/src/lib.rs(L18 - L62)  axdriver_net/src/lib.rs(L24 - L68)  axdriver_block/src/lib.rs(L15 - L38) 

Device-Specific Extensions

Each device type extends the base operations with specialized functionality:

TraitKey OperationsBuffer ManagementHardware Integration
NetDriverOpstransmit(),receive(),mac_address()NetBufPtr,NetBufPoolNIC hardware, packet processing
BlockDriverOpsread_block(),write_block(),flush()Direct buffer I/OStorage controllers, file systems
DisplayDriverOpsframebuffer(),flush()Framebuffer managementGraphics hardware, display output

Sources: axdriver_net/src/lib.rs(L24 - L68)  axdriver_block/src/lib.rs(L15 - L38) 

Device Type Abstraction System

The framework uses a centralized device type system that enables uniform device discovery and management across different hardware platforms.

flowchart TD
subgraph subGraph2["Device Registration"]
    DriverManager["Device driver registration"]
    TypeMatching["Device type matching"]
    DriverBinding["Driver binding"]
end
subgraph subGraph1["VirtIO Device Discovery"]
    VirtIOProbe["probe_mmio_device()"]
    VirtIOPCIProbe["probe_pci_device()"]
    VirtIOTypeConv["as_dev_type()"]
end
subgraph subGraph0["Native Device Discovery"]
    PCIProbe["PCI device probing"]
    MMIOProbe["MMIO device discovery"]
    HardwareEnum["Hardware enumeration"]
end
DeviceType["DeviceType enum"]

DeviceType --> TypeMatching
DriverBinding --> DriverManager
MMIOProbe --> DeviceType
PCIProbe --> DeviceType
TypeMatching --> DriverBinding
VirtIOPCIProbe --> VirtIOTypeConv
VirtIOProbe --> VirtIOTypeConv
VirtIOTypeConv --> DeviceType

Diagram: Device Type Abstraction and Discovery Flow

The DeviceType enum serves as the central abstraction that maps hardware devices to appropriate driver implementations. The VirtIO integration demonstrates this pattern through type conversion functions that translate VirtIO-specific device types to the common DeviceType enumeration.

Sources: axdriver_base/src/lib.rs(L18 - L29)  axdriver_virtio/src/lib.rs(L71 - L79)  axdriver_virtio/src/lib.rs(L38 - L69) 

Error Handling Design

The framework implements a unified error handling strategy through the DevResult<T> type and standardized error codes in DevError.

Error Code Mapping

The framework provides consistent error semantics across different hardware backends through error code translation:

flowchart TD
subgraph subGraph2["Driver Operations"]
    NetOps["NetDriverOps methods"]
    BlockOps["BlockDriverOps methods"]
    BaseOps["BaseDriverOps methods"]
end
subgraph subGraph1["Error Types"]
    QueueFull["QueueFull → BadState"]
    NotReady["NotReady → Again"]
    InvalidParam["InvalidParam → InvalidParam"]
    DmaError["DmaError → NoMemory"]
    IoError["IoError → Io"]
    Unsupported["Unsupported → Unsupported"]
end
subgraph subGraph0["VirtIO Error Translation"]
    VirtIOError["virtio_drivers::Error"]
    ErrorMapping["as_dev_err() function"]
    DevError["DevError enum"]
end

BaseOps --> DevError
BlockOps --> DevError
ErrorMapping --> DevError
ErrorMapping --> DmaError
ErrorMapping --> InvalidParam
ErrorMapping --> IoError
ErrorMapping --> NotReady
ErrorMapping --> QueueFull
ErrorMapping --> Unsupported
NetOps --> DevError
VirtIOError --> ErrorMapping

Diagram: Unified Error Handling Architecture

This design enables consistent error handling across different driver implementations while preserving semantic meaning of hardware-specific error conditions.

Sources: axdriver_base/src/lib.rs(L31 - L53)  axdriver_virtio/src/lib.rs(L82 - L97) 

Modular Workspace Organization

The framework employs a modular workspace structure that promotes code reuse and selective compilation.

Workspace Dependency Graph

flowchart TD
subgraph subGraph3["External Dependencies"]
    virtio_drivers["virtio-drivers crate"]
    hardware_crates["Hardware-specific cratesfxmac_rsixgbe-driver"]
end
subgraph subGraph2["Hardware Integration"]
    axdriver_pci["axdriver_pciPCI bus operations"]
    axdriver_virtio["axdriver_virtioVirtIO device wrappersTransport abstraction"]
end
subgraph subGraph1["Device Abstractions"]
    axdriver_net["axdriver_netNetDriverOpsNetBuf systemEthernetAddress"]
    axdriver_block["axdriver_blockBlockDriverOpsBlock I/O"]
    axdriver_display["axdriver_displayDisplayDriverOpsGraphics"]
end
subgraph Foundation["Foundation"]
    axdriver_base["axdriver_baseBaseDriverOpsDeviceTypeDevError"]
end

axdriver_base --> axdriver_block
axdriver_base --> axdriver_display
axdriver_base --> axdriver_net
axdriver_base --> axdriver_pci
axdriver_base --> axdriver_virtio
axdriver_net --> hardware_crates
axdriver_pci --> axdriver_block
axdriver_pci --> axdriver_net
axdriver_virtio --> virtio_drivers

Diagram: Workspace Module Dependencies

This organization allows applications to selectively include only the device drivers needed for their target platform, minimizing binary size and compilation time.

Sources: axdriver_base/src/lib.rs(L1 - L15)  axdriver_net/src/lib.rs(L6 - L11)  axdriver_block/src/lib.rs(L6 - L10)  axdriver_virtio/src/lib.rs(L16 - L28) 

Feature-Based Compilation Strategy

The framework uses cargo features to enable conditional compilation of device drivers and their dependencies.

Feature Organization Pattern

CrateFeaturesPurpose
axdriver_netixgbe,fxmacSpecific NIC hardware support
axdriver_blockramdisk,bcm2835-sdhciStorage device implementations
axdriver_virtioblock,net,gpuVirtIO device types

This pattern enables several deployment scenarios:

  • Minimal embedded: Only essential drivers compiled in
  • Full server: All available drivers for maximum hardware compatibility
  • Virtualized: Only VirtIO drivers for VM environments
  • Development: All drivers for testing and validation

Sources: axdriver_net/src/lib.rs(L6 - L11)  axdriver_block/src/lib.rs(L6 - L10)  axdriver_virtio/src/lib.rs(L16 - L21) 

Hardware Abstraction Patterns

The framework implements two primary patterns for hardware abstraction:

Direct Hardware Integration

For native hardware drivers, the framework integrates with hardware-specific crates that provide low-level device access. This pattern is used by drivers like FXmac and Intel ixgbe network controllers.

Virtualization Layer Integration

For virtualized environments, the framework provides adapter implementations that wrap external VirtIO drivers to implement the common trait interfaces. This enables the same driver API to work across both physical and virtual hardware.

The VirtIO integration demonstrates sophisticated error translation and type conversion to maintain API compatibility while leveraging mature external driver implementations.

Sources: axdriver_virtio/src/lib.rs(L38 - L97)  axdriver_net/src/lib.rs(L6 - L11) 

Foundation Layer (axdriver_base)

Relevant source files

Purpose and Scope

The axdriver_base crate provides the foundational traits, types, and error handling mechanisms that underpin all device drivers in the ArceOS driver ecosystem. This crate defines the minimal common interface that every device driver must implement, regardless of device type or specific functionality.

This document covers the core abstractions and interfaces defined in axdriver_base. For device-specific driver interfaces that extend these foundations, see Network Drivers, Block Storage Drivers, and Display Drivers. For information about how these foundations integrate with virtualized environments, see VirtIO Integration.

Core Device Type Classification

The foundation layer establishes a clear taxonomy of supported device types through the DeviceType enumeration. This classification system enables type-safe device management and routing throughout the driver framework.

Device Type Enumeration

flowchart TD
DeviceType["DeviceType enum"]
Block["BlockStorage devices(disks, SSDs)"]
Char["CharCharacter devices(serial ports)"]
Net["NetNetwork devices(NICs, WiFi)"]
Display["DisplayGraphics devices(GPUs, framebuffers)"]
BlockDrivers["Block driver implementations"]
CharDrivers["Character driver implementations"]
NetDrivers["Network driver implementations"]
DisplayDrivers["Display driver implementations"]

Block --> BlockDrivers
Char --> CharDrivers
DeviceType --> Block
DeviceType --> Char
DeviceType --> Display
DeviceType --> Net
Display --> DisplayDrivers
Net --> NetDrivers

The DeviceType enum provides a standardized way to categorize hardware devices, enabling the driver framework to route operations to appropriate specialized driver interfaces.

Sources: axdriver_base/src/lib.rs(L18 - L29) 

Error Handling System

The foundation layer establishes a unified error handling approach across all drivers through the DevError enumeration and DevResult type alias. This system provides consistent error semantics regardless of the underlying hardware or driver implementation.

Error Type Hierarchy

flowchart TD
DevError["DevError enum"]
AlreadyExists["AlreadyExistsEntity conflict"]
Again["AgainNon-blocking retry"]
BadState["BadStateInvalid driver state"]
InvalidParam["InvalidParamBad arguments"]
Io["IoHardware I/O failure"]
NoMemory["NoMemoryDMA allocation failure"]
ResourceBusy["ResourceBusyHardware unavailable"]
Unsupported["UnsupportedUnimplemented operation"]
DevResult["DevResult<T> = Result<T, DevError>"]

DevError --> Again
DevError --> AlreadyExists
DevError --> BadState
DevError --> DevResult
DevError --> InvalidParam
DevError --> Io
DevError --> NoMemory
DevError --> ResourceBusy
DevError --> Unsupported

Error Categories

Error TypeUse CaseTypical Scenarios
AlreadyExistsResource conflictsDevice already registered, buffer already allocated
AgainNon-blocking operationsQueue full, temporary unavailability
BadStateDriver state issuesUninitialized device, invalid configuration
InvalidParamParameter validationInvalid buffer size, out-of-range values
IoHardware failuresDevice timeout, communication errors
NoMemoryMemory allocationDMA buffer allocation failure
ResourceBusyConcurrent accessDevice locked by another process
UnsupportedFeature availabilityOptional hardware features not present

Sources: axdriver_base/src/lib.rs(L31 - L53) 

Base Driver Interface

All device drivers in the framework must implement the BaseDriverOps trait, which provides the minimal set of operations required for device identification and type classification.

BaseDriverOps Trait Structure

flowchart TD
subgraph subGraph1["Core Methods"]
    DeviceName["device_name() -> &str"]
    DeviceTypeMethod["device_type() -> DeviceType"]
end
subgraph subGraph0["Trait Requirements"]
    Bounds["Send + Sync bounds"]
    SendSync["Thread-safe driver instances"]
end
BaseDriverOps["BaseDriverOps trait"]
NameString["Human-readable device identifier"]
TypeEnum["DeviceType classification"]

BaseDriverOps --> Bounds
BaseDriverOps --> DeviceName
BaseDriverOps --> DeviceTypeMethod
Bounds --> SendSync
DeviceName --> NameString
DeviceTypeMethod --> TypeEnum

Implementation Requirements

The BaseDriverOps trait enforces two critical requirements:

  1. Thread Safety: All drivers must implement Send + Sync, enabling safe concurrent access across threads
  2. Device Identification: Every driver must provide both a human-readable name and a type classification

These requirements ensure that the driver framework can safely manage device instances in multi-threaded environments while maintaining clear device identification.

Sources: axdriver_base/src/lib.rs(L55 - L62) 

Integration with Specialized Driver Types

The foundation layer serves as the building block for all specialized device driver interfaces. Each device-specific crate extends these core abstractions with domain-specific functionality.

Driver Trait Hierarchy

flowchart TD
subgraph subGraph2["Concrete Implementations"]
    FXmacNic["FXmacNic"]
    IxgbeNic["IxgbeNic"]
    VirtIoNetDev["VirtIoNetDev"]
    RamDisk["RamDisk"]
    SDHCIDriver["SDHCIDriver"]
    VirtIoBlkDev["VirtIoBlkDev"]
    VirtIoGpuDev["VirtIoGpuDev"]
end
subgraph subGraph1["Specialized Interfaces"]
    NetDriverOps["NetDriverOps(axdriver_net)"]
    BlockDriverOps["BlockDriverOps(axdriver_block)"]
    DisplayDriverOps["DisplayDriverOps(axdriver_display)"]
end
subgraph subGraph0["Foundation Layer"]
    BaseDriverOps["BaseDriverOps(axdriver_base)"]
    DevError["DevError"]
    DevResult["DevResult<T>"]
    DeviceType["DeviceType"]
end

BaseDriverOps --> BlockDriverOps
BaseDriverOps --> DisplayDriverOps
BaseDriverOps --> NetDriverOps
BlockDriverOps --> RamDisk
BlockDriverOps --> SDHCIDriver
BlockDriverOps --> VirtIoBlkDev
DisplayDriverOps --> VirtIoGpuDev
NetDriverOps --> FXmacNic
NetDriverOps --> IxgbeNic
NetDriverOps --> VirtIoNetDev

Extension Pattern

Each specialized driver interface follows a consistent extension pattern:

  1. Inherit from BaseDriverOps for basic device identification
  2. Extend with device-specific operations (e.g., transmit() for network devices)
  3. Utilize the common DevResult<T> error handling system
  4. Classify using the appropriate DeviceType variant

This pattern ensures consistency across all driver types while allowing for domain-specific optimizations and functionality.

Sources: axdriver_base/src/lib.rs(L1 - L15) 

No-std Compatibility

The foundation layer is designed for embedded and systems programming environments, maintaining #![no_std] compatibility throughout. This design choice enables the driver framework to operate in resource-constrained environments and kernel contexts where the standard library is not available.

The no_std constraint influences several design decisions:

  • Error types use Debug trait instead of std::error::Error
  • No heap allocations in core types
  • Compatible with embedded target architectures

Sources: axdriver_base/src/lib.rs(L16) 

Network Drivers

Relevant source files

Purpose and Scope

This document covers the network driver subsystem within the axdriver_crates framework, which provides traits, types, and implementations for Network Interface Card (NIC) drivers. The network subsystem is the most sophisticated component in the driver framework, featuring advanced buffer management, multiple hardware implementations, and support for both physical and virtualized network devices.

For information about the foundational driver traits that network drivers extend, see Foundation Layer (axdriver_base). For VirtIO network device integration, see VirtIO Integration.

Network Driver Architecture

The network driver subsystem follows a layered architecture where hardware-specific implementations conform to standardized traits while maintaining optimized performance paths for different types of network hardware.

Core Network Driver Interface

The NetDriverOps trait defined in axdriver_net/src/lib.rs(L25 - L68)  serves as the primary interface that all network drivers must implement. This trait extends BaseDriverOps and provides network-specific operations:

flowchart TD
BaseDriverOps["BaseDriverOps• device_name()• device_type()"]
NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• rx/tx_queue_size()• transmit/receive()• buffer management"]
EthernetAddress["EthernetAddress6-byte MAC address"]
NetBufPtr["NetBufPtrRaw buffer pointer• raw_ptr• buf_ptr• len"]

BaseDriverOps --> NetDriverOps
NetDriverOps --> EthernetAddress
NetDriverOps --> NetBufPtr

Title: Network Driver Interface Hierarchy

The interface provides both synchronous and asynchronous patterns through methods like can_transmit() and can_receive() for non-blocking operations, while receive() returns DevError::Again when no packets are available.

Sources: axdriver_net/src/lib.rs(L25 - L68) 

Buffer Management System

Network drivers use a sophisticated buffer management system centered around NetBufPtr and related types. The NetBufPtr structure provides zero-copy buffer operations for high-performance networking:

FieldTypePurpose
raw_ptrNonNullPointer to original object for deallocation
buf_ptrNonNullPointer to actual network data
lenusizeLength of network packet data
flowchart TD
NetBufPool["NetBufPoolMemory allocation pool"]
NetBuf["NetBufManaged buffer wrapper"]
NetBufBox["NetBufBoxOwned buffer container"]
NetBufPtr["NetBufPtrRaw pointer wrapper• raw_ptr: NonNull<u8>• buf_ptr: NonNull<u8>• len: usize"]
PacketData["&[u8] packet data"]
PacketDataMut["&mut [u8] packet data"]
RawPtr["*mut T raw pointer"]

NetBuf --> NetBufBox
NetBufBox --> NetBufPtr
NetBufPool --> NetBuf
NetBufPtr --> PacketData
NetBufPtr --> PacketDataMut
NetBufPtr --> RawPtr

Title: Network Buffer Management Architecture

The buffer system supports both pre-allocated buffer pools for high-performance drivers and dynamic allocation for simpler implementations. The separation between raw_ptr and buf_ptr enables complex memory layouts where the network data is embedded within larger structures.

Sources: axdriver_net/src/lib.rs(L19)  axdriver_net/src/lib.rs(L70 - L108) 

Hardware Driver Implementations

The network subsystem includes two primary hardware implementations with different architectural approaches:

Feature-Based Driver Selection

Network drivers are compiled conditionally based on Cargo features, enabling targeted builds for specific hardware platforms:

FeatureDriverHardware TargetExternal Dependency
ixgbeIntel ixgbe10 Gigabit Ethernetixgbe-drivercrate
fxmacFXmacPhytiumPi Ethernetfxmac_rscrate
flowchart TD
subgraph subGraph1["axdriver_net Implementation"]
    IxgbeNic["ixgbe::IxgbeNicNetDriverOps impl"]
    FxmacNic["fxmac::FxmacNicNetDriverOps impl"]
end
subgraph subGraph0["Hardware Abstraction Layer"]
    IxgbeDriver["ixgbe-driverExternal crateLow-level hardware access"]
    FxmacRs["fxmac_rsExternal cratePhytiumPi HAL"]
end
CargoFeatures["Cargo Features"]
NetDriverOps["NetDriverOps trait"]

CargoFeatures --> FxmacRs
CargoFeatures --> IxgbeDriver
FxmacNic --> NetDriverOps
FxmacRs --> FxmacNic
IxgbeDriver --> IxgbeNic
IxgbeNic --> NetDriverOps

Title: Hardware Driver Integration Pattern

Each hardware implementation wraps an external hardware abstraction layer (HAL) crate and adapts it to the NetDriverOps interface, providing a consistent API while preserving hardware-specific optimizations.

Sources: axdriver_net/Cargo.toml(L14 - L24)  axdriver_net/src/lib.rs(L6 - L11) 

Driver-Specific Patterns

Different network drivers employ distinct patterns based on their hardware characteristics:

  • Intel ixgbe Driver: Uses memory pool allocation with NetBufPool for efficient buffer management in high-throughput scenarios
  • FXmac Driver: Implements queue-based receive/transmit operations optimized for embedded ARM platforms

The drivers abstract these differences behind the uniform NetDriverOps interface while maintaining their performance characteristics through the flexible buffer management system.

Error Handling and Device States

Network drivers use the standardized error handling from axdriver_base through DevResult and DevError types. Network-specific error conditions include:

  • DevError::Again: Returned by receive() when no packets are available
  • Standard device errors for hardware failures and invalid operations
  • Buffer allocation failures through the alloc_tx_buffer() method

The state management pattern allows drivers to report their current capabilities through can_transmit() and can_receive() methods, enabling efficient polling-based network stacks.

Sources: axdriver_net/src/lib.rs(L17)  axdriver_net/src/lib.rs(L29 - L33)  axdriver_net/src/lib.rs(L61 - L63) 

Integration Points

The network driver subsystem integrates with several other components in the axdriver ecosystem:

  1. Foundation Layer: All network drivers implement BaseDriverOps for consistent device identification
  2. VirtIO Integration: VirtIO network devices are wrapped to implement NetDriverOps
  3. PCI Bus Operations: Physical network cards are discovered and initialized through PCI enumeration
  4. Build System: Conditional compilation enables platform-specific driver selection

This modular design allows the same network driver interface to support both bare-metal hardware and virtualized environments while maintaining optimal performance for each use case.

Sources: axdriver_net/src/lib.rs(L17)  axdriver_net/Cargo.toml(L22) 

Network Driver Interface

Relevant source files

Purpose and Scope

This document covers the core network driver interface defined by the NetDriverOps trait and related types in the axdriver_net crate. This interface provides the foundational abstraction layer for all network device drivers in the ArceOS driver framework.

For detailed information about the underlying foundation traits and error handling, see Foundation Layer. For specific buffer management implementations and memory pool strategies, see Network Buffer Management. For concrete hardware driver implementations that use this interface, see Hardware Implementations.

Network Driver Trait Architecture

The network driver interface builds upon the foundation layer through a clear trait hierarchy that extends BaseDriverOps with network-specific functionality.

Trait Inheritance Structure

flowchart TD
BaseDriverOps["BaseDriverOps• device_name()• device_type()"]
NetDriverOps["NetDriverOps• mac_address()• can_transmit/receive()• transmit/receive()• buffer management"]
DeviceType["DeviceType::Net"]
DevResult["DevResult"]
DevError["DevError"]
EthernetAddress["EthernetAddress"]
NetBufPtr["NetBufPtr"]

BaseDriverOps --> DeviceType
BaseDriverOps --> NetDriverOps
DevResult --> DevError
NetDriverOps --> DevResult
NetDriverOps --> EthernetAddress
NetDriverOps --> NetBufPtr

Sources: axdriver_net/src/lib.rs(L16 - L17)  axdriver_net/src/lib.rs(L25 - L68) 

The NetDriverOps trait extends BaseDriverOps to provide network-specific operations while maintaining compatibility with the unified driver framework. All network drivers must implement both trait interfaces to ensure consistent device management and network functionality.

Core Type Definitions

TypePurposeDefinition Location
EthernetAddressMAC address representationaxdriver_net/src/lib.rs22
NetBufPtrRaw network buffer pointeraxdriver_net/src/lib.rs71-108
NetDriverOpsPrimary network driver traitaxdriver_net/src/lib.rs25-68

Sources: axdriver_net/src/lib.rs(L19 - L22)  axdriver_net/src/lib.rs(L71) 

Core Network Operations

The NetDriverOps trait defines the essential operations that all network drivers must implement, organized into device information, queue management, and packet processing categories.

Device Information and Capabilities

flowchart TD
Driver["Network Driver"]
MacAddr["mac_address()Returns EthernetAddress"]
CanTx["can_transmit()Returns bool"]
CanRx["can_receive()Returns bool"]
TxQSize["tx_queue_size()Returns usize"]
RxQSize["rx_queue_size()Returns usize"]

Driver --> CanRx
Driver --> CanTx
Driver --> MacAddr
Driver --> RxQSize
Driver --> TxQSize

Sources: axdriver_net/src/lib.rs(L26 - L39) 

These methods provide essential device characteristics:

  • mac_address() returns the hardware ethernet address as an EthernetAddress struct
  • can_transmit() and can_receive() indicate current device capability status
  • tx_queue_size() and rx_queue_size() return the capacity of transmit and receive queues

Packet Processing Operations

The trait defines a complete packet processing lifecycle with explicit buffer management:

MethodPurposeParametersReturn Type
transmit()Send packet to networktx_buf: NetBufPtrDevResult
receive()Receive packet from networkNoneDevResult
alloc_tx_buffer()Allocate transmission buffersize: usizeDevResult
recycle_rx_buffer()Return buffer to receive queuerx_buf: NetBufPtrDevResult
recycle_tx_buffers()Reclaim transmitted buffersNoneDevResult

Sources: axdriver_net/src/lib.rs(L41 - L68) 

Buffer Management Interface

The network interface uses NetBufPtr as the primary buffer abstraction, providing a safe wrapper around raw memory pointers for network packet data.

NetBufPtr Structure and Operations

flowchart TD
NetBufPtr["NetBufPtr"]
RawPtr["raw_ptr: NonNullOriginal object pointer"]
BufPtr["buf_ptr: NonNullBuffer data pointer"]
Len["len: usizeBuffer length"]
NewMethod["new(raw_ptr, buf_ptr, len)"]
RawPtrMethod["raw_ptr() -> *mut T"]
PacketMethod["packet() -> &[u8]"]
PacketMutMethod["packet_mut() -> &mut [u8]"]
PacketLenMethod["packet_len() -> usize"]

NetBufPtr --> BufPtr
NetBufPtr --> Len
NetBufPtr --> NewMethod
NetBufPtr --> PacketLenMethod
NetBufPtr --> PacketMethod
NetBufPtr --> PacketMutMethod
NetBufPtr --> RawPtr
NetBufPtr --> RawPtrMethod

Sources: axdriver_net/src/lib.rs(L71 - L108) 

The NetBufPtr design separates the original object pointer from the buffer data pointer, enabling:

  • Safe reference counting of the underlying buffer object
  • Direct access to packet data without additional indirection
  • Flexible buffer layout for different hardware requirements

Buffer Lifecycle Management

sequenceDiagram
    participant Application as "Application"
    participant NetDriver as "NetDriver"
    participant Hardware as "Hardware"

    Note over Application,Hardware: Transmission Flow
    Application ->> NetDriver: alloc_tx_buffer(size)
    NetDriver -->> Application: NetBufPtr
    Application ->> Application: Fill packet data
    Application ->> NetDriver: transmit(NetBufPtr)
    NetDriver ->> Hardware: Send to hardware queue
    NetDriver ->> NetDriver: recycle_tx_buffers()
    Note over Application,Hardware: Reception Flow
    Application ->> NetDriver: recycle_rx_buffer(NetBufPtr)
    NetDriver ->> Hardware: Add to receive queue
    Hardware ->> NetDriver: Packet received
    NetDriver ->> NetDriver: receive()
    NetDriver -->> Application: NetBufPtr with data

Sources: axdriver_net/src/lib.rs(L41 - L68) 

The buffer lifecycle ensures efficient memory management:

  1. Allocation: alloc_tx_buffer() provides buffers for transmission
  2. Population: Applications fill buffers with packet data via packet_mut()
  3. Transmission: transmit() queues buffers to hardware
  4. Recycling: recycle_tx_buffers() reclaims completed transmissions
  5. Reception: receive() returns buffers with incoming packets
  6. Return: recycle_rx_buffer() returns buffers to the receive queue

Data Flow and Network Driver Integration

The network driver interface integrates with the broader ArceOS driver framework through standardized error handling and device type classification.

Integration with Foundation Layer

flowchart TD
subgraph subGraph2["Hardware Implementations"]
    FxmacNic["FxmacNic"]
    IxgbeNic["IxgbeNic"]
    VirtIoNetDev["VirtIoNetDev"]
end
subgraph subGraph1["Network Layer (axdriver_net)"]
    NetDriverOps["NetDriverOps"]
    EthernetAddress["EthernetAddress"]
    NetBufPtr["NetBufPtr"]
    NetBuf["NetBuf"]
    NetBufPool["NetBufPool"]
end
subgraph subGraph0["Foundation Layer (axdriver_base)"]
    BaseDriverOps["BaseDriverOps"]
    DeviceType["DeviceType::Net"]
    DevResult["DevResult"]
    DevError["DevError"]
end

BaseDriverOps --> NetDriverOps
DevResult --> NetDriverOps
DeviceType --> NetDriverOps
NetBuf --> NetBufPtr
NetBufPool --> NetBuf
NetBufPtr --> NetDriverOps
NetDriverOps --> FxmacNic
NetDriverOps --> IxgbeNic
NetDriverOps --> VirtIoNetDev

Sources: axdriver_net/src/lib.rs(L16 - L19)  axdriver_net/src/lib.rs(L25) 

The network driver interface serves as the bridge between the generic driver framework and specific hardware implementations, providing:

  • Consistent error handling through DevResult and DevError types
  • Device type classification through DeviceType::Net
  • Standardized buffer management abstractions
  • Hardware-agnostic network operations

This design enables multiple network hardware implementations to coexist while maintaining a unified programming interface for upper-layer network protocols and applications.

Network Buffer Management

Relevant source files

This document covers the sophisticated buffer allocation and management system used for high-performance network operations in the axdriver framework. The network buffer management provides efficient memory allocation, automatic resource cleanup, and optimized buffer layouts for packet processing.

For information about the network driver interface that uses these buffers, see Network Driver Interface. For details on how specific hardware implementations utilize this buffer system, see Hardware Implementations.

Buffer Structure and Layout

The core of the network buffer system is the NetBuf structure, which provides a flexible buffer layout optimized for network packet processing with separate header and packet regions.

NetBuf Memory Layout

flowchart TD
subgraph subGraph1["Size Tracking"]
    HL["header_len"]
    PL["packet_len"]
    CAP["capacity"]
end
subgraph subGraph0["NetBuf Memory Layout"]
    BP["buf_ptr"]
    H["Header Region"]
    P["Packet Region"]
    U["Unused Region"]
end

BP --> H
CAP --> U
H --> P
HL --> H
P --> U
PL --> P

The NetBuf structure implements a three-region memory layout where the header region stores protocol headers, the packet region contains the actual data payload, and unused space allows for future expansion without reallocation.

Sources: axdriver_net/src/net_buf.rs(L19 - L38) 

Core Buffer Operations

OperationMethodPurpose
Header Accessheader()Read-only access to header region
Packet Accesspacket()/packet_mut()Access to packet data
Combined Accesspacket_with_header()Contiguous header + packet view
Raw Bufferraw_buf()/raw_buf_mut()Access to entire buffer
Size Managementset_header_len()/set_packet_len()Adjust region boundaries

The buffer provides const methods for efficient access patterns and maintains safety through debug assertions that prevent region overlap.

Sources: axdriver_net/src/net_buf.rs(L43 - L103) 

Memory Pool Architecture

The NetBufPool provides high-performance buffer allocation through pre-allocated memory pools, eliminating the overhead of individual allocations during packet processing.

Pool Allocation Strategy

flowchart TD
subgraph subGraph2["Allocation Process"]
    ALLOC["alloc()"]
    OFFSET["pop offset from free_list"]
    NETBUF["create NetBuf"]
end
subgraph subGraph1["Memory Layout"]
    B0["Buffer 0"]
    B1["Buffer 1"]
    B2["Buffer 2"]
    BN["Buffer N"]
end
subgraph subGraph0["NetBufPool Structure"]
    POOL["pool: Vec"]
    FREELIST["free_list: Mutex>"]
    META["capacity, buf_len"]
end

ALLOC --> OFFSET
B0 --> B1
B1 --> B2
B2 --> BN
FREELIST --> OFFSET
META --> NETBUF
OFFSET --> NETBUF
POOL --> B0

The pool divides a large contiguous memory allocation into fixed-size buffers, maintaining a free list of available buffer offsets for O(1) allocation and deallocation operations.

Sources: axdriver_net/src/net_buf.rs(L133 - L165) 

Pool Configuration and Constraints

ParameterRangePurpose
MIN_BUFFER_LEN1526 bytesMinimum Ethernet frame size
MAX_BUFFER_LEN65535 bytesMaximum buffer allocation
capacity> 0Number of buffers in pool

The pool validates buffer sizes against network protocol requirements and prevents invalid configurations through compile-time constants and runtime checks.

Sources: axdriver_net/src/net_buf.rs(L8 - L152) 

Buffer Lifecycle Management

The network buffer system implements RAII (Resource Acquisition Is Initialization) patterns for automatic memory management and integration with raw pointer operations for hardware drivers.

Allocation and Deallocation Flow

stateDiagram-v2
[*] --> PoolFree : "Pool initialized"
PoolFree --> Allocated : "alloc() / alloc_boxed()"
Allocated --> InUse : "NetBuf created"
InUse --> BufPtr : "into_buf_ptr()"
BufPtr --> Restored : "from_buf_ptr()"
Restored --> InUse : "NetBuf restored"
InUse --> Deallocated : "Drop trait"
BufPtr --> Deallocated : "Drop from ptr"
Deallocated --> PoolFree : "dealloc()"
PoolFree --> [*] : "Pool destroyed"

The lifecycle supports both high-level RAII management through Drop implementation and low-level pointer conversion for hardware driver integration.

Sources: axdriver_net/src/net_buf.rs(L105 - L131) 

Raw Pointer Integration

The buffer system provides conversion to and from NetBufPtr for integration with hardware drivers that require raw memory pointers:

OperationMethodSafety Requirements
To Pointerinto_buf_ptr()Consumes NetBuf, transfers ownership
From Pointerfrom_buf_ptr()Unsafe, requires priorinto_buf_ptr()call
Pointer CreationNetBufPtr::new()Hardware driver responsibility

This dual-mode operation allows the same buffer to be used efficiently in both safe Rust code and unsafe hardware interaction contexts.

Sources: axdriver_net/src/net_buf.rs(L104 - L123) 

Thread Safety and Concurrency

The buffer management system provides thread-safe operations through careful synchronization design:

flowchart TD
subgraph subGraph1["Concurrent Operations"]
    T1["Thread 1: alloc()"]
    T2["Thread 2: alloc()"]
    T3["Thread 3: drop()"]
end
subgraph subGraph0["Thread Safety Design"]
    NETBUF["NetBuf: Send + Sync"]
    POOL["NetBufPool: Arc"]
    FREELIST["free_list: Mutex>"]
end

NETBUF --> POOL
POOL --> FREELIST
T1 --> FREELIST
T2 --> FREELIST
T3 --> FREELIST

The NetBufPool uses Arc for shared ownership and Mutex protection of the free list, enabling concurrent allocation and deallocation from multiple threads while maintaining memory safety.

Sources: axdriver_net/src/net_buf.rs(L40 - L207) 

Integration with Network Drivers

The buffer management system integrates with the broader network driver ecosystem through standardized interfaces and efficient memory patterns designed for high-throughput network operations.

Buffer Type Relationships

flowchart TD
subgraph subGraph1["Driver Integration"]
    NETDRIVEROPS["NetDriverOps"]
    TRANSMIT["transmit()"]
    RECEIVE["receive()"]
    ALLOC["alloc_tx_buffer()"]
    RECYCLE["recycle_rx_buffer()"]
end
subgraph subGraph0["Buffer Types"]
    NETBUF["NetBuf"]
    NETBUFBOX["NetBufBox = Box"]
    NETBUFPTR["NetBufPtr"]
    POOL["NetBufPool"]
end

ALLOC --> NETBUFBOX
NETBUF --> NETBUFBOX
NETBUFBOX --> NETBUFPTR
NETDRIVEROPS --> ALLOC
NETDRIVEROPS --> RECEIVE
NETDRIVEROPS --> RECYCLE
NETDRIVEROPS --> TRANSMIT
POOL --> NETBUF
RECEIVE --> NETBUFBOX
RECYCLE --> NETBUFBOX
TRANSMIT --> NETBUFBOX

Network drivers utilize the buffer system through the NetDriverOps trait methods, providing standardized buffer allocation and recycling operations that maintain pool efficiency across different hardware implementations.

Sources: axdriver_net/src/net_buf.rs(L1 - L208) 

Hardware Implementations

Relevant source files

This document covers the concrete network hardware driver implementations that provide device-specific functionality within the axdriver network subsystem. These implementations demonstrate how the abstract NetDriverOps trait is realized for specific network controllers, including buffer management strategies and hardware abstraction layer integration.

For information about the network driver interface abstractions, see Network Driver Interface. For details about buffer management patterns, see Network Buffer Management.

Overview

The axdriver network subsystem includes two primary hardware implementations that showcase different architectural approaches:

DriverHardware TargetBuffer StrategyExternal Dependencies
FXmacNicPhytiumPi Ethernet ControllerQueue-based RX bufferingfxmac_rscrate
IxgbeNicIntel 10GbE ControllerMemory pool allocationixgbe-drivercrate

Both implementations provide the same NetDriverOps interface while handling hardware-specific details internally.

FXmac Network Controller Implementation

Architecture and Integration

The FXmacNic struct provides an implementation for PhytiumPi Ethernet controllers using a queue-based approach for receive buffer management.

flowchart TD
subgraph subGraph2["Trait Implementation"]
    BASE_OPS["BaseDriverOps"]
    NET_OPS["NetDriverOps"]
    DEVICE_NAME["device_name()"]
    MAC_ADDR["mac_address()"]
    RECEIVE["receive()"]
    TRANSMIT["transmit()"]
end
subgraph subGraph1["External Hardware Layer"]
    FXMAC_RS["fxmac_rs crate"]
    XMAC_INIT["xmac_init()"]
    GET_MAC["FXmacGetMacAddress()"]
    RECV_HANDLER["FXmacRecvHandler()"]
    TX_FUNC["FXmacLwipPortTx()"]
end
subgraph subGraph0["FXmacNic Driver Structure"]
    FXMAC_STRUCT["FXmacNic"]
    INNER["inner: &'static mut FXmac"]
    HWADDR["hwaddr: [u8; 6]"]
    RX_QUEUE["rx_buffer_queue: VecDeque"]
end

BASE_OPS --> DEVICE_NAME
FXMAC_STRUCT --> BASE_OPS
FXMAC_STRUCT --> HWADDR
FXMAC_STRUCT --> INNER
FXMAC_STRUCT --> NET_OPS
FXMAC_STRUCT --> RX_QUEUE
HWADDR --> GET_MAC
INNER --> XMAC_INIT
NET_OPS --> MAC_ADDR
NET_OPS --> RECEIVE
NET_OPS --> TRANSMIT
RECEIVE --> RECV_HANDLER
TRANSMIT --> TX_FUNC

Sources: axdriver_net/src/fxmac.rs(L1 - L145) 

Initialization and Hardware Integration

The FXmacNic::init() function demonstrates the initialization pattern for hardware-dependent drivers:

flowchart TD
subgraph subGraph0["Initialization Sequence"]
    MAPPED_REGS["mapped_regs: usize"]
    INIT_QUEUE["VecDeque::with_capacity(QS)"]
    GET_MAC_ADDR["FXmacGetMacAddress(&mut hwaddr, 0)"]
    XMAC_INIT_CALL["xmac_init(&hwaddr)"]
    CONSTRUCT["FXmacNic { inner, hwaddr, rx_buffer_queue }"]
end

GET_MAC_ADDR --> XMAC_INIT_CALL
INIT_QUEUE --> GET_MAC_ADDR
MAPPED_REGS --> INIT_QUEUE
XMAC_INIT_CALL --> CONSTRUCT

The driver uses a fixed queue size of 64 (QS = 64) for both receive and transmit operations, defined as a constant.

Sources: axdriver_net/src/fxmac.rs(L16)  axdriver_net/src/fxmac.rs(L30 - L45) 

Buffer Management Strategy

The FXmac implementation uses a simple queue-based approach for managing received packets:

  • Receive Path: Uses VecDeque<NetBufPtr> to queue incoming packets from FXmacRecvHandler()
  • Transmit Path: Allocates Box<Vec<u8>> for each transmission
  • Buffer Recycling: Drops allocated boxes directly using Box::from_raw()

The receive() method demonstrates this pattern by first checking the local queue, then polling the hardware.

Sources: axdriver_net/src/fxmac.rs(L92 - L118)  axdriver_net/src/fxmac.rs(L134 - L144) 

Intel ixgbe Network Controller Implementation

Architecture and Memory Pool Integration

The IxgbeNic struct implements a more sophisticated memory management approach using pre-allocated memory pools:

flowchart TD
subgraph subGraph2["Generic Parameters"]
    HAL_PARAM["H: IxgbeHal"]
    QS_PARAM["QS: queue size"]
    QN_PARAM["QN: queue number"]
end
subgraph subGraph1["External Hardware Layer"]
    IXGBE_DRIVER["ixgbe-driver crate"]
    IXGBE_DEVICE["IxgbeDevice"]
    MEM_POOL_TYPE["MemPool"]
    IXGBE_NETBUF["IxgbeNetBuf"]
    NIC_DEVICE["NicDevice trait"]
end
subgraph subGraph0["IxgbeNic Driver Structure"]
    IXGBE_STRUCT["IxgbeNic"]
    INNER_DEV["inner: IxgbeDevice"]
    MEM_POOL["mem_pool: Arc"]
    RX_QUEUE["rx_buffer_queue: VecDeque"]
end

INNER_DEV --> IXGBE_DEVICE
IXGBE_DEVICE --> IXGBE_NETBUF
IXGBE_DEVICE --> NIC_DEVICE
IXGBE_STRUCT --> HAL_PARAM
IXGBE_STRUCT --> INNER_DEV
IXGBE_STRUCT --> MEM_POOL
IXGBE_STRUCT --> QN_PARAM
IXGBE_STRUCT --> QS_PARAM
IXGBE_STRUCT --> RX_QUEUE
MEM_POOL --> MEM_POOL_TYPE

Sources: axdriver_net/src/ixgbe.rs(L18 - L28) 

Memory Pool Configuration

The ixgbe driver uses predefined memory pool parameters for efficient buffer allocation:

ParameterValuePurpose
MEM_POOL4096Total memory pool entries
MEM_POOL_ENTRY_SIZE2048Size per pool entry in bytes
RX_BUFFER_SIZE1024Receive buffer queue capacity
RECV_BATCH_SIZE64Batch size for receive operations

These constants ensure optimal performance for high-throughput network operations.

Sources: axdriver_net/src/ixgbe.rs(L13 - L16) 

NetBufPtr Conversion Pattern

The ixgbe implementation demonstrates a sophisticated buffer conversion pattern between IxgbeNetBuf and NetBufPtr:

flowchart TD
subgraph subGraph0["Buffer Conversion Flow"]
    IXGBE_BUF["IxgbeNetBuf"]
    MANUALLY_DROP["ManuallyDrop::new(buf)"]
    PACKET_PTR["buf.packet_mut().as_mut_ptr()"]
    NET_BUF_PTR["NetBufPtr::new(raw_ptr, buf_ptr, len)"]
    subgraph subGraph1["Reverse Conversion"]
        NET_BUF_PTR_REV["NetBufPtr"]
        CONSTRUCT["IxgbeNetBuf::construct()"]
        IXGBE_BUF_REV["IxgbeNetBuf"]
        POOL_ENTRY["buf.pool_entry() as *mut u8"]
    end
end

CONSTRUCT --> IXGBE_BUF_REV
IXGBE_BUF --> MANUALLY_DROP
MANUALLY_DROP --> PACKET_PTR
MANUALLY_DROP --> POOL_ENTRY
NET_BUF_PTR_REV --> CONSTRUCT
PACKET_PTR --> NET_BUF_PTR
POOL_ENTRY --> NET_BUF_PTR

The From<IxgbeNetBuf> implementation uses ManuallyDrop to avoid premature deallocation while transferring ownership to the NetBufPtr abstraction.

Sources: axdriver_net/src/ixgbe.rs(L143 - L162) 

Trait Implementation Patterns

BaseDriverOps Implementation

Both drivers implement BaseDriverOps with device-specific identification:

Driverdevice_name()Return ValueSource
FXmacNic"cdns,phytium-gem-1.0"Hardware device tree compatible string
IxgbeNicself.inner.get_driver_name()Delegated to underlying driver

Sources: axdriver_net/src/fxmac.rs(L48 - L56)  axdriver_net/src/ixgbe.rs(L50 - L58) 

NetDriverOps Core Methods

The receive and transmit implementations showcase different architectural approaches:

flowchart TD
subgraph subGraph1["Ixgbe Receive Pattern"]
    IXGBE_CAN_RECV["can_receive(): !queue.is_empty() || inner.can_receive(0)"]
    IXGBE_RECV_CHECK["Check local queue first"]
    IXGBE_BATCH_RECV["inner.receive_packets(0, RECV_BATCH_SIZE, closure)"]
    IXGBE_QUEUE_PUSH["Closure pushes to rx_buffer_queue"]
    FXMAC_CAN_RECV["can_receive(): !rx_buffer_queue.is_empty()"]
    FXMAC_RECV_CHECK["Check local queue first"]
    FXMAC_HW_POLL["FXmacRecvHandler(self.inner)"]
    FXMAC_QUEUE_PUSH["Push to rx_buffer_queue"]
end
subgraph subGraph0["FXmac Receive Pattern"]
    IXGBE_CAN_RECV["can_receive(): !queue.is_empty() || inner.can_receive(0)"]
    IXGBE_RECV_CHECK["Check local queue first"]
    IXGBE_BATCH_RECV["inner.receive_packets(0, RECV_BATCH_SIZE, closure)"]
    IXGBE_QUEUE_PUSH["Closure pushes to rx_buffer_queue"]
    FXMAC_CAN_RECV["can_receive(): !rx_buffer_queue.is_empty()"]
    FXMAC_RECV_CHECK["Check local queue first"]
    FXMAC_HW_POLL["FXmacRecvHandler(self.inner)"]
    FXMAC_QUEUE_PUSH["Push to rx_buffer_queue"]
end

FXMAC_CAN_RECV --> FXMAC_RECV_CHECK
FXMAC_HW_POLL --> FXMAC_QUEUE_PUSH
FXMAC_RECV_CHECK --> FXMAC_HW_POLL
IXGBE_BATCH_RECV --> IXGBE_QUEUE_PUSH
IXGBE_CAN_RECV --> IXGBE_RECV_CHECK
IXGBE_RECV_CHECK --> IXGBE_BATCH_RECV

Sources: axdriver_net/src/fxmac.rs(L71 - L118)  axdriver_net/src/ixgbe.rs(L73 - L124) 

Hardware Abstraction Layer Integration

External Crate Dependencies

Both implementations demonstrate integration with external hardware abstraction crates:

DriverExternal CrateKey Functions Used
FXmacNicfxmac_rsxmac_init,FXmacGetMacAddress,FXmacRecvHandler,FXmacLwipPortTx
IxgbeNicixgbe-driverIxgbeDevice::init,MemPool::allocate,receive_packets,send

This pattern allows the axdriver framework to leverage existing hardware-specific implementations while providing a unified interface.

Sources: axdriver_net/src/fxmac.rs(L7)  axdriver_net/src/ixgbe.rs(L6 - L7) 

Error Handling Translation

Both drivers translate hardware-specific errors to the common DevError type:

flowchart TD
subgraph subGraph1["Ixgbe Error Translation"]
    IXGBE_ERR["IxgbeError"]
    IXGBE_NOT_READY["IxgbeError::NotReady"]
    IXGBE_QUEUE_FULL["IxgbeError::QueueFull"]
    DEV_AGAIN["DevError::Again"]
    DEV_BAD_STATE["DevError::BadState"]
    FXMAC_RET["FXmacLwipPortTx return value"]
    FXMAC_CHECK["ret < 0"]
    FXMAC_ERROR["DevError::Again"]
end
subgraph subGraph0["FXmac Error Translation"]
    IXGBE_NOT_READY["IxgbeError::NotReady"]
    DEV_AGAIN["DevError::Again"]
    FXMAC_RET["FXmacLwipPortTx return value"]
    FXMAC_CHECK["ret < 0"]
    FXMAC_ERROR["DevError::Again"]
end

FXMAC_CHECK --> FXMAC_ERROR
FXMAC_RET --> FXMAC_CHECK
IXGBE_ERR --> DEV_BAD_STATE
IXGBE_ERR --> IXGBE_NOT_READY
IXGBE_ERR --> IXGBE_QUEUE_FULL
IXGBE_NOT_READY --> DEV_AGAIN
IXGBE_QUEUE_FULL --> DEV_AGAIN

Sources: axdriver_net/src/fxmac.rs(L127 - L131)  axdriver_net/src/ixgbe.rs(L118 - L122)  axdriver_net/src/ixgbe.rs(L130 - L134) 

Block Storage Drivers

Relevant source files

Purpose and Scope

The block storage driver subsystem provides a unified interface for accessing block-oriented storage devices such as disks, flash storage, and RAM disks within the ArceOS driver framework. This subsystem defines common traits and types that allow different storage hardware implementations to be used interchangeably through a consistent API.

For information about the foundation layer that all drivers build upon, see Foundation Layer (axdriver_base). For detailed documentation of network drivers, see Network Drivers. For VirtIO block device integration, see VirtIO Integration.

Block Storage Architecture Overview

The block storage subsystem follows the same architectural patterns as other driver types in the axdriver_crates ecosystem, building upon the foundation provided by axdriver_base while defining storage-specific operations.

Block Storage Component Architecture

flowchart TD
subgraph subGraph2["Concrete Implementations"]
    RamDisk["ramdisk module(feature: ramdisk)"]
    BCM2835SDHCI["bcm2835sdhci module(feature: bcm2835-sdhci)"]
    VirtIOBlock["VirtIO Block(via axdriver_virtio)"]
end
subgraph subGraph1["axdriver_block Interface"]
    BlockDriverOps["BlockDriverOps"]
    BlockTraitMethods["num_blocks()block_size()read_block()write_block()flush()"]
end
subgraph subGraph0["axdriver_base Foundation"]
    BaseDriverOps["BaseDriverOps"]
    DevResult["DevResult"]
    DevError["DevError"]
    DeviceType["DeviceType"]
end

BaseDriverOps --> BlockDriverOps
BlockDriverOps --> BCM2835SDHCI
BlockDriverOps --> BlockTraitMethods
BlockDriverOps --> RamDisk
BlockDriverOps --> VirtIOBlock
DevError --> BlockDriverOps
DevResult --> BlockDriverOps
DeviceType --> BlockDriverOps

Sources: axdriver_block/src/lib.rs(L1 - L39)  axdriver_block/Cargo.toml(L1 - L23) 

Core Block Storage Interface

The BlockDriverOps trait defines the essential operations that all block storage devices must support. This trait extends BaseDriverOps to inherit common driver functionality while adding storage-specific methods.

BlockDriverOps Trait Structure

flowchart TD
subgraph subGraph2["Error Handling"]
    DevResult["DevResult"]
    DevError["DevError"]
end
subgraph subGraph1["BlockDriverOps Methods"]
    num_blocks["num_blocks()-> u64"]
    block_size["block_size()-> usize"]
    read_block["read_block()(block_id, buf)-> DevResult"]
    write_block["write_block()(block_id, buf)-> DevResult"]
    flush["flush()-> DevResult"]
end
subgraph BaseDriverOps["BaseDriverOps"]
    device_name["device_name()"]
    device_type["device_type()"]
end

DevResult --> DevError
device_name --> num_blocks
device_type --> num_blocks
flush --> DevResult
read_block --> DevResult
write_block --> DevResult

Block Operations

The trait provides five core operations for block-level storage access:

MethodPurposeParametersReturn Type
num_blocks()Get total number of blocksNoneu64
block_size()Get size of each block in bytesNoneusize
read_block()Read data from storage blocksblock_id: u64, buf: &mut [u8]DevResult
write_block()Write data to storage blocksblock_id: u64, buf: &[u8]DevResult
flush()Ensure all pending writes are committedNoneDevResult

The read and write operations support multi-block transfers when the buffer size exceeds a single block size, allowing for efficient bulk data operations.

Sources: axdriver_block/src/lib.rs(L16 - L38) 

Available Block Storage Implementations

The block storage subsystem supports multiple implementation types through conditional compilation features, allowing systems to include only the drivers they need.

Implementation Feature Matrix

flowchart TD
subgraph subGraph3["Target Use Cases"]
    embedded["Embedded SystemsBCM2835 SDHCI"]
    testing["Testing & DevelopmentRAM Disk"]
    virtualized["Virtualized EnvironmentsVirtIO Block"]
end
subgraph subGraph2["External Dependencies"]
    bcm2835_dep["bcm2835-sdhci crate(optional)"]
end
subgraph subGraph1["Implementation Modules"]
    ramdisk_mod["pub mod ramdisk"]
    sdhci_mod["pub mod bcm2835sdhci"]
end
subgraph subGraph0["Feature Gates"]
    ramdisk_feature["feature = 'ramdisk'"]
    sdhci_feature["feature = 'bcm2835-sdhci'"]
    default_feature["default = []"]
end

bcm2835_dep --> embedded
ramdisk_feature --> ramdisk_mod
ramdisk_mod --> testing
sdhci_feature --> bcm2835_dep
sdhci_feature --> sdhci_mod
sdhci_mod --> embedded

Implementation Types

  1. RAM Disk (ramdisk feature)
  • In-memory block storage for testing and temporary storage
  • No external dependencies
  • Useful for development and system testing
  1. BCM2835 SDHCI (bcm2835-sdhci feature)
  • Hardware driver for BCM2835 (Raspberry Pi) SD card interface
  • Depends on external bcm2835-sdhci crate
  • Targets embedded ARM systems
  1. VirtIO Block (via axdriver_virtio)
  • Virtual block devices in virtualized environments
  • Integrated through the VirtIO subsystem
  • Supports both MMIO and PCI transport layers

Sources: axdriver_block/Cargo.toml(L14 - L22)  axdriver_block/src/lib.rs(L6 - L10) 

Integration with Driver Framework

The block storage subsystem integrates seamlessly with the broader axdriver framework through consistent use of foundation types and patterns.

Framework Integration Points

flowchart TD
subgraph subGraph2["Runtime Polymorphism"]
    trait_objects["dyn BlockDriverOps"]
    dispatch["Dynamic dispatch"]
end
subgraph subGraph1["Driver Discovery"]
    device_type_method["device_type() -> DeviceType"]
    device_name_method["device_name() -> &str"]
end
subgraph subGraph0["Type System Integration"]
    DeviceType_Block["DeviceType::Block"]
    BaseDriverOps_impl["BaseDriverOps implementation"]
    Error_handling["DevResult/DevError"]
end

BaseDriverOps_impl --> device_name_method
DeviceType_Block --> device_type_method
Error_handling --> dispatch
device_name_method --> trait_objects
device_type_method --> trait_objects
trait_objects --> dispatch

Framework Consistency

  • Type Safety: All operations return DevResult for consistent error handling
  • Device Identification: Drivers report DeviceType::Block for proper categorization
  • Polymorphism: All implementations can be used through dyn BlockDriverOps trait objects
  • Feature Gating: Optional implementations keep the framework lightweight

The block storage subsystem exemplifies the modular design principles of the axdriver framework, providing a clean separation between interface definition and hardware-specific implementations while maintaining type safety and performance.

Sources: axdriver_block/src/lib.rs(L13 - L38) 

Block Driver Interface

Relevant source files

Purpose and Scope

This document covers the core block storage driver interface defined in the axdriver_block crate. The interface provides a standardized abstraction for block-oriented storage devices such as disks, RAM disks, and SD cards. This page focuses specifically on the BlockDriverOps trait and fundamental block device abstractions.

For information about specific block device implementations like RAM disk and BCM2835 SDHCI drivers, see Block Device Implementations. For the foundation layer that all drivers build upon, see Foundation Layer (axdriver_base).

Trait Hierarchy and Base Integration

Block Driver Trait Structure

flowchart TD
subgraph subGraph0["Core Methods"]
    NumBlocks["num_blocks()→ u64"]
    BlockSize["block_size()→ usize"]
    ReadBlock["read_block(block_id, buf)→ DevResult"]
    WriteBlock["write_block(block_id, buf)→ DevResult"]
    Flush["flush()→ DevResult"]
end
BaseDriverOps["BaseDriverOps(from axdriver_base)"]
BlockDriverOps["BlockDriverOps(extends BaseDriverOps)"]
DeviceType["DeviceType::Block(device classification)"]
ErrorTypes["DevResult & DevError(error handling)"]

BaseDriverOps --> BlockDriverOps
BlockDriverOps --> BlockSize
BlockDriverOps --> DeviceType
BlockDriverOps --> ErrorTypes
BlockDriverOps --> Flush
BlockDriverOps --> NumBlocks
BlockDriverOps --> ReadBlock
BlockDriverOps --> WriteBlock

The BlockDriverOps trait extends the base driver interface with block-specific operations. All block drivers must implement both the base operations (device identification) and block-specific operations (storage access).

Sources: axdriver_block/src/lib.rs(L15 - L38) 

Type System Integration

ComponentPurposeSource
BlockDriverOpsMain trait for block device operationsaxdriver_block/src/lib.rs16
BaseDriverOpsInherited base driver interfaceaxdriver_block/src/lib.rs13
DevResultStandard result type for all operationsaxdriver_block/src/lib.rs13
DevErrorUnified error type across driversaxdriver_block/src/lib.rs13
DeviceTypeDevice classification enumerationaxdriver_block/src/lib.rs13

Sources: axdriver_block/src/lib.rs(L12 - L13) 

Core Interface Methods

Device Geometry Methods

The interface provides two fundamental methods for determining the storage device's physical characteristics:

flowchart TD
Device["Block Storage Device"]
NumBlocks["num_blocks()Returns: u64"]
BlockSize["block_size()Returns: usize"]
TotalSize["Total Device Size= num_blocks() × block_size()"]

BlockSize --> TotalSize
Device --> BlockSize
Device --> NumBlocks
NumBlocks --> TotalSize

The total device capacity is calculated as num_blocks() * block_size(), providing a consistent way to determine storage limits across different hardware implementations.

Sources: axdriver_block/src/lib.rs(L17 - L22) 

Data Access Methods

Block I/O Operations Flow

sequenceDiagram
    participant Client as Client
    participant BlockDriverOpsImplementation as "BlockDriverOps Implementation"
    participant StorageHardware as "Storage Hardware"

    Note over Client,StorageHardware: Read Operation
    Client ->> BlockDriverOpsImplementation: read_block(block_id, &mut buf)
    BlockDriverOpsImplementation ->> StorageHardware: Hardware-specific read
    StorageHardware -->> BlockDriverOpsImplementation: Raw data
    BlockDriverOpsImplementation -->> Client: DevResult (success/error)
    Note over Client,StorageHardware: Write Operation
    Client ->> BlockDriverOpsImplementation: write_block(block_id, &buf)
    BlockDriverOpsImplementation ->> StorageHardware: Hardware-specific write
    StorageHardware -->> BlockDriverOpsImplementation: Write acknowledgment
    BlockDriverOpsImplementation -->> Client: DevResult (success/error)
    Note over Client,StorageHardware: Flush Operation
    Client ->> BlockDriverOpsImplementation: flush()
    BlockDriverOpsImplementation ->> StorageHardware: Commit pending writes
    StorageHardware -->> BlockDriverOpsImplementation: Flush complete
    BlockDriverOpsImplementation -->> Client: DevResult (success/error)

The interface defines three primary data access operations:

Read Block Operation

  • Signature: read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult
  • Purpose: Reads data from storage starting at the specified block ID
  • Multi-block Support: Buffer size may exceed block size to read multiple contiguous blocks
  • Error Handling: Returns DevResult for consistent error propagation

Write Block Operation

  • Signature: write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult
  • Purpose: Writes data to storage starting at the specified block ID
  • Multi-block Support: Buffer size may exceed block size to write multiple contiguous blocks
  • Error Handling: Returns DevResult for consistent error propagation

Flush Operation

  • Signature: flush(&mut self) -> DevResult
  • Purpose: Forces all pending write operations to be committed to persistent storage
  • Consistency: Ensures data durability and consistency guarantees

Sources: axdriver_block/src/lib.rs(L24 - L37) 

Error Handling and Result Types

The block driver interface uses the unified error handling system from axdriver_base:

TypePurposeUsage Pattern
DevResultStandard result type for all operationsfn operation() -> DevResult
DevErrorUnified error enumerationConsistent error reporting across drivers

All block operations return DevResult, enabling consistent error handling across different hardware implementations and allowing higher-level code to handle errors uniformly regardless of the underlying storage technology.

Sources: axdriver_block/src/lib.rs(L13)  axdriver_block/src/lib.rs(L28)  axdriver_block/src/lib.rs(L34)  axdriver_block/src/lib.rs(L37) 

Design Patterns and Principles

Mutable Reference Pattern

All data-modifying operations (read_block, write_block, flush) require a mutable reference to self, ensuring exclusive access during I/O operations and preventing data races in concurrent scenarios.

Multi-Block Operation Support

The interface design explicitly supports multi-block operations through buffer sizing. When the provided buffer exceeds the device's block size, the implementation automatically handles reading or writing multiple contiguous blocks, improving I/O efficiency for large transfers.

Hardware Abstraction Layer

The trait provides a hardware-agnostic interface that abstracts away device-specific details while maintaining the essential block-oriented semantics that storage hardware typically provides.

Sources: axdriver_block/src/lib.rs(L15 - L38) 

Feature-Based Compilation Integration

The crate supports conditional compilation for specific implementations:


This design allows systems to include only the block driver implementations they need, reducing binary size and compilation time for embedded or specialized deployments.

Sources: axdriver_block/src/lib.rs(L6 - L10) 

Block Device Implementations

Relevant source files

This document covers the concrete implementations of block storage devices in the axdriver framework. These implementations demonstrate how the abstract BlockDriverOps trait is realized for different types of storage hardware and virtual devices. For information about the block driver interface and trait definitions, see Block Driver Interface.

The implementations covered include both software-based virtual devices and hardware-specific drivers, showcasing the flexibility of the block driver abstraction layer.

Implementation Overview

The axdriver framework provides two primary block device implementations, each serving different use cases and demonstrating different implementation patterns.

flowchart TD
subgraph subGraph3["Implementation Characteristics"]
    RamCharacteristics["• Synchronous operations• No initialization required• Perfect reliability• 512-byte blocks"]
    SDHCICharacteristics["• Hardware initialization• Error mapping required• Buffer alignment constraints• Variable block sizes"]
end
subgraph subGraph2["Hardware Implementation"]
    SDHCIDriver["SDHCIDriver"]
    EmmcCtl["EmmcCtl (from bcm2835_sdhci)"]
    SDCardHW["SD Card Hardware"]
end
subgraph subGraph1["Virtual Device Implementation"]
    RamDisk["RamDisk"]
    VecData["Vec<u8> data"]
    MemoryOps["In-memory operations"]
end
subgraph subGraph0["Block Driver Trait System"]
    BaseDriverOps["BaseDriverOps"]
    BlockDriverOps["BlockDriverOps"]
end

BaseDriverOps --> BlockDriverOps
BlockDriverOps --> RamDisk
BlockDriverOps --> SDHCIDriver
EmmcCtl --> SDCardHW
RamDisk --> MemoryOps
RamDisk --> RamCharacteristics
RamDisk --> VecData
SDHCIDriver --> EmmcCtl
SDHCIDriver --> SDHCICharacteristics

Block Device Implementation Architecture

Sources: axdriver_block/src/ramdisk.rs(L1 - L101)  axdriver_block/src/bcm2835sdhci.rs(L1 - L89) 

RamDisk Implementation

The RamDisk implementation provides an in-memory block device primarily used for testing and development. It stores all data in a contiguous Vec<u8> and performs all operations synchronously without any possibility of failure.

Core Structure and Initialization

flowchart TD
subgraph subGraph2["Block Operations"]
    read_block["read_block()"]
    write_block["write_block()"]
    num_blocks["num_blocks()"]
    block_size["block_size()"]
end
subgraph subGraph1["RamDisk Creation Patterns"]
    new["RamDisk::new(size_hint)"]
    from["RamDisk::from(buf)"]
    default["RamDisk::default()"]
    align_up["align_up() function"]
    vec_alloc["vec![0; size]"]
    subgraph subGraph0["Internal State"]
        size_field["size: usize"]
        data_field["data: Vec<u8>"]
    end
end

align_up --> vec_alloc
data_field --> read_block
data_field --> write_block
from --> align_up
new --> align_up
size_field --> num_blocks
vec_alloc --> data_field
vec_alloc --> size_field

RamDisk Internal Structure and Operations

The RamDisk struct contains two fields: size for the total allocated size and data for the actual storage vector. All operations work directly with this vector using standard slice operations.

MethodImplementation PatternKey Characteristics
new()Size alignment + zero-filled vectorAlways succeeds, rounds up to block boundary
from()Copy existing data + size alignmentPreserves input data, pads to block boundary
read_block()Direct slice copy from vectorValidates bounds and block alignment
write_block()Direct slice copy to vectorValidates bounds and block alignment

Sources: axdriver_block/src/ramdisk.rs(L12 - L46)  axdriver_block/src/ramdisk.rs(L58 - L96) 

Memory Management and Block Operations

The RamDisk implementation uses a fixed block size of 512 bytes and enforces strict alignment requirements:

flowchart TD
subgraph subGraph2["Data Operations"]
    ReadOp["buf.copy_from_slice(&self.data[offset..])"]
    WriteOp["self.data[offset..].copy_from_slice(buf)"]
end
subgraph subGraph1["Validation Rules"]
    BoundsCheck["offset + buf.len() <= self.size"]
    AlignmentCheck["buf.len() % BLOCK_SIZE == 0"]
end
subgraph subGraph0["Block Operation Flow"]
    ValidateParams["Validate Parameters"]
    CheckBounds["Check Bounds"]
    CheckAlignment["Check Block Alignment"]
    CopyData["Copy Data"]
end

CheckAlignment --> AlignmentCheck
CheckAlignment --> CopyData
CheckBounds --> BoundsCheck
CheckBounds --> CheckAlignment
CopyData --> ReadOp
CopyData --> WriteOp
ValidateParams --> CheckBounds

RamDisk Block Operation Validation and Execution

Sources: axdriver_block/src/ramdisk.rs(L69 - L91)  axdriver_block/src/ramdisk.rs(L98 - L100) 

SDHCI Implementation

The SDHCIDriver provides access to SD cards on BCM2835-based systems (such as Raspberry Pi). This implementation demonstrates hardware integration patterns and error handling complexity.

Hardware Abstraction and Initialization

flowchart TD
subgraph subGraph2["Driver Wrapper"]
    SDHCIDriver_struct["SDHCIDriver(EmmcCtl)"]
    deal_sdhci_err["deal_sdhci_err() mapper"]
end
subgraph subGraph1["Hardware Abstraction Layer"]
    try_new["SDHCIDriver::try_new()"]
    emmc_new["EmmcCtl::new()"]
    emmc_init["ctrl.init()"]
    bcm2835_sdhci["bcm2835_sdhci crate"]
    EmmcCtl_extern["EmmcCtl"]
    SDHCIError_extern["SDHCIError"]
    BLOCK_SIZE_extern["BLOCK_SIZE constant"]
end
subgraph subGraph0["Initialization Flow"]
    try_new["SDHCIDriver::try_new()"]
    emmc_new["EmmcCtl::new()"]
    emmc_init["ctrl.init()"]
    check_result["Check init result"]
    success["Ok(SDHCIDriver(ctrl))"]
    failure["Err(DevError::Io)"]
    bcm2835_sdhci["bcm2835_sdhci crate"]
end

EmmcCtl_extern --> SDHCIDriver_struct
SDHCIError_extern --> deal_sdhci_err
bcm2835_sdhci --> BLOCK_SIZE_extern
bcm2835_sdhci --> EmmcCtl_extern
bcm2835_sdhci --> SDHCIError_extern
check_result --> failure
check_result --> success
emmc_init --> check_result
emmc_new --> emmc_init
try_new --> emmc_new

SDHCI Driver Hardware Integration Architecture

Sources: axdriver_block/src/bcm2835sdhci.rs(L12 - L24)  axdriver_block/src/bcm2835sdhci.rs(L26 - L37) 

Buffer Alignment and Hardware Operations

The SDHCI implementation requires careful buffer alignment and provides comprehensive error mapping:

flowchart TD
subgraph subGraph2["Hardware Operations"]
    read_block_hw["ctrl.read_block(block_id, 1, aligned_buf)"]
    write_block_hw["ctrl.write_block(block_id, 1, aligned_buf)"]
    get_block_num["ctrl.get_block_num()"]
    get_block_size["ctrl.get_block_size()"]
end
subgraph subGraph1["Error Handling Pipeline"]
    sdhci_errors["SDHCIError variants"]
    deal_sdhci_err_fn["deal_sdhci_err()"]
    dev_errors["DevError variants"]
end
subgraph subGraph0["Buffer Alignment Process"]
    input_buf["Input buffer: &[u8] or &mut [u8]"]
    align_check["buf.align_to::()"]
    validate_alignment["Check prefix/suffix empty"]
    hardware_call["Call EmmcCtl methods"]
end

align_check --> validate_alignment
deal_sdhci_err_fn --> dev_errors
hardware_call --> get_block_num
hardware_call --> get_block_size
hardware_call --> read_block_hw
hardware_call --> sdhci_errors
hardware_call --> write_block_hw
input_buf --> align_check
sdhci_errors --> deal_sdhci_err_fn
validate_alignment --> hardware_call

SDHCI Buffer Alignment and Hardware Operation Flow

Sources: axdriver_block/src/bcm2835sdhci.rs(L49 - L88) 

Implementation Comparison

The two block device implementations demonstrate contrasting approaches to the same abstract interface:

AspectRamDiskSDHCIDriver
InitializationAlways succeeds (new(),from())Can fail (try_new()returnsDevResult)
Storage BackendVecin memoryHardware SD card viabcm2835_sdhci
Error HandlingMinimal (only bounds/alignment)Comprehensive error mapping
Buffer RequirementsAny byte-aligned buffer32-bit aligned buffers required
Block SizeFixed 512 bytes (BLOCK_SIZEconstant)Variable (ctrl.get_block_size())
PerformanceSynchronous memory operationsHardware-dependent timing
Use CasesTesting, temporary storageProduction SD card access

Error Mapping Patterns

flowchart TD
subgraph subGraph2["Common DevError Interface"]
    dev_error_unified["DevError enum"]
end
subgraph subGraph1["SDHCI Error Sources"]
    sdhci_hardware["Hardware failures"]
    sdhci_state["Device state issues"]
    sdhci_resources["Resource constraints"]
    sdhci_complex["9 distinct SDHCIError variants"]
    sdhci_mapped["Mapped to appropriate DevError"]
end
subgraph subGraph0["RamDisk Error Sources"]
    ram_bounds["Bounds checking"]
    ram_alignment["Block alignment"]
    ram_simple["Simple DevError::Io or DevError::InvalidParam"]
end

ram_alignment --> ram_simple
ram_bounds --> ram_simple
ram_simple --> dev_error_unified
sdhci_complex --> sdhci_mapped
sdhci_hardware --> sdhci_complex
sdhci_mapped --> dev_error_unified
sdhci_resources --> sdhci_complex
sdhci_state --> sdhci_complex

Error Handling Strategy Comparison

The error mapping function deal_sdhci_err() provides a complete translation between hardware-specific errors and the unified DevError enum, ensuring consistent error handling across different block device types.

Sources: axdriver_block/src/ramdisk.rs(L69 - L95)  axdriver_block/src/bcm2835sdhci.rs(L26 - L37)  axdriver_block/src/bcm2835sdhci.rs(L49 - L88) 

Display Drivers

Relevant source files

This document covers the display driver subsystem within the axdriver_crates framework. The display drivers provide a unified interface for graphics devices and framebuffer management in ArceOS. This subsystem handles graphics device initialization, framebuffer access, and display output operations.

For network device drivers, see Network Drivers. For block storage drivers, see Block Storage Drivers. For VirtIO display device integration, see VirtIO Integration.

Display Driver Architecture

The display driver subsystem follows the same architectural pattern as other device types in the framework, extending the base driver operations with display-specific functionality.

Display Driver Interface Overview

flowchart TD
subgraph subGraph3["Integration Points"]
    VirtIOGpu["VirtIO GPU Device"]
    PCIGraphics["PCI Graphics Device"]
    MMIODisplay["MMIO Display Device"]
end
subgraph subGraph2["Core Operations"]
    info_op["info() -> DisplayInfo"]
    fb_op["fb() -> FrameBuffer"]
    need_flush_op["need_flush() -> bool"]
    flush_op["flush() -> DevResult"]
end
subgraph subGraph1["Display Interface"]
    DisplayDriverOps["DisplayDriverOps trait"]
    DisplayInfo["DisplayInfo struct"]
    FrameBuffer["FrameBuffer struct"]
end
subgraph Foundation["Foundation"]
    BaseDriverOps["BaseDriverOps"]
    DevResult["DevResult / DevError"]
    DeviceType["DeviceType::Display"]
end

BaseDriverOps --> DisplayDriverOps
DevResult --> flush_op
DisplayDriverOps --> MMIODisplay
DisplayDriverOps --> PCIGraphics
DisplayDriverOps --> VirtIOGpu
DisplayDriverOps --> fb_op
DisplayDriverOps --> flush_op
DisplayDriverOps --> info_op
DisplayDriverOps --> need_flush_op
DisplayInfo --> info_op
FrameBuffer --> fb_op

Sources: axdriver_display/src/lib.rs(L1 - L60) 

The DisplayDriverOps trait extends BaseDriverOps and defines the core interface that all display drivers must implement. This trait provides four essential operations for display management.

Core Data Structures

The display subsystem defines two primary data structures for managing graphics device state and framebuffer access.

DisplayInfo Structure

flowchart TD
subgraph Usage["Usage"]
    info_method["DisplayDriverOps::info()"]
    display_config["Display Configuration"]
    memory_mapping["Memory Mapping Setup"]
end
subgraph subGraph0["DisplayInfo Fields"]
    width["width: u32Visible display width"]
    height["height: u32Visible display height"]
    fb_base_vaddr["fb_base_vaddr: usizeFramebuffer virtual address"]
    fb_size["fb_size: usizeFramebuffer size in bytes"]
end

fb_base_vaddr --> memory_mapping
fb_size --> memory_mapping
height --> display_config
info_method --> fb_base_vaddr
info_method --> fb_size
info_method --> height
info_method --> width
width --> display_config

Sources: axdriver_display/src/lib.rs(L8 - L19) 

The DisplayInfo struct contains essential metadata about the graphics device, including display dimensions and framebuffer memory layout. This information is used by higher-level graphics systems to configure rendering operations.

FrameBuffer Management

flowchart TD
subgraph Integration["Integration"]
    fb_method["DisplayDriverOps::fb()"]
    graphics_rendering["Graphics Rendering"]
    pixel_operations["Pixel Operations"]
end
subgraph subGraph1["Safety Considerations"]
    unsafe_creation["Unsafe raw pointer access"]
    memory_validity["Memory region validity"]
    exclusive_access["Exclusive mutable access"]
end
subgraph subGraph0["FrameBuffer Construction"]
    from_raw_parts_mut["from_raw_parts_mut(ptr: *mut u8, len: usize)"]
    from_slice["from_slice(slice: &mut [u8])"]
    raw_field["_raw: &mut [u8]"]
end

exclusive_access --> raw_field
fb_method --> graphics_rendering
fb_method --> pixel_operations
from_raw_parts_mut --> raw_field
from_slice --> raw_field
memory_validity --> from_raw_parts_mut
raw_field --> fb_method
unsafe_creation --> from_raw_parts_mut

Sources: axdriver_display/src/lib.rs(L21 - L44) 

The FrameBuffer struct provides safe access to the device framebuffer memory. It offers both safe construction from existing slices and unsafe construction from raw pointers for device driver implementations.

DisplayDriverOps Trait Implementation

Required Methods

MethodReturn TypePurpose
info()DisplayInfoRetrieve display configuration and framebuffer metadata
fb()FrameBufferGet access to the framebuffer for rendering operations
need_flush()boolDetermine if explicit flush operations are required
flush()DevResultSynchronize framebuffer contents to the display

Sources: axdriver_display/src/lib.rs(L46 - L59) 

Display Driver Operation Flow

sequenceDiagram
    participant Application as "Application"
    participant DisplayDriverOpsImplementation as "DisplayDriverOps Implementation"
    participant GraphicsHardware as "Graphics Hardware"

    Application ->> DisplayDriverOpsImplementation: info()
    DisplayDriverOpsImplementation ->> GraphicsHardware: Query display configuration
    GraphicsHardware -->> DisplayDriverOpsImplementation: Display dimensions & framebuffer info
    DisplayDriverOpsImplementation -->> Application: DisplayInfo struct
    Application ->> DisplayDriverOpsImplementation: fb()
    DisplayDriverOpsImplementation -->> Application: FrameBuffer reference
    Note over Application: Render graphics to framebuffer
    Application ->> DisplayDriverOpsImplementation: need_flush()
    DisplayDriverOpsImplementation -->> Application: bool (hardware-dependent)
    opt If flush needed
        Application ->> DisplayDriverOpsImplementation: flush()
        DisplayDriverOpsImplementation ->> GraphicsHardware: Synchronize framebuffer
        GraphicsHardware -->> DisplayDriverOpsImplementation: Completion status
        DisplayDriverOpsImplementation -->> Application: DevResult
    end

Sources: axdriver_display/src/lib.rs(L46 - L59) 

The need_flush() method allows drivers to indicate whether they require explicit flush operations. Some hardware configurations automatically update the display when framebuffer memory is modified, while others require explicit synchronization commands.

Integration with Driver Framework

Base Driver Integration

The display driver system inherits core functionality from the axdriver_base foundation layer:

flowchart TD
subgraph subGraph2["Error Handling"]
    DevResult["DevResult"]
    DevError["DevError enum"]
end
subgraph subGraph1["Display-Specific Extensions"]
    info["info() -> DisplayInfo"]
    fb["fb() -> FrameBuffer"]
    need_flush["need_flush() -> bool"]
    flush["flush() -> DevResult"]
end
subgraph subGraph0["Inherited from BaseDriverOps"]
    device_name["device_name() -> &str"]
    device_type["device_type() -> DeviceType"]
end
DisplayDriverOps["DisplayDriverOps"]

DevResult --> DevError
DisplayDriverOps --> fb
DisplayDriverOps --> flush
DisplayDriverOps --> info
DisplayDriverOps --> need_flush
device_name --> DisplayDriverOps
device_type --> DisplayDriverOps
flush --> DevResult

Sources: axdriver_display/src/lib.rs(L5 - L6)  axdriver_display/src/lib.rs(L46 - L59) 

Device Type Classification

Display drivers return DeviceType::Display from their device_type() method, enabling the driver framework to properly categorize and route display devices during system initialization.

Hardware Implementation Considerations

Memory Safety

The FrameBuffer implementation provides both safe and unsafe construction methods to accommodate different hardware access patterns:

  • from_slice() for pre-validated memory regions
  • from_raw_parts_mut() for direct hardware memory mapping with explicit safety requirements

Flush Requirements

The need_flush() method accommodates different hardware architectures:

  • Immediate update hardware: Returns false, display updates automatically
  • Buffered hardware: Returns true, requires explicit flush() calls
  • Memory-mapped displays: May return false for direct framebuffer access

Sources: axdriver_display/src/lib.rs(L54 - L58) 

Dependencies and Build Configuration

The display driver crate has minimal dependencies, relying only on axdriver_base for core driver infrastructure:

[dependencies]
axdriver_base = { workspace = true }

This lightweight dependency structure ensures that display drivers can be compiled efficiently and integrated into embedded systems with minimal overhead.

Sources: axdriver_display/Cargo.toml(L14 - L15) 

VirtIO Integration

Relevant source files

The VirtIO integration layer provides a bridge between the external virtio-drivers crate and the axdriver framework, enabling virtualized hardware devices to be accessed through the same standardized interfaces as physical hardware. This integration allows ArceOS to run efficiently in virtualized environments while maintaining consistent driver APIs.

For information about the underlying driver traits that VirtIO devices implement, see Foundation Layer (axdriver_base). For details on specific device type implementations, see Network Drivers, Block Storage Drivers, and Display Drivers.

Architecture Overview

The VirtIO integration follows an adapter pattern that wraps virtio-drivers device implementations to conform to the axdriver trait system. This design allows the same application code to work with both physical and virtualized hardware.

flowchart TD
subgraph Target_Traits["axdriver Traits"]
    BASE_OPS["BaseDriverOps"]
    BLOCK_OPS["BlockDriverOps"]
    NET_OPS["NetDriverOps"]
    DISPLAY_OPS["DisplayDriverOps"]
end
subgraph Bridge_Layer["axdriver_virtio Bridge"]
    PROBE_MMIO["probe_mmio_device()"]
    PROBE_PCI["probe_pci_device()"]
    TYPE_CONV["as_dev_type()"]
    ERR_CONV["as_dev_err()"]
    subgraph Device_Wrappers["Device Wrappers"]
        VIRTIO_BLK["VirtIoBlkDev"]
        VIRTIO_NET["VirtIoNetDev"]
        VIRTIO_GPU["VirtIoGpuDev"]
    end
end
subgraph External_VirtIO["External VirtIO Layer"]
    VD["virtio-drivers crate"]
    TRANSPORT["Transport Layer"]
    HAL["VirtIoHal trait"]
end

ERR_CONV --> BASE_OPS
PROBE_MMIO --> TYPE_CONV
PROBE_PCI --> TYPE_CONV
TRANSPORT --> PROBE_MMIO
TRANSPORT --> PROBE_PCI
TYPE_CONV --> VIRTIO_BLK
TYPE_CONV --> VIRTIO_GPU
TYPE_CONV --> VIRTIO_NET
VD --> PROBE_MMIO
VD --> PROBE_PCI
VIRTIO_BLK --> BASE_OPS
VIRTIO_BLK --> BLOCK_OPS
VIRTIO_GPU --> BASE_OPS
VIRTIO_GPU --> DISPLAY_OPS
VIRTIO_NET --> BASE_OPS
VIRTIO_NET --> NET_OPS

Sources: axdriver_virtio/src/lib.rs(L1 - L98) 

Transport Layer Support

The VirtIO integration supports both MMIO and PCI transport mechanisms, providing flexibility for different virtualization platforms and hardware configurations.

MMIO Transport

The probe_mmio_device function discovers VirtIO devices mapped into memory regions, commonly used in embedded virtualization scenarios.

flowchart TD
subgraph Detection_Process["Detection Process"]
    PROBE_FUNC["probe_mmio_device()"]
    DEVICE_TYPE["DeviceType"]
    TRANSPORT_OBJ["Transport Object"]
end
subgraph MMIO_Discovery["MMIO Device Discovery"]
    MEM_REGION["Memory Regionreg_base: *mut u8"]
    VIRTIO_HEADER["VirtIOHeader"]
    MMIO_TRANSPORT["MmioTransport"]
end

MEM_REGION --> VIRTIO_HEADER
MMIO_TRANSPORT --> PROBE_FUNC
PROBE_FUNC --> DEVICE_TYPE
PROBE_FUNC --> TRANSPORT_OBJ
VIRTIO_HEADER --> MMIO_TRANSPORT

The function performs device validation and type identification:

StepOperationResult
1CreateNonNullfrom memory baseHeader pointer validation
2InitializeMmioTransportTransport layer setup
3Callas_dev_type()on device typeConvert toDeviceType
4Return tuple(DeviceType, MmioTransport)

Sources: axdriver_virtio/src/lib.rs(L38 - L53) 

PCI Transport

The probe_pci_device function discovers VirtIO devices on the PCI bus, typical for desktop and server virtualization environments.

flowchart TD
subgraph Probing_Logic["Probing Logic"]
    VIRTIO_TYPE["virtio_device_type()"]
    TYPE_CONV["as_dev_type()"]
    PCI_TRANSPORT["PciTransport::new()"]
end
subgraph PCI_Discovery["PCI Device Discovery"]
    PCI_ROOT["PciRoot"]
    BDF["DeviceFunction(Bus/Device/Function)"]
    DEV_INFO["DeviceFunctionInfo"]
end

BDF --> PCI_TRANSPORT
DEV_INFO --> VIRTIO_TYPE
PCI_ROOT --> PCI_TRANSPORT
TYPE_CONV --> PCI_TRANSPORT
VIRTIO_TYPE --> TYPE_CONV

Sources: axdriver_virtio/src/lib.rs(L55 - L69) 

Device Type Mapping

The integration includes a comprehensive type conversion system that maps VirtIO device types to axdriver device categories.

Type Conversion Function

The as_dev_type function provides the core mapping between VirtIO and axdriver type systems:

flowchart TD
subgraph Axdriver_Types["axdriver Device Types"]
    DEV_BLOCK["DeviceType::Block"]
    DEV_NET["DeviceType::Net"]
    DEV_DISPLAY["DeviceType::Display"]
    DEV_NONE["None"]
end
subgraph Mapping_Function["as_dev_type()"]
    MATCH_EXPR["match expression"]
end
subgraph VirtIO_Types["VirtIO Device Types"]
    VIRTIO_BLOCK["VirtIoDevType::Block"]
    VIRTIO_NET["VirtIoDevType::Network"]
    VIRTIO_GPU["VirtIoDevType::GPU"]
    VIRTIO_OTHER["Other VirtIO Types"]
end

MATCH_EXPR --> DEV_BLOCK
MATCH_EXPR --> DEV_DISPLAY
MATCH_EXPR --> DEV_NET
MATCH_EXPR --> DEV_NONE
VIRTIO_BLOCK --> MATCH_EXPR
VIRTIO_GPU --> MATCH_EXPR
VIRTIO_NET --> MATCH_EXPR
VIRTIO_OTHER --> MATCH_EXPR
VirtIO Typeaxdriver TypePurpose
BlockDeviceType::BlockStorage devices
NetworkDeviceType::NetNetwork interfaces
GPUDeviceType::DisplayGraphics and display
OtherNoneUnsupported device types

Sources: axdriver_virtio/src/lib.rs(L71 - L79) 

Error Handling Integration

The VirtIO integration includes comprehensive error mapping to ensure consistent error handling across the driver framework.

Error Conversion System

The as_dev_err function maps virtio_drivers::Error variants to axdriver_base::DevError types:

flowchart TD
subgraph Axdriver_Errors["axdriver_base::DevError"]
    BAD_STATE["BadState"]
    AGAIN["Again"]
    ALREADY_EXISTS["AlreadyExists"]
    INVALID_PARAM_OUT["InvalidParam"]
    NO_MEMORY["NoMemory"]
    IO_OUT["Io"]
    UNSUPPORTED_OUT["Unsupported"]
end
subgraph Conversion["as_dev_err()"]
    ERROR_MATCH["match expression"]
end
subgraph VirtIO_Errors["virtio_drivers::Error"]
    QUEUE_FULL["QueueFull"]
    NOT_READY["NotReady"]
    WRONG_TOKEN["WrongToken"]
    ALREADY_USED["AlreadyUsed"]
    INVALID_PARAM["InvalidParam"]
    DMA_ERROR["DmaError"]
    IO_ERROR["IoError"]
    UNSUPPORTED["Unsupported"]
    CONFIG_ERRORS["Config Errors"]
    OTHER_ERRORS["Other Errors"]
end

ALREADY_USED --> ERROR_MATCH
CONFIG_ERRORS --> ERROR_MATCH
DMA_ERROR --> ERROR_MATCH
ERROR_MATCH --> AGAIN
ERROR_MATCH --> ALREADY_EXISTS
ERROR_MATCH --> BAD_STATE
ERROR_MATCH --> INVALID_PARAM_OUT
ERROR_MATCH --> IO_OUT
ERROR_MATCH --> NO_MEMORY
ERROR_MATCH --> UNSUPPORTED_OUT
INVALID_PARAM --> ERROR_MATCH
IO_ERROR --> ERROR_MATCH
NOT_READY --> ERROR_MATCH
OTHER_ERRORS --> ERROR_MATCH
QUEUE_FULL --> ERROR_MATCH
UNSUPPORTED --> ERROR_MATCH
WRONG_TOKEN --> ERROR_MATCH

Sources: axdriver_virtio/src/lib.rs(L81 - L97) 

Feature-Based Compilation

The VirtIO integration uses feature flags to enable selective compilation of device types, allowing minimal builds for specific deployment scenarios.

Feature Configuration

FeatureDependenciesEnabled Components
blockaxdriver_blockVirtIoBlkDevwrapper
netaxdriver_netVirtIoNetDevwrapper
gpuaxdriver_displayVirtIoGpuDevwrapper
flowchart TD
subgraph Public_Exports["Public API"]
    PUB_BLK["pub use VirtIoBlkDev"]
    PUB_NET["pub use VirtIoNetDev"]
    PUB_GPU["pub use VirtIoGpuDev"]
end
subgraph Conditional_Modules["Conditional Compilation"]
    MOD_BLK["mod blk"]
    MOD_NET["mod net"]
    MOD_GPU["mod gpu"]
end
subgraph Feature_Flags["Cargo Features"]
    FEAT_BLOCK["block"]
    FEAT_NET["net"]
    FEAT_GPU["gpu"]
end

FEAT_BLOCK --> MOD_BLK
FEAT_GPU --> MOD_GPU
FEAT_NET --> MOD_NET
MOD_BLK --> PUB_BLK
MOD_GPU --> PUB_GPU
MOD_NET --> PUB_NET

Sources: axdriver_virtio/Cargo.toml(L14 - L24)  axdriver_virtio/src/lib.rs(L16 - L28) 

Public API Surface

The crate re-exports essential VirtIO components to provide a complete integration interface:

Core Re-exports

ExportSourcePurpose
pcivirtio_drivers::transport::pci::busPCI bus operations
MmioTransportvirtio_drivers::transport::mmioMMIO transport
PciTransportvirtio_drivers::transport::pciPCI transport
VirtIoHalvirtio_drivers::HalHardware abstraction trait
PhysAddrvirtio_drivers::PhysAddrPhysical address type
BufferDirectionvirtio_drivers::BufferDirectionDMA buffer direction

Sources: axdriver_virtio/src/lib.rs(L30 - L36) 

VirtIO Core Abstraction

Relevant source files

This document covers the core abstraction layer that bridges the external virtio-drivers crate with the axdriver ecosystem. It focuses on device probing mechanisms, type conversion, error handling, and the fundamental infrastructure that enables VirtIO device integration. For information about specific VirtIO device implementations (block, network, GPU), see VirtIO Device Implementations.

Overview

The VirtIO core abstraction in axdriver_virtio serves as an adapter layer that translates between the virtio-drivers crate's interfaces and the standardized axdriver trait system. This layer handles device discovery, type mapping, and error conversion while maintaining compatibility with both MMIO and PCI transport mechanisms.

Sources: axdriver_virtio/src/lib.rs(L1 - L98) 

Device Probing Architecture

The VirtIO subsystem provides two primary device probing mechanisms that support different hardware configurations and virtualization environments.

MMIO Device Probing

flowchart TD
A["probe_mmio_device()"]
B["reg_base: *mut u8"]
C["VirtIOHeader"]
D["MmioTransport::new()"]
E["Transport Valid?"]
F["transport.device_type()"]
G["Return None"]
H["as_dev_type()"]
I["Type Supported?"]
J["Return (DeviceType, MmioTransport)"]

A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
F --> H
H --> I
I --> G
I --> J

The probe_mmio_device function creates a VirtIO MMIO transport from a memory-mapped region and identifies the device type. It validates the VirtIO header structure and extracts device type information for compatibility checking.

Sources: axdriver_virtio/src/lib.rs(L38 - L53) 

PCI Device Probing

flowchart TD
A["probe_pci_device()"]
B["PciRoot, DeviceFunction, DeviceFunctionInfo"]
C["virtio_device_type()"]
D["as_dev_type()"]
E["Type Supported?"]
F["PciTransport::new()"]
G["Return None"]
H["Transport Created?"]
I["Return (DeviceType, PciTransport)"]

A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
F --> H
H --> G
H --> I

The probe_pci_device function handles PCI-based VirtIO devices using the PCI bus infrastructure. It requires a HAL implementation for DMA operations and device access.

Sources: axdriver_virtio/src/lib.rs(L55 - L69) 

Type Conversion System

The core abstraction implements bidirectional type mapping between the virtio-drivers crate and the axdriver ecosystem.

Device Type Mapping

VirtIO Device Typeaxdriver Device TypeSupport Status
VirtIoDevType::BlockDeviceType::Block✓ Supported
VirtIoDevType::NetworkDeviceType::Net✓ Supported
VirtIoDevType::GPUDeviceType::Display✓ Supported
Other typesNoneNot supported
flowchart TD
subgraph axdriver_base["axdriver_base"]
    AB["DeviceType::Block"]
    AN["DeviceType::Net"]
    AD["DeviceType::Display"]
    NONE["None"]
end
subgraph as_dev_type()["as_dev_type()"]
    CONV["Type Conversion"]
end
subgraph virtio_drivers["virtio_drivers"]
    VB["VirtIoDevType::Block"]
    VN["VirtIoDevType::Network"]
    VG["VirtIoDevType::GPU"]
    VO["Other Types"]
end

CONV --> AB
CONV --> AD
CONV --> AN
CONV --> NONE
VB --> CONV
VG --> CONV
VN --> CONV
VO --> CONV

The as_dev_type function provides compile-time type conversion that filters supported VirtIO device types and maps them to corresponding axdriver types.

Sources: axdriver_virtio/src/lib.rs(L71 - L79) 

Error Handling Bridge

The abstraction layer provides comprehensive error conversion between VirtIO-specific errors and the standardized axdriver error types.

Error Mapping Table

VirtIO Erroraxdriver DevErrorDescription
QueueFullBadStateDevice queue at capacity
NotReadyAgainDevice not ready, retry later
WrongTokenBadStateInvalid operation token
AlreadyUsedAlreadyExistsResource already in use
InvalidParamInvalidParamInvalid parameter provided
DmaErrorNoMemoryDMA allocation failure
IoErrorIoI/O operation failure
UnsupportedUnsupportedUnsupported operation
ConfigSpaceTooSmallBadStateInsufficient config space
ConfigSpaceMissingBadStateMissing config space
DefaultBadStateGeneric error state
flowchart TD
subgraph axdriver_base::DevError["axdriver_base::DevError"]
    DE1["BadState"]
    DE2["Again"]
    DE3["NoMemory"]
    DE4["Io"]
    DE5["Unsupported"]
end
subgraph as_dev_err()["as_dev_err()"]
    ECONVERT["Error Conversion"]
end
subgraph virtio_drivers::Error["virtio_drivers::Error"]
    VE1["QueueFull"]
    VE2["NotReady"]
    VE3["DmaError"]
    VE4["IoError"]
    VE5["Unsupported"]
    VEX["Others..."]
end

ECONVERT --> DE1
ECONVERT --> DE2
ECONVERT --> DE3
ECONVERT --> DE4
ECONVERT --> DE5
VE1 --> ECONVERT
VE2 --> ECONVERT
VE3 --> ECONVERT
VE4 --> ECONVERT
VE5 --> ECONVERT
VEX --> ECONVERT

The as_dev_err function provides semantic mapping of VirtIO errors to axdriver error categories, enabling consistent error handling across the driver ecosystem.

Sources: axdriver_virtio/src/lib.rs(L81 - L97) 

Transport Abstraction Layer

The VirtIO core abstraction re-exports key transport components from the virtio-drivers crate, providing a unified interface for different VirtIO transport mechanisms.

Core Transport Types

flowchart TD
subgraph subGraph2["PCI Infrastructure"]
    PCIBUS["pci::bus module"]
    DEVFUNC["DeviceFunction"]
    DEVINFO["DeviceFunctionInfo"]
    PCIROOT["PciRoot"]
end
subgraph subGraph1["Supporting Types"]
    HAL["VirtIoHal Trait"]
    PADDR["PhysAddr"]
    BDIR["BufferDirection"]
end
subgraph subGraph0["Transport Layer"]
    TRANS["Transport Trait"]
    MMIO["MmioTransport"]
    PCI["PciTransport"]
end

HAL --> BDIR
HAL --> PADDR
MMIO --> HAL
PCI --> HAL
PCI --> PCIBUS
PCIBUS --> DEVFUNC
PCIBUS --> DEVINFO
PCIBUS --> PCIROOT
TRANS --> MMIO
TRANS --> PCI

The transport abstraction provides:

  • Transport Trait: Common interface for device communication
  • MmioTransport: Memory-mapped I/O transport for embedded systems
  • PciTransport: PCI bus transport for conventional systems
  • VirtIoHal: Hardware abstraction layer for memory management
  • PCI Infrastructure: Bus enumeration and device management

Sources: axdriver_virtio/src/lib.rs(L30 - L36) 

HAL Requirements

The VirtIO integration requires implementation of the VirtIoHal trait for hardware-specific operations:

flowchart TD
subgraph subGraph1["axdriver Integration"]
    PROBE["Device Probing"]
    TRANSPORT["Transport Creation"]
    DEVOPS["Device Operations"]
end
subgraph subGraph0["VirtIoHal Requirements"]
    DMA["DMA Memory Allocation"]
    ADDR["Physical Address Translation"]
    CACHE["Cache Coherency"]
    SYNC["Memory Synchronization"]
end

ADDR --> TRANSPORT
CACHE --> DEVOPS
DMA --> PROBE
SYNC --> DEVOPS

The HAL implementation must provide memory management capabilities that bridge between the host system's memory management and VirtIO device requirements.

Sources: axdriver_virtio/src/lib.rs(L4 - L7) 

Conditional Compilation Support

The VirtIO core abstraction supports selective compilation through feature flags:

FeaturePurpose
blockEnable VirtIO block device support
netEnable VirtIO network device support
gpuEnable VirtIO GPU device support

This design allows minimal binary size by including only required device types while maintaining the same core probing and abstraction infrastructure.

Sources: axdriver_virtio/src/lib.rs(L16 - L28) 

VirtIO Device Implementations

Relevant source files

This page documents the specific VirtIO device wrapper implementations that provide ArceOS-compatible drivers for virtualized hardware. These implementations bridge the external virtio_drivers crate with the axdriver trait system, enabling VirtIO devices to be used seamlessly alongside native hardware drivers.

For information about the core VirtIO abstraction layer and device probing mechanisms, see VirtIO Core Abstraction. For details about the underlying device traits these implementations fulfill, see Foundation Layer, Network Driver Interface, Block Driver Interface, and Display Drivers.

VirtIO Device Architecture Overview

The axdriver_virtio crate provides three main device wrapper implementations that adapt VirtIO devices to the axdriver trait system:

flowchart TD
subgraph subGraph2["axdriver Traits"]
    BaseOps["BaseDriverOps"]
    BlockOps["BlockDriverOps"]
    NetOps["NetDriverOps"]
    DisplayOps["DisplayDriverOps"]
end
subgraph subGraph1["axdriver_virtio Wrappers"]
    BlkWrapper["VirtIoBlkDev"]
    NetWrapper["VirtIoNetDev"]
    GpuWrapper["VirtIoGpuDev"]
end
subgraph subGraph0["External VirtIO Layer"]
    VirtIOBlk["virtio_drivers::device::blk::VirtIOBlk"]
    VirtIONet["virtio_drivers::device::net::VirtIONetRaw"]
    VirtIOGpu["virtio_drivers::device::gpu::VirtIOGpu"]
end

BlkWrapper --> BaseOps
BlkWrapper --> BlockOps
GpuWrapper --> BaseOps
GpuWrapper --> DisplayOps
NetWrapper --> BaseOps
NetWrapper --> NetOps
VirtIOBlk --> BlkWrapper
VirtIOGpu --> GpuWrapper
VirtIONet --> NetWrapper

Sources: axdriver_virtio/src/blk.rs(L1 - L61)  axdriver_virtio/src/net.rs(L1 - L194)  axdriver_virtio/src/gpu.rs(L1 - L71) 

VirtIO Block Device Implementation

The VirtIoBlkDev provides block storage functionality through VirtIO's block device interface.

Structure and Initialization

flowchart TD
subgraph subGraph1["Trait Implementation"]
    BaseImpl["BaseDriverOps:device_name() -> 'virtio-blk'device_type() -> Block"]
    BlockImpl["BlockDriverOps:num_blocks()block_size()read_block()write_block()flush()"]
end
subgraph subGraph0["VirtIoBlkDev Components"]
    Inner["inner: VirtIOBlk"]
    Params["Generic Parameters:H: HalT: Transport"]
end

Inner --> BaseImpl
Inner --> BlockImpl
Params --> Inner

The block device implementation is straightforward, directly delegating most operations to the underlying VirtIO block device:

MethodImplementationPurpose
try_new()CreatesVirtIOBlk::new(transport)Device initialization
num_blocks()Returnsinner.capacity()Total device capacity
block_size()ReturnsSECTOR_SIZEconstantFixed 512-byte sectors
read_block()Callsinner.read_blocks()Synchronous block reads
write_block()Callsinner.write_blocks()Synchronous block writes
flush()No-op returningOk(())VirtIO handles consistency

Sources: axdriver_virtio/src/blk.rs(L7 - L22)  axdriver_virtio/src/blk.rs(L34 - L60) 

VirtIO GPU Device Implementation

The VirtIoGpuDev provides display and graphics functionality with framebuffer management.

Structure and Framebuffer Setup

flowchart TD
subgraph subGraph1["Initialization Process"]
    Create["VirtIOGpu::new(transport)"]
    Setup["setup_framebuffer()"]
    GetRes["resolution()"]
    BuildInfo["Build DisplayInfo"]
end
subgraph subGraph0["VirtIoGpuDev Structure"]
    InnerGpu["inner: VirtIOGpu"]
    DisplayInfo["info: DisplayInfowidth, heightfb_base_vaddrfb_size"]
end

BuildInfo --> DisplayInfo
Create --> Setup
GetRes --> BuildInfo
InnerGpu --> DisplayInfo
Setup --> GetRes

The GPU device performs complex initialization to establish the framebuffer:

  1. Device Creation: Initialize the underlying VirtIO GPU device
  2. Framebuffer Setup: Call setup_framebuffer() to allocate GPU memory
  3. Resolution Query: Get device capabilities for width and height
  4. Info Structure: Build DisplayInfo with framebuffer details

The DisplayInfo structure contains:

  • width, height: Display resolution
  • fb_base_vaddr: Virtual address of framebuffer memory
  • fb_size: Total framebuffer size in bytes

Sources: axdriver_virtio/src/gpu.rs(L17 - L40)  axdriver_virtio/src/gpu.rs(L52 - L70) 

VirtIO Network Device Implementation

The VirtIoNetDev is the most complex VirtIO implementation, featuring sophisticated buffer management for high-performance networking.

Buffer Management Architecture

flowchart TD
subgraph subGraph1["VirtIO Queue Operations"]
    ReceiveBegin["receive_begin()"]
    TransmitBegin["transmit_begin()"]
    PollReceive["poll_receive()"]
    PollTransmit["poll_transmit()"]
end
subgraph subGraph0["VirtIoNetDev Buffer System"]
    RxBuffers["rx_buffers: [Option; QS]"]
    TxBuffers["tx_buffers: [Option; QS]"]
    FreeTxBufs["free_tx_bufs: Vec"]
    BufPool["buf_pool: Arc"]
end

BufPool --> FreeTxBufs
BufPool --> RxBuffers
BufPool --> TxBuffers
FreeTxBufs --> TransmitBegin
ReceiveBegin --> PollReceive
RxBuffers --> ReceiveBegin
TransmitBegin --> PollTransmit
TxBuffers --> TransmitBegin

Network Device Initialization Process

The network device initialization involves three main phases:

  1. RX Buffer Pre-allocation: Fill all QS receive buffer slots
- Allocate NetBufBox from pool
- Call receive_begin() with buffer
- Store buffer in rx_buffers[token]
  1. TX Buffer Preparation: Pre-allocate transmit buffers with headers
- Allocate NetBufBox from pool  
- Fill VirtIO header using fill_buffer_header()
- Store in free_tx_bufs for later use
  1. Pool Management: Initialize NetBufPool with 2 * QS total buffers

Network Operations Flow

OperationBuffer FlowQueue Interaction
Transmitfree_tx_bufs.pop()→tx_buffers[token]transmit_begin()returns token
Receiverx_buffers[token].take()→ return to callerpoll_receive()returns token
TX Recycletx_buffers[token].take()→free_tx_bufs.push()poll_transmit()+transmit_complete()
RX RecycleCaller returns →rx_buffers[token]receive_begin()with recycled buffer

Sources: axdriver_virtio/src/net.rs(L14 - L73)  axdriver_virtio/src/net.rs(L85 - L193) 

Common Implementation Patterns

All VirtIO device implementations follow consistent architectural patterns:

Generic Type Parameters

flowchart TD
subgraph subGraph1["Usage in Devices"]
    BlkDev["VirtIoBlkDev"]
    GpuDev["VirtIoGpuDev"]
    NetDev["VirtIoNetDev"]
end
subgraph subGraph0["Shared Generic Structure"]
    H["H: Hal(Hardware Abstraction)"]
    T["T: Transport(Communication Layer)"]
    QS["QS: usize(Queue Size - Net only)"]
end

H --> BlkDev
H --> GpuDev
H --> NetDev
QS --> NetDev
T --> BlkDev
T --> GpuDev
T --> NetDev

Trait Implementation Pattern

Each device implements the same trait hierarchy:

TraitBlock DeviceGPU DeviceNetwork Device
BaseDriverOps✓ "virtio-blk"✓ "virtio-gpu"✓ "virtio-net"
Device-specificBlockDriverOpsDisplayDriverOpsNetDriverOps
Thread SafetySend + SyncSend + SyncSend + Sync

Error Handling Convention

All implementations use the as_dev_err function to convert VirtIO-specific errors to DevError types, providing consistent error handling across the driver framework.

Sources: axdriver_virtio/src/blk.rs(L24 - L32)  axdriver_virtio/src/gpu.rs(L42 - L50)  axdriver_virtio/src/net.rs(L75 - L83) 

PCI Bus Operations

Relevant source files

This document covers the PCI (Peripheral Component Interconnect) bus operations provided by the axdriver_pci crate. This crate serves as a bridge between the ArceOS driver framework and PCI device discovery and management functionality. It provides the foundational infrastructure for PCI device enumeration, configuration space access, and memory-mapped I/O (MMIO) region allocation needed by hardware device drivers.

For information about specific device driver implementations that use PCI operations, see Network Drivers and Block Storage Drivers. For VirtIO device integration over PCI transport, see VirtIO Integration.

Architecture Overview

The PCI bus operations layer sits between the hardware abstraction and device-specific drivers, providing standardized access to PCI configuration and memory management.

PCI Integration Architecture

flowchart TD
subgraph subGraph3["Device Drivers"]
    NETDRIVERS["Network Drivers(ixgbe, VirtIO-Net)"]
    BLOCKDRIVERS["Block Drivers(VirtIO-Block)"]
    DISPLAYDRIVERS["Display Drivers(VirtIO-GPU)"]
end
subgraph subGraph2["virtio-drivers Foundation"]
    VIRTIOPCI["virtio_drivers::transport::pci::bus"]
    PCITYPES["PCI Types & Operations"]
end
subgraph subGraph1["axdriver_pci Layer"]
    PCIROOT["PciRootBus enumeration"]
    DEVICEFUNC["DeviceFunctionDevice access"]
    ALLOCATOR["PciRangeAllocatorMMIO allocation"]
    BARINFO["BarInfoMemory region info"]
end
subgraph subGraph0["Hardware Layer"]
    PCIHW["PCI Hardware Bus"]
    DEVICES["PCI Devices(Network, Storage, Display)"]
end

ALLOCATOR --> BLOCKDRIVERS
ALLOCATOR --> DISPLAYDRIVERS
ALLOCATOR --> NETDRIVERS
DEVICEFUNC --> BLOCKDRIVERS
DEVICEFUNC --> DISPLAYDRIVERS
DEVICEFUNC --> NETDRIVERS
DEVICES --> VIRTIOPCI
PCIHW --> VIRTIOPCI
PCIROOT --> BLOCKDRIVERS
PCIROOT --> DISPLAYDRIVERS
PCIROOT --> NETDRIVERS
PCITYPES --> ALLOCATOR
VIRTIOPCI --> BARINFO
VIRTIOPCI --> DEVICEFUNC
VIRTIOPCI --> PCIROOT

Sources: axdriver_pci/src/lib.rs(L1 - L14)  axdriver_pci/Cargo.toml(L14 - L15) 

Core PCI Types and Operations

The axdriver_pci crate primarily re-exports essential PCI types from the virtio-drivers crate, providing a consistent interface for PCI device management across the ArceOS ecosystem.

Device Discovery and Access

TypePurposeKey Operations
PciRootRoot PCI bus controllerDevice enumeration and scanning
DeviceFunctionIndividual PCI device/functionConfiguration space access
DeviceFunctionInfoDevice metadataVendor ID, device ID, class codes
CapabilityInfoPCI capabilitiesExtended feature discovery

Memory and I/O Management

TypePurposeUsage
BarInfoBase Address Register infoMemory region mapping
MemoryBarTypeMemory BAR classification32-bit vs 64-bit addressing
CommandPCI command registerBus mastering, memory enable
StatusPCI status registerCapability support, error status

PCI Type Relationships

flowchart TD
subgraph Configuration["Configuration"]
    COMMAND["Command"]
    STATUS["Status"]
    CAPINFO["CapabilityInfo"]
end
subgraph subGraph1["Memory Management"]
    BARINFO["BarInfo"]
    MEMBARTYPE["MemoryBarType"]
    ALLOCATOR["PciRangeAllocator"]
end
subgraph subGraph0["Device Discovery"]
    PCIROOT["PciRoot"]
    DEVICEFUNC["DeviceFunction"]
    DEVINFO["DeviceFunctionInfo"]
end

BARINFO --> ALLOCATOR
BARINFO --> MEMBARTYPE
DEVICEFUNC --> BARINFO
DEVICEFUNC --> CAPINFO
DEVICEFUNC --> COMMAND
DEVICEFUNC --> DEVINFO
DEVICEFUNC --> STATUS
PCIROOT --> DEVICEFUNC

Sources: axdriver_pci/src/lib.rs(L11 - L14) 

MMIO Range Allocation

The PciRangeAllocator provides a custom memory allocation system specifically designed for PCI Base Address Register (BAR) allocation. This allocator ensures proper alignment and prevents memory region conflicts.

Allocator Structure

The PciRangeAllocator maintains a simple linear allocation strategy:

pub struct PciRangeAllocator {
    _start: u64,    // Base address of allocatable region
    end: u64,       // End boundary of allocatable region  
    current: u64,   // Current allocation pointer
}

Allocation Algorithm

The allocator implements power-of-2 aligned allocation with the following constraints:

PropertyRequirementRationale
Size alignmentMust be power of 2PCI BAR size requirements
Address alignmentMultiple of sizeHardware addressing constraints
Boundary checkingWithin allocated rangeMemory safety

MMIO Allocation Flow

flowchart TD
START["alloc(size: u64)"]
CHECK_POW2["size.is_power_of_two()"]
ALIGN["ret = align_up(current, size)"]
CHECK_BOUNDS["ret + size > end?"]
UPDATE["current = ret + size"]
RETURN_ADDR["return Some(ret)"]
RETURN_NONE["return None"]

ALIGN --> CHECK_BOUNDS
CHECK_BOUNDS --> RETURN_NONE
CHECK_BOUNDS --> UPDATE
CHECK_POW2 --> ALIGN
CHECK_POW2 --> RETURN_NONE
START --> CHECK_POW2
UPDATE --> RETURN_ADDR

Usage Example

The typical allocation pattern for PCI BARs:

  1. Create allocator with memory range: PciRangeAllocator::new(base, size)
  2. Request aligned memory: allocator.alloc(bar_size)
  3. Map returned address to device BAR

Sources: axdriver_pci/src/lib.rs(L17 - L53) 

Integration with Device Drivers

The PCI operations integrate with device drivers through standardized patterns for device discovery, configuration, and memory mapping.

Typical Driver Integration Pattern

sequenceDiagram
    participant DeviceDriver as "Device Driver"
    participant PciRoot as "PciRoot"
    participant DeviceFunction as "DeviceFunction"
    participant PciRangeAllocator as "PciRangeAllocator"

    DeviceDriver ->> PciRoot: Enumerate devices
    PciRoot ->> DeviceDriver: Found matching device
    DeviceDriver ->> DeviceFunction: Read configuration
    DeviceFunction ->> DeviceDriver: Device info & BARs
    DeviceDriver ->> PciRangeAllocator: Request MMIO region
    PciRangeAllocator ->> DeviceDriver: Allocated address
    DeviceDriver ->> DeviceFunction: Configure BAR mapping
    DeviceFunction ->> DeviceDriver: Device ready

Error Handling

PCI operations use the PciError type for consistent error reporting across device initialization and configuration operations.

Sources: axdriver_pci/src/lib.rs(L11) 

Dependencies and External Integration

The axdriver_pci crate builds upon the virtio-drivers crate version 0.7.4, specifically leveraging its PCI transport layer. This dependency provides:

  • Low-level PCI configuration space access
  • Standard PCI type definitions
  • Cross-platform PCI hardware abstraction

The integration allows ArceOS drivers to benefit from the mature PCI implementation in virtio-drivers while maintaining the consistent ArceOS driver interface patterns.

Sources: axdriver_pci/Cargo.toml(L15)  axdriver_pci/src/lib.rs(L3 - L7) 

Development and Build Configuration

Relevant source files

This document explains the build system, continuous integration pipeline, and development workflow for the axdriver_crates repository. It covers the automated testing and validation processes, multi-target compilation support, and documentation generation that ensure code quality and maintain the driver framework across different hardware platforms.

For information about the overall architecture and design patterns, see Architecture and Design. For details about specific driver implementations and their usage, see the respective subsystem documentation (Network Drivers, Block Storage Drivers, etc.).

Build System Overview

The axdriver_crates workspace uses Cargo's workspace functionality to manage multiple related crates with unified build configuration. The build system supports compilation for embedded targets without standard library support, requiring careful dependency management and feature gating.

Workspace Structure

flowchart TD
subgraph subGraph2["Build Steps"]
    FORMAT_CHECK["cargo fmt --checkCode Formatting"]
    CLIPPY["cargo clippyLinting"]
    BUILD["cargo buildCompilation"]
    TEST["cargo testUnit Testing"]
end
subgraph subGraph1["Build Targets"]
    X86_64_LINUX["x86_64-unknown-linux-gnuStandard Linux"]
    X86_64_NONE["x86_64-unknown-noneBare Metal x86"]
    RISCV64["riscv64gc-unknown-none-elfRISC-V Embedded"]
    AARCH64["aarch64-unknown-none-softfloatARM64 Embedded"]
end
subgraph subGraph0["Workspace Root"]
    CARGO_TOML["Cargo.tomlWorkspace Configuration"]
    CI_YML[".github/workflows/ci.ymlBuild Pipeline"]
    GITIGNORE[".gitignoreBuild Artifacts"]
end

BUILD --> AARCH64
BUILD --> RISCV64
BUILD --> X86_64_LINUX
BUILD --> X86_64_NONE
CARGO_TOML --> BUILD
CI_YML --> BUILD
CI_YML --> CLIPPY
CI_YML --> FORMAT_CHECK
CI_YML --> TEST

The build system validates code across multiple architectures to ensure compatibility with various embedded and desktop platforms that ArceOS supports.

Sources: .github/workflows/ci.yml(L1 - L58)  .gitignore(L1 - L5) 

Continuous Integration Pipeline

The CI pipeline implements a comprehensive validation strategy using GitHub Actions, ensuring code quality and cross-platform compatibility for all commits and pull requests.

CI Job Matrix Configuration

flowchart TD
subgraph subGraph2["Validation Steps"]
    SETUP["Setup Toolchainrust-src, clippy, rustfmt"]
    FORMAT["Format Checkcargo fmt --all -- --check"]
    LINT["Lintingcargo clippy --all-features"]
    COMPILE["Buildcargo build --all-features"]
    UNIT_TEST["Unit Testscargo test (Linux only)"]
end
subgraph subGraph1["CI Matrix"]
    TOOLCHAIN["nightlyRust Toolchain"]
    TARGET_1["x86_64-unknown-linux-gnu"]
    TARGET_2["x86_64-unknown-none"]
    TARGET_3["riscv64gc-unknown-none-elf"]
    TARGET_4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["CI Trigger Events"]
    PUSH["git push"]
    PR["Pull Request"]
end

COMPILE --> UNIT_TEST
FORMAT --> LINT
LINT --> COMPILE
PR --> TOOLCHAIN
PUSH --> TOOLCHAIN
SETUP --> FORMAT
TOOLCHAIN --> TARGET_1
TOOLCHAIN --> TARGET_2
TOOLCHAIN --> TARGET_3
TOOLCHAIN --> TARGET_4

The pipeline uses the fail-fast: false strategy to ensure all target platforms are tested even if one fails, providing comprehensive feedback for cross-platform issues.

Sources: .github/workflows/ci.yml(L6 - L30) 

Documentation Pipeline

The documentation build process generates and deploys API documentation automatically for the main branch:

flowchart TD
subgraph subGraph2["Documentation Features"]
    INDEX_PAGE["--enable-index-page"]
    BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"]
    MISSING_DOCS["-D missing-docs"]
end
subgraph subGraph1["Documented Crates"]
    BASE["axdriver_base"]
    BLOCK["axdriver_block"]
    NET["axdriver_net"]
    DISPLAY["axdriver_display"]
    PCI["axdriver_pci"]
    VIRTIO["axdriver_virtio"]
end
subgraph subGraph0["Documentation Job"]
    DOC_TRIGGER["Main Branch Push"]
    DOC_BUILD["rustdoc Generation"]
    DOC_DEPLOY["GitHub Pages Deploy"]
end

DOC_BUILD --> BASE
DOC_BUILD --> BLOCK
DOC_BUILD --> BROKEN_LINKS
DOC_BUILD --> DISPLAY
DOC_BUILD --> DOC_DEPLOY
DOC_BUILD --> INDEX_PAGE
DOC_BUILD --> MISSING_DOCS
DOC_BUILD --> NET
DOC_BUILD --> PCI
DOC_BUILD --> VIRTIO
DOC_TRIGGER --> DOC_BUILD

The documentation pipeline enforces strict documentation requirements with -D missing-docs and validates all internal links to maintain high-quality API documentation.

Sources: .github/workflows/ci.yml(L32 - L58) 

Target Platform Support

The framework supports multiple target platforms with different characteristics and constraints:

Target PlatformPurposeStandard LibraryUse Case
x86_64-unknown-linux-gnuDevelopment/TestingFullUnit tests, development
x86_64-unknown-noneBare Metal x86No-stdPC-based embedded systems
riscv64gc-unknown-none-elfRISC-V EmbeddedNo-stdRISC-V microcontrollers
aarch64-unknown-none-softfloatARM64 EmbeddedNo-stdARM-based embedded systems

Compilation Configuration

The build system uses --all-features to ensure maximum compatibility testing across all optional features. Target-specific compilation is managed through:

  • Toolchain Requirements: Nightly Rust with rust-src component for no-std targets
  • Component Dependencies: clippy and rustfmt for code quality validation
  • Feature Testing: All features enabled during compilation to catch integration issues

Sources: .github/workflows/ci.yml(L11 - L12)  .github/workflows/ci.yml(L17 - L19)  .github/workflows/ci.yml(L25 - L27) 

Development Workflow

Code Quality Gates

The development workflow enforces strict quality standards through automated checks:

flowchart TD
subgraph subGraph2["Quality Requirements"]
    NO_FORMAT_ERRORS["No Format Violations"]
    NO_LINT_ERRORS["No Clippy Warnings(except new_without_default)"]
    ALL_TARGETS_BUILD["All Targets Compile"]
    TESTS_PASS["Unit Tests Pass"]
end
subgraph subGraph1["Automated Validation"]
    FORMAT_GATE["Format Checkcargo fmt --all -- --check"]
    CLIPPY_GATE["Lint Checkcargo clippy -A clippy::new_without_default"]
    BUILD_GATE["Cross-Platform Build4 target platforms"]
    TEST_GATE["Unit TestsLinux target only"]
end
subgraph subGraph0["Developer Workflow"]
    CODE_CHANGE["Code Changes"]
    LOCAL_TEST["Local Testing"]
    COMMIT["Git Commit"]
    PUSH["Git Push/PR"]
end

BUILD_GATE --> ALL_TARGETS_BUILD
BUILD_GATE --> TEST_GATE
CLIPPY_GATE --> BUILD_GATE
CLIPPY_GATE --> NO_LINT_ERRORS
CODE_CHANGE --> LOCAL_TEST
COMMIT --> PUSH
FORMAT_GATE --> CLIPPY_GATE
FORMAT_GATE --> NO_FORMAT_ERRORS
LOCAL_TEST --> COMMIT
PUSH --> FORMAT_GATE
TEST_GATE --> TESTS_PASS

Clippy Configuration

The CI pipeline uses a relaxed clippy configuration with -A clippy::new_without_default to accommodate embedded development patterns where implementing Default may not be appropriate for hardware drivers.

Sources: .github/workflows/ci.yml(L22 - L30) 

Build Artifacts and Caching

Ignored Files and Directories

The repository maintains a minimal .gitignore configuration focusing on essential build artifacts:

/target         # Cargo build output
/.vscode        # VSCode workspace files  
.DS_Store       # macOS system files
Cargo.lock      # Lock file (workspace)

The Cargo.lock exclusion is intentional for library crates, allowing consumers to use their preferred dependency versions while maintaining reproducible builds during development.

Sources: .gitignore(L1 - L5) 

Documentation Deployment

Documentation deployment uses a single-commit strategy to GitHub Pages, minimizing repository size while maintaining complete API documentation history through the gh-pages branch.

Sources: .github/workflows/ci.yml(L51 - L57) 

Feature Configuration Strategy

The build system uses --all-features compilation to ensure comprehensive testing of all optional functionality. This approach validates:

  • Feature Compatibility: All feature combinations compile successfully
  • Cross-Platform Features: Features work across all supported target platforms
  • Integration Testing: Optional components integrate properly with core functionality

This strategy supports the modular architecture where different embedded systems can selectively enable only required driver components while maintaining compatibility guarantees.

Sources: .github/workflows/ci.yml(L25)  .github/workflows/ci.yml(L27)  .github/workflows/ci.yml(L47 - L50) 

Overview

Relevant source files

Purpose and Scope

This document provides an overview of the axio crate, a library that implements std::io-like I/O traits for no_std environments. It covers the crate's architecture, core components, and design principles. For detailed information about specific I/O traits, see Core I/O Traits. For configuration options and feature gates, see Crate Configuration and Features. For concrete implementations, see Implementations.

What is axio?

The axio crate provides a collection of I/O traits and implementations designed specifically for no_std environments such as embedded systems, OS kernels, and resource-constrained applications. It serves as a drop-in replacement for Rust's std::io module when the standard library is not available.

The crate name reflects its purpose: "ax" (from ArceOS ecosystem) + "io" (input/output operations). It maintains API compatibility with std::io while being usable in environments without heap allocation or operating system support.

Key characteristics:

  • no_std compatible by default
  • Optional alloc feature for enhanced functionality
  • Minimal dependency footprint
  • API compatibility with std::io
  • Designed for embedded and kernel development

Sources: Cargo.toml(L1 - L20)  README.md(L1 - L10) 

System Architecture

The following diagram shows the high-level architecture of the axio crate and its relationship to external dependencies and target environments:

System Context and Dependencies

flowchart TD
subgraph targets["Target Environments"]
    embedded["Embedded Systems"]
    kernels["OS Kernels"]
    nostd_apps["no_std Applications"]
end
subgraph features["Feature Configuration"]
    default_feat["default = []"]
    alloc_feat["alloc feature"]
end
subgraph implementations["Trait Implementations"]
    impls_rs["src/impls.rs"]
    buffered_mod["src/buffered/"]
end
subgraph axio_core["axio Core Library"]
    lib_rs["src/lib.rs"]
    error_rs["src/error.rs"]
    prelude_rs["src/prelude.rs"]
end
subgraph external["External Dependencies"]
    axerrno["axerrno crate"]
    alloc_crate["alloc crate"]
end

alloc_crate --> lib_rs
alloc_feat --> alloc_crate
axerrno --> error_rs
default_feat --> lib_rs
error_rs --> lib_rs
lib_rs --> buffered_mod
lib_rs --> embedded
lib_rs --> impls_rs
lib_rs --> kernels
lib_rs --> nostd_apps
lib_rs --> prelude_rs

Sources: Cargo.toml(L14 - L20) 

Core Trait Ecosystem

The axio crate implements four fundamental I/O traits that mirror those found in std::io. This diagram maps the natural language concepts to specific code entities:

I/O Trait Hierarchy and Code Entities

flowchart TD
subgraph implementations["Concrete Implementations"]
    slice_impl["&[u8] impl Read"]
    BufReader["BufReader"]
end
subgraph supporting_types["Supporting Types"]
    SeekFrom["SeekFrom enum"]
    Result_type["Result"]
    Error_type["Error"]
    PollState["PollState"]
end
subgraph trait_methods["Key Trait Methods"]
    read_method["Read::read()"]
    read_exact["Read::read_exact()"]
    write_method["Write::write()"]
    write_all["Write::write_all()"]
    seek_method["Seek::seek()"]
    fill_buf["BufRead::fill_buf()"]
end
subgraph core_traits["Core I/O Traits"]
    Read["Read trait"]
    Write["Write trait"]
    Seek["Seek trait"]
    BufRead["BufRead trait"]
end

BufRead --> Read
BufRead --> Result_type
BufRead --> fill_buf
BufReader --> BufRead
BufReader --> Read
Read --> Result_type
Read --> read_exact
Read --> read_method
Result_type --> Error_type
Seek --> SeekFrom
Seek --> seek_method
Write --> Result_type
Write --> write_all
Write --> write_method
slice_impl --> Read

Sources: Cargo.toml(L6) 

Target Environments and Use Cases

The axio crate is specifically designed for environments where std::io is not available:

EnvironmentUse CaseKey Benefits
Embedded SystemsMicrocontroller I/O operationsMinimal memory footprint, no heap required
OS KernelsKernel-level I/O abstractionsNo standard library dependency
no_std ApplicationsResource-constrained applicationsPredictable memory usage
ArceOS EcosystemOperating system componentsSeamless integration with ArceOS

The crate maintains compatibility with std::io APIs, allowing code to be portable between std and no_std environments with minimal changes.

Sources: Cargo.toml(L11 - L12) 

Feature Configuration

The axio crate uses a feature-driven compilation model to provide different levels of functionality:

Default Configuration

  • Feature set: default = [] - no features enabled by default
  • Provides core I/O traits without heap allocation
  • Minimal dependency footprint with only axerrno

Enhanced Configuration

  • Feature set: alloc - enables dynamic memory operations
  • Adds methods that require heap allocation (e.g., read_to_string, read_to_end)
  • Maintains no_std compatibility while providing enhanced functionality

This design allows the crate to serve both minimal embedded environments and more capable systems that have dynamic memory allocation available.

Sources: Cargo.toml(L14 - L16) 

Dependencies and Integration

The axio crate maintains a lean dependency graph:

Required Dependencies:

  • axerrno = "0.1" - Provides error types and codes compatible with the ArceOS ecosystem

Optional Dependencies:

  • alloc crate - Enabled via the alloc feature for dynamic memory operations

The crate integrates with the broader ArceOS ecosystem through shared error handling conventions and API design patterns. Error handling is centralized through the axerrno crate, ensuring consistency across ArceOS components.

Sources: Cargo.toml(L18 - L19)  Cargo.toml(L8 - L9) 

For detailed information about specific aspects of the axio crate:

Core I/O Traits

Relevant source files

This page documents the four fundamental I/O traits that form the foundation of the axio library: Read, Write, Seek, and BufRead. These traits provide a std::io-compatible interface for performing I/O operations in no_std environments.

The traits are designed to mirror Rust's standard library I/O traits while supporting resource-constrained environments through optional allocation-dependent features. For information about concrete implementations of these traits, see Implementations. For details about the crate's feature configuration and dependency management, see Crate Configuration and Features.

Trait Hierarchy and Relationships

The core I/O traits in axio form a well-structured hierarchy where BufRead extends Read, while Write and Seek operate independently. The following diagram shows the relationships between traits and their key methods:

flowchart TD
Read["Read"]
ReadMethods["read()read_exact()read_to_end()read_to_string()"]
Write["Write"]
WriteMethods["write()flush()write_all()write_fmt()"]
Seek["Seek"]
SeekMethods["seek()rewind()stream_position()"]
BufRead["BufRead"]
BufReadMethods["fill_buf()consume()has_data_left()read_until()read_line()"]
ResultType["Result"]
SeekFrom["SeekFromStart(u64)End(i64)Current(i64)"]
AllocFeature["alloc feature"]
ReadToEnd["read_to_end()read_to_string()read_until()read_line()"]

AllocFeature --> ReadToEnd
BufRead --> BufReadMethods
BufRead --> Read
BufReadMethods --> ResultType
Read --> ReadMethods
ReadMethods --> ResultType
Seek --> SeekMethods
SeekMethods --> ResultType
SeekMethods --> SeekFrom
Write --> WriteMethods
WriteMethods --> ResultType

Sources: src/lib.rs(L152 - L355) 

The Read Trait

The Read trait provides the fundamental interface for reading bytes from a source. It defines both required and optional methods, with some methods only available when the alloc feature is enabled.

Core Methods

MethodSignatureDescriptionFeature Requirement
readfn read(&mut self, buf: &mut [u8]) -> ResultRequired method to read bytes into bufferNone
read_exactfn read_exact(&mut self, buf: &mut [u8]) -> Result<()>Read exact number of bytes to fill bufferNone
read_to_endfn read_to_end(&mut self, buf: &mut Vec) -> ResultRead all bytes until EOFalloc
read_to_stringfn read_to_string(&mut self, buf: &mut String) -> ResultRead all bytes as UTF-8 stringalloc

The read method is the only required implementation. The read_exact method provides a default implementation that repeatedly calls read until the buffer is filled or EOF is reached. When EOF is encountered before the buffer is filled, it returns an UnexpectedEof error.

flowchart TD
ReadTrait["Read trait"]
RequiredRead["read(&mut self, buf: &mut [u8])"]
ProvidedMethods["Provided implementations"]
ReadExact["read_exact()"]
AllocMethods["Allocation-dependent methods"]
ReadToEnd["read_to_end()"]
ReadToString["read_to_string()"]
ReadExactLoop["Loop calling read() until buffer filled"]
DefaultReadToEnd["default_read_to_end() function"]
AppendToString["append_to_string() helper"]
UnexpectedEof["UnexpectedEof error if incomplete"]
SmallProbe["Small probe reads for efficiency"]
Utf8Validation["UTF-8 validation"]

AllocMethods --> ReadToEnd
AllocMethods --> ReadToString
AppendToString --> Utf8Validation
DefaultReadToEnd --> SmallProbe
ProvidedMethods --> AllocMethods
ProvidedMethods --> ReadExact
ReadExact --> ReadExactLoop
ReadExactLoop --> UnexpectedEof
ReadToEnd --> DefaultReadToEnd
ReadToString --> AppendToString
ReadTrait --> ProvidedMethods
ReadTrait --> RequiredRead

Sources: src/lib.rs(L152 - L188) 

Memory-Efficient Reading Strategy

The default_read_to_end function implements an optimized reading strategy that balances performance with memory usage. It uses several techniques:

  • Small probe reads: Initial 32-byte reads to avoid unnecessary capacity expansion
  • Adaptive buffer sizing: Dynamically adjusts read buffer size based on reader behavior
  • Capacity management: Uses try_reserve to handle allocation failures gracefully
  • Short read detection: Tracks consecutive short reads to optimize buffer sizes

Sources: src/lib.rs(L26 - L150) 

The Write Trait

The Write trait provides the interface for writing bytes to a destination. Like Read, it has one required method with several provided implementations.

Core Methods

MethodSignatureDescription
writefn write(&mut self, buf: &[u8]) -> ResultRequired method to write bytes from buffer
flushfn flush(&mut self) -> Result<()>Required method to flush buffered data
write_allfn write_all(&mut self, buf: &[u8]) -> Result<()>Write entire buffer or return error
write_fmtfn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()>Write formatted output

The write_all method ensures that the entire buffer is written by repeatedly calling write until completion. If write returns 0 (indicating no progress), it returns a WriteZero error.

The write_fmt method enables formatted output support by implementing a bridge between fmt::Write and the I/O Write trait through an internal Adapter struct.

flowchart TD
WriteTrait["Write trait"]
RequiredMethods["Required methods"]
ProvidedMethods["Provided methods"]
WriteMethod["write()"]
FlushMethod["flush()"]
WriteAll["write_all()"]
WriteFmt["write_fmt()"]
WriteLoop["Loop calling write() until complete"]
WriteZeroError["WriteZero error if no progress"]
AdapterStruct["Adapter struct"]
FmtWriteBridge["Bridge to fmt::Write"]
ErrorTranslation["Translate fmt errors to I/O errors"]

AdapterStruct --> FmtWriteBridge
FmtWriteBridge --> ErrorTranslation
ProvidedMethods --> WriteAll
ProvidedMethods --> WriteFmt
RequiredMethods --> FlushMethod
RequiredMethods --> WriteMethod
WriteAll --> WriteLoop
WriteFmt --> AdapterStruct
WriteLoop --> WriteZeroError
WriteTrait --> ProvidedMethods
WriteTrait --> RequiredMethods

Sources: src/lib.rs(L190 - L249) 

The Seek Trait

The Seek trait provides cursor positioning within streams that support random access. It works with the SeekFrom enum to specify different seeking strategies.

Methods and SeekFrom Enum

MethodSignatureDescription
seekfn seek(&mut self, pos: SeekFrom) -> ResultRequired method to seek to specified position
rewindfn rewind(&mut self) -> Result<()>Convenience method to seek to beginning
stream_positionfn stream_position(&mut self) -> ResultGet current position in stream

The SeekFrom enum defines three positioning strategies:

  • Start(u64): Absolute position from beginning
  • End(i64): Relative position from end (negative values seek backwards)
  • Current(i64): Relative position from current location
flowchart TD
SeekTrait["Seek trait"]
SeekMethod["seek(pos: SeekFrom)"]
ConvenienceMethods["Convenience methods"]
Rewind["rewind()"]
StreamPosition["stream_position()"]
SeekFromEnum["SeekFrom enum"]
StartVariant["Start(u64)"]
EndVariant["End(i64)"]
CurrentVariant["Current(i64)"]
SeekStart["seek(SeekFrom::Start(0))"]
SeekCurrent["seek(SeekFrom::Current(0))"]
PositionResult["Returns new position as u64"]

ConvenienceMethods --> Rewind
ConvenienceMethods --> StreamPosition
Rewind --> SeekStart
SeekFromEnum --> CurrentVariant
SeekFromEnum --> EndVariant
SeekFromEnum --> StartVariant
SeekMethod --> PositionResult
SeekMethod --> SeekFromEnum
SeekTrait --> ConvenienceMethods
SeekTrait --> SeekMethod
StreamPosition --> SeekCurrent

Sources: src/lib.rs(L252 - L301) 

The BufRead Trait

The BufRead trait extends Read to provide buffered reading capabilities. It enables efficient line-by-line reading and delimiter-based parsing through its internal buffer management.

Buffer Management Methods

MethodSignatureDescriptionFeature Requirement
fill_buffn fill_buf(&mut self) -> Result<&[u8]>Required method to access internal bufferNone
consumefn consume(&mut self, amt: usize)Required method to mark bytes as consumedNone
has_data_leftfn has_data_left(&mut self) -> ResultCheck if more data is availableNone
read_untilfn read_until(&mut self, byte: u8, buf: &mut Vec) -> ResultRead until delimiter foundalloc
read_linefn read_line(&mut self, buf: &mut String) -> ResultRead until newline foundalloc

The buffered reading pattern follows a fill-consume cycle where fill_buf exposes the internal buffer and consume marks bytes as processed. This allows for efficient parsing without unnecessary copying.

flowchart TD
BufReadTrait["BufRead trait"]
ExtendsRead["extends Read"]
RequiredBuffer["Required buffer methods"]
ProvidedParsing["Provided parsing methods"]
FillBuf["fill_buf() -> &[u8]"]
Consume["consume(amt: usize)"]
HasDataLeft["has_data_left()"]
AllocParsing["Allocation-dependent parsing"]
ReadUntil["read_until(delimiter)"]
ReadLine["read_line()"]
InternalBuffer["Access to internal buffer"]
MarkProcessed["Mark bytes as processed"]
DelimiterSearch["Search for delimiter byte"]
NewlineSearch["Search for 0xA (newline)"]
FillConsumeLoop["fill_buf/consume loop"]
Utf8Append["UTF-8 string append"]

AllocParsing --> ReadLine
AllocParsing --> ReadUntil
BufReadTrait --> ExtendsRead
BufReadTrait --> ProvidedParsing
BufReadTrait --> RequiredBuffer
Consume --> MarkProcessed
DelimiterSearch --> FillConsumeLoop
FillBuf --> InternalBuffer
NewlineSearch --> Utf8Append
ProvidedParsing --> AllocParsing
ProvidedParsing --> HasDataLeft
ReadLine --> NewlineSearch
ReadUntil --> DelimiterSearch
RequiredBuffer --> Consume
RequiredBuffer --> FillBuf

Sources: src/lib.rs(L303 - L355) 

Error Handling in Buffered Reading

The read_until method demonstrates sophisticated error handling, particularly for the WouldBlock error case. When fill_buf returns WouldBlock, the method continues the loop rather than propagating the error, enabling non-blocking I/O patterns.

Sources: src/lib.rs(L320 - L347) 

Supporting Types and Utilities

PollState Structure

The PollState struct provides a simple interface for I/O readiness polling, commonly used in async I/O scenarios:

pub struct PollState {
    pub readable: bool,
    pub writable: bool,
}

This structure enables applications to check I/O readiness without blocking, supporting efficient event-driven programming patterns.

Helper Functions

The crate provides several utility functions that support the trait implementations:

  • default_read_to_end: Optimized implementation for reading all data with size hints
  • append_to_string: UTF-8 validation wrapper for string operations

These functions are designed to be reusable across different implementations while maintaining the performance characteristics expected in no_std environments.

Sources: src/lib.rs(L357 - L380) 

Feature-Gated Functionality

Many methods in the core traits are gated behind the alloc feature, which enables dynamic memory allocation. This design allows the library to provide enhanced functionality when memory allocation is available while maintaining core functionality in highly constrained environments.

flowchart TD
CoreTraits["Core trait methods"]
AlwaysAvailable["Always available"]
AllocGated["alloc feature gated"]
ReadBasic["read(), read_exact()"]
WriteBasic["write(), flush(), write_all()"]
SeekAll["All Seek methods"]
BufReadBasic["fill_buf(), consume()"]
ReadDynamic["read_to_end(), read_to_string()"]
BufReadParsing["read_until(), read_line()"]
AllocFeature["alloc feature"]
NoStdBasic["no_std environments"]
Enhanced["Enhanced environments"]

AllocFeature --> AllocGated
AllocGated --> BufReadParsing
AllocGated --> ReadDynamic
AlwaysAvailable --> BufReadBasic
AlwaysAvailable --> ReadBasic
AlwaysAvailable --> SeekAll
AlwaysAvailable --> WriteBasic
CoreTraits --> AllocGated
CoreTraits --> AlwaysAvailable
Enhanced --> AllocGated
Enhanced --> AlwaysAvailable
NoStdBasic --> AlwaysAvailable

Sources: src/lib.rs(L7 - L8)  src/lib.rs(L21 - L22)  src/lib.rs(L159 - L168)  src/lib.rs(L320 - L355) 

Crate Configuration and Features

Relevant source files

This document explains the configuration structure of the axio crate, including its feature gates, dependencies, and compilation targets. It covers how the crate's modular design enables different functionality levels depending on the target environment's capabilities.

For information about the actual I/O traits and their implementations, see Core I/O Traits and Implementations. For details about the build system and CI configuration, see Build System and CI.

Crate Metadata and Configuration

The axio crate is configured as a no_std-first library designed for resource-constrained environments. The core metadata defines its purpose and compatibility:

PropertyValue
Nameaxio
Version0.1.1
Edition2021
Descriptionstd::io-like I/O traits forno_stdenvironment
Categoriesno-std
Keywordsarceos,io,no-std

The crate uses a dual-licensing model supporting GPL-3.0-or-later, Apache-2.0, and MulanPSL-2.0 licenses, making it suitable for both open source and commercial projects.

Sources: Cargo.toml(L1 - L13) 

Feature System Overview

The axio crate implements a minimal feature system with two defined features:

flowchart TD
subgraph subGraph2["Optional Functionality"]
    DYNAMIC["Dynamic Memory Operations"]
    VECTORS["Vec Support"]
    STRINGS["String Operations"]
end
subgraph subGraph1["Core Library"]
    CORE["Core I/O Traits"]
    NOSTD["no_std Compatibility"]
end
subgraph subGraph0["Feature Configuration"]
    DEFAULT["default = []"]
    ALLOC["alloc = []"]
end

ALLOC --> DYNAMIC
ALLOC --> STRINGS
ALLOC --> VECTORS
CORE --> DYNAMIC
DEFAULT --> CORE
DEFAULT --> NOSTD

Feature Gate Dependencies and Enabled Functionality

The feature system follows a progressive enhancement model:

  • default = []: Provides core I/O traits without any optional features
  • alloc = []: Enables dynamic memory operations that require heap allocation

Sources: Cargo.toml(L14 - L16) 

Dependencies

The crate maintains a minimal dependency footprint to support its no_std target:


Dependency Graph with Feature Gates

Core Dependencies

  • axerrno: Provides unified error handling across the ArceOS ecosystem
  • alloc: Standard library allocation primitives (feature-gated)

The axerrno dependency is always present and provides the Error type used throughout the I/O trait definitions. The alloc dependency is conditionally included only when the alloc feature is enabled.

Sources: Cargo.toml(L18 - L19)  src/lib.rs(L7 - L8)  src/lib.rs(L21 - L22) 

Feature-Specific Functionality

The alloc feature gate controls access to operations requiring dynamic memory allocation:

Core Functionality (Always Available)

flowchart TD
subgraph subGraph0["Always Available"]
    SEEK_OPS["Seek operations"]
    BUFREAD_BASIC["BufRead::fill_buf()"]
    BUFREAD_CONSUME["BufRead::consume()"]
    BUFREAD_HAS_DATA["BufRead::has_data_left()"]
    subgraph subGraph1["Feature-Gated (alloc)"]
        READ_TO_END["Read::read_to_end()"]
        READ_TO_STRING["Read::read_to_string()"]
        BUFREAD_UNTIL["BufRead::read_until()"]
        BUFREAD_LINE["BufRead::read_line()"]
        DEFAULT_READ["default_read_to_end()"]
        READ_BASIC["Read::read()"]
        READ_EXACT["Read::read_exact()"]
        WRITE_BASIC["Write::write()"]
        WRITE_ALL["Write::write_all()"]
        WRITE_FMT["Write::write_fmt()"]
    end
end

Trait Methods by Feature Availability

Alloc-Gated Operations

When the alloc feature is enabled, several additional methods become available:

Trait MethodFunctionRequirements
Read::read_to_end()Reads all bytes to aVecDynamic allocation
Read::read_to_string()Reads UTF-8 data to aStringDynamic allocation + UTF-8 validation
BufRead::read_until()Reads until delimiter toVecDynamic allocation
BufRead::read_line()Reads line toStringDynamic allocation + UTF-8 validation

The implementation uses conditional compilation to gate these features:

#![allow(unused)]
fn main() {
#[cfg(feature = "alloc")]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>
}

Sources: src/lib.rs(L159 - L162)  src/lib.rs(L165 - L168)  src/lib.rs(L320 - L347)  src/lib.rs(L351 - L354) 

Compilation Targets

The crate supports multiple compilation scenarios based on feature selection:

flowchart TD
subgraph subGraph2["Available APIs"]
    BASIC_API["Basic I/O Operations"]
    DYNAMIC_API["Dynamic Memory Operations"]
    COMPAT_API["std::io Compatibility"]
end
subgraph subGraph1["Feature Combinations"]
    MIN["default only"]
    ENHANCED["default + alloc"]
end
subgraph subGraph0["Target Environments"]
    EMBEDDED["Embedded Systems"]
    KERNEL["OS Kernels"]
    NOSTD_APP["no_std Applications"]
    STD_APP["std Applications"]
end

ENHANCED --> BASIC_API
ENHANCED --> COMPAT_API
ENHANCED --> DYNAMIC_API
ENHANCED --> NOSTD_APP
ENHANCED --> STD_APP
MIN --> BASIC_API
MIN --> EMBEDDED
MIN --> KERNEL
MIN --> NOSTD_APP

Compilation Targets and Feature Combinations

No Features (Default)

Provides core I/O traits suitable for:

  • Embedded systems without heap allocation
  • Kernel-space code
  • Applications with strict memory constraints

WithallocFeature

Enables enhanced functionality for:

  • Applications with heap allocation available
  • Code requiring std::io compatibility
  • Systems needing dynamic buffer operations

The crate uses #![cfg_attr(not(doc), no_std)] to maintain no_std compatibility while allowing documentation generation with full standard library support.

Sources: src/lib.rs(L3)  src/lib.rs(L7 - L8) 

Feature Implementation Details

The feature system implementation uses several Rust conditional compilation patterns:

External Crate Imports

#[cfg(feature = "alloc")]
extern crate alloc;

This pattern conditionally imports the alloc crate only when needed, avoiding link-time dependencies in constrained environments.

Type Imports

#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};

Dynamic types are imported only when the corresponding feature is enabled, preventing compilation errors in no_std environments.

Function Gating

Complex functions like default_read_to_end demonstrate sophisticated feature gating with full implementation details available only when alloc is enabled. This function includes optimizations for buffer management, probe reading, and dynamic capacity growth.

Sources: src/lib.rs(L31 - L150)  src/lib.rs(L357 - L370) 

Implementations

Relevant source files

This document provides an overview of the concrete implementations of I/O traits provided by the axio crate. These implementations offer ready-to-use functionality for common I/O operations in no_std environments. For detailed information about the core I/O traits themselves, see Core I/O Traits. For in-depth coverage of specific implementation categories, see Buffered I/O and Basic Type Implementations.

The axio crate provides two main categories of implementations: basic type implementations for fundamental Rust types, and buffered I/O implementations that add performance optimizations. These implementations are designed to work seamlessly in no_std environments while maintaining compatibility with std::io patterns.

Implementation Categories

The axio crate organizes its implementations into distinct categories based on functionality and feature requirements:

CategoryTypesTraits ImplementedFeature Requirements
Basic Types&[u8]ReadNone
Buffered I/OBufReaderRead,BufReadNone for core,allocfor enhanced methods

Implementation Architecture

flowchart TD
subgraph subGraph3["Source Files"]
    ImplsFile["src/impls.rs"]
    BufferedMod["src/buffered/mod.rs"]
    BufReaderFile["src/buffered/bufreader.rs"]
end
subgraph subGraph2["Buffered Implementations"]
    BufReaderImpl["BufReader impl Read"]
    BufReaderBufImpl["BufReader impl BufRead"]
end
subgraph subGraph1["Basic Implementations"]
    SliceImpl["&[u8] impl Read"]
end
subgraph subGraph0["Trait Definitions"]
    ReadTrait["Read trait"]
    WriteTrait["Write trait"]
    SeekTrait["Seek trait"]
    BufReadTrait["BufRead trait"]
end

BufReadTrait --> BufReaderBufImpl
BufReaderFile --> BufReaderBufImpl
BufReaderFile --> BufReaderImpl
BufferedMod --> BufReaderBufImpl
BufferedMod --> BufReaderImpl
ImplsFile --> SliceImpl
ReadTrait --> BufReaderImpl
ReadTrait --> SliceImpl

Sources: src/impls.rs(L1 - L54)  src/buffered/mod.rs(L1 - L4) 

Feature-Gated Implementation Matrix

Different implementations provide varying levels of functionality depending on enabled cargo features:

flowchart TD
subgraph Implementations["Implementations"]
    SliceRead["&[u8] Read impl"]
    BufReaderRead["BufReader Read impl"]
end
subgraph subGraph1["alloc Feature"]
    AllocMethods["Allocation Methodsread_to_end()read_to_string()"]
end
subgraph subGraph0["Default Features"]
    CoreMethods["Core Methodsread()read_exact()"]
end

AllocMethods --> BufReaderRead
AllocMethods --> SliceRead
CoreMethods --> BufReaderRead
CoreMethods --> SliceRead

Sources: src/impls.rs(L47 - L53) 

Implementation Details Overview

Basic Type Implementations

The &[u8] implementation in src/impls.rs provides a fundamental building block for reading from byte slices. This implementation includes performance optimizations and careful memory handling:

  • Single-byte optimization: Special handling for single-byte reads to avoid memcpy overhead
  • Exact reading: read_exact() method with proper error handling for insufficient data
  • Feature-gated extensions: read_to_end() method available only with alloc feature

The implementation uses axerrno::ax_err! macro for consistent error reporting and advances the slice pointer after each read operation.

Buffered I/O Module Structure

The buffered I/O implementations are organized through a module hierarchy:

flowchart TD
subgraph subGraph0["Public API"]
    BufReaderType["BufReader type"]
    ReadImpl["Read trait impl"]
    BufReadImpl["BufRead trait impl"]
end
BufferedMod["src/buffered/mod.rs"]
BufReaderMod["src/buffered/bufreader.rs"]
BufReaderExport["BufReader export"]
BufReaderImpl["BufReader implementation"]

BufReaderExport --> BufReaderType
BufReaderImpl --> BufReadImpl
BufReaderImpl --> ReadImpl
BufReaderMod --> BufReaderImpl
BufferedMod --> BufReaderExport

Sources: src/buffered/mod.rs(L1 - L4) 

Implementation Characteristics

All implementations in axio share common design principles:

  • no_std compatibility: No reliance on standard library components
  • Zero-copy where possible: Efficient memory usage patterns
  • Consistent error handling: Integration with axerrno error system
  • Feature-aware compilation: Conditional compilation based on cargo features

The implementations maintain API compatibility with std::io counterparts while providing the flexibility needed for resource-constrained environments.

Sources: src/impls.rs(L1 - L54)  src/buffered/mod.rs(L1 - L4) 

Buffered I/O

Relevant source files

This document covers the buffered I/O functionality provided by the axio crate, specifically focusing on the BufRead trait and its concrete implementation BufReader<R>. Buffered I/O enables efficient reading from sources by maintaining an internal buffer, reducing the number of system calls and improving performance for sequential read operations.

For information about basic I/O trait implementations for primitive types, see Basic Type Implementations. For details about the core I/O traits that form the foundation of this buffered system, see Core I/O Traits.

BufRead Trait

The BufRead trait extends the basic Read trait to provide buffered reading capabilities. It defines the interface for readers that maintain an internal buffer, allowing for more efficient reading patterns and specialized operations like reading until a delimiter.

Core Methods

The trait defines several key methods for buffer management and specialized reading:

MethodPurposeAvailability
fill_buf()Returns buffered data, filling from source if emptyAlways
consume(amt)Marks bytes as consumed from bufferAlways
has_data_left()Checks if more data is availableAlways
read_until(byte, buf)Reads until delimiter or EOFallocfeature
read_line(buf)Reads until newlineallocfeature
flowchart TD
Read["Read Trait"]
BufRead["BufRead Trait"]
fill_buf["fill_buf()"]
consume["consume(amt)"]
has_data_left["has_data_left()"]
read_until["read_until(byte, buf)"]
read_line["read_line(buf)"]
alloc_feature["alloc feature"]
buffer_ref["&[u8]"]
bool_result["bool"]
usize_result["usize"]

BufRead --> consume
BufRead --> fill_buf
BufRead --> has_data_left
BufRead --> read_line
BufRead --> read_until
Read --> BufRead
fill_buf --> buffer_ref
has_data_left --> bool_result
read_line --> alloc_feature
read_line --> usize_result
read_until --> alloc_feature
read_until --> usize_result

Sources: src/lib.rs(L305 - L355) 

Buffer Management Protocol

The BufRead trait establishes a protocol for buffer management through the interaction between fill_buf() and consume():

  1. fill_buf() returns a reference to the internal buffer, filling it from the underlying source if necessary
  2. Consumer reads from the returned buffer slice
  3. consume(amt) notifies the buffer that amt bytes have been processed and can be discarded
sequenceDiagram
    participant Consumer as "Consumer"
    participant BufReader as "BufReader"
    participant Source as "Source"

    Consumer ->> BufReader: "fill_buf()"
    alt "Buffer Empty"
        BufReader ->> Source: "read(internal_buf)"
        Source -->> BufReader: "data"
    end
    BufReader -->> Consumer: "&[u8] buffer_slice"
    Consumer ->> Consumer: "process data"
    Consumer ->> BufReader: "consume(amt)"
    BufReader ->> BufReader: "advance position"

Sources: src/lib.rs(L308 - L312)  src/buffered/bufreader.rs(L146 - L158) 

BufReader Implementation

The BufReader<R> struct provides a concrete implementation of buffered reading for any type that implements Read. It maintains a fixed-size internal buffer and implements both Read and BufRead traits.

Internal Structure

flowchart TD
subgraph subGraph1["Buffer State"]
    consumed["Consumed Data[0..pos)"]
    available["Available Data[pos..filled)"]
    empty["Empty Space[filled..1024)"]
end
subgraph BufReader<R>["BufReader"]
    inner["inner: R"]
    pos["pos: usize"]
    filled["filled: usize"]
    buf["buf: [u8; 1024]"]
end
source["Underlying Reader"]

filled --> available
filled --> empty
inner --> source
pos --> available
pos --> consumed

Sources: src/buffered/bufreader.rs(L8 - L26) 

The BufReader maintains state through three key fields:

  • inner: The wrapped reader of type R
  • pos: Current read position within the buffer
  • filled: Number of valid bytes in the buffer
  • buf: Fixed 1KB buffer array

Buffer Management Implementation

The BufReader implements sophisticated buffer management to optimize different reading patterns:

flowchart TD
read_request["Read Request"]
is_empty["Buffer Empty?"]
large_read["Request >= 1KB?"]
use_buffer["Use Buffered Data"]
bypass["Bypass BufferDirect Read"]
fill_buffer["Fill Internal Buffer"]
consume_data["consume(amt)"]
return_result["Return Result"]

bypass --> return_result
consume_data --> return_result
fill_buffer --> use_buffer
is_empty --> large_read
is_empty --> use_buffer
large_read --> bypass
large_read --> fill_buffer
read_request --> is_empty
use_buffer --> consume_data

Sources: src/buffered/bufreader.rs(L68 - L83) 

Read Trait Implementation

The Read implementation for BufReader includes several optimizations:

Large Read Bypass

When the buffer is empty and the requested read size is at least 1KB (the buffer capacity), BufReader bypasses its internal buffer entirely and reads directly into the destination buffer.

Optimized read_exact

For small exact reads that fit within the current buffer, BufReader provides an optimized path that avoids the loop in the default read_exact implementation.

Delegation for read_to_end

The read_to_end method drains the current buffer first, then delegates to the inner reader's potentially optimized implementation.

Sources: src/buffered/bufreader.rs(L89 - L108) 

BufRead Trait Implementation

The BufRead implementation manages the internal buffer state:

flowchart TD
fill_buf_call["fill_buf() called"]
buffer_check["is_empty()?"]
read_inner["inner.read(&mut buf)"]
return_current["Return current buffer"]
update_state["pos = 0filled = read_len"]
return_buffer["Return &buf[pos..filled]"]
consume_call["consume(amt) called"]
update_pos["pos = min(pos + amt, filled)"]

buffer_check --> read_inner
buffer_check --> return_current
consume_call --> update_pos
fill_buf_call --> buffer_check
read_inner --> update_state
return_current --> return_buffer
update_state --> return_buffer

Sources: src/buffered/bufreader.rs(L146 - L158) 

Buffer Allocation Strategies

The BufReader uses a fixed-size buffer allocation strategy that balances memory usage with performance:

AspectImplementationRationale
Buffer Size1024 bytes (1KB)Balances memory usage with read efficiency
AllocationStack-allocated arrayAvoids heap allocation, suitable forno_std
ReuseBuffer is reused across readsMinimizes allocation overhead

The buffer size constant is defined as DEFAULT_BUF_SIZE: usize = 1024 and cannot be configured at runtime, ensuring predictable memory usage in constrained environments.

Sources: src/buffered/bufreader.rs(L6)  src/buffered/bufreader.rs(L13) 

Performance Optimizations

Read Pattern Optimization

flowchart TD
subgraph subGraph1["Large Reads"]
    exact["read_exact()"]
    fast_path["Fast Path forBuffer-Sized Reads"]
    copy["Single memcpy"]
    large["Unsupported markdown: blockquote"]
    direct["Direct to Destination"]
    minimal["Minimal Overhead"]
    small["< 1KB reads"]
    buffered["Use Internal Buffer"]
    efficient["Efficient forSequential Access"]
    subgraph subGraph2["Exact Reads"]
        subgraph subGraph0["Small Reads"]
            exact["read_exact()"]
            fast_path["Fast Path forBuffer-Sized Reads"]
            copy["Single memcpy"]
            large["Unsupported markdown: blockquote"]
            direct["Direct to Destination"]
            minimal["Minimal Overhead"]
            small["< 1KB reads"]
            buffered["Use Internal Buffer"]
            efficient["Efficient forSequential Access"]
        end
    end
end

buffered --> efficient
direct --> minimal
exact --> fast_path
fast_path --> copy
large --> direct
small --> buffered

Sources: src/buffered/bufreader.rs(L73 - L76)  src/buffered/bufreader.rs(L89 - L97) 

String Reading Optimization

For read_to_string() operations, BufReader implements an optimization that avoids unnecessary memory copies when the target string is empty, allowing direct reading into the string's internal buffer.

Sources: src/buffered/bufreader.rs(L113 - L142) 

Feature Dependencies

The buffered I/O implementation has conditional compilation based on the alloc feature:

Feature StateAvailable MethodsLimitations
WithoutallocCore buffering, basic readsNo dynamic allocation methods
WithallocAll methods includingread_until,read_lineFull functionality

Methods requiring dynamic allocation are conditionally compiled and only available when the alloc feature is enabled.

Sources: src/buffered/bufreader.rs(L3 - L4)  src/buffered/bufreader.rs(L101)  src/buffered/bufreader.rs(L112) 

Basic Type Implementations

Relevant source files

This section documents the concrete implementations of I/O traits for basic Rust types provided by the axio crate. These implementations enable fundamental data types to participate in the I/O trait ecosystem without requiring additional wrapper types.

The primary focus is on implementations for byte slices (&[u8]), which provide efficient read operations directly from memory. For buffered I/O implementations like BufReader, see Buffered I/O. For error handling across all implementations, see Error Handling.

Byte Slice Read Implementation

The axio crate provides a comprehensive Read trait implementation for byte slices (&[u8]), enabling direct reading from memory buffers. This implementation is found in src/impls.rs(L4 - L54)  and provides all core Read trait methods with performance optimizations.

Implementation Overview

The &[u8] implementation supports three key methods from the Read trait:

MethodPurposeFeature Gate
read()Pull bytes into a bufferAlways available
read_exact()Read exact number of bytesAlways available
read_to_end()Read all remaining bytesallocfeature

Sources: src/impls.rs(L4 - L54) 

Core Read Method

The fundamental read() method implementation uses an optimized approach for copying data from the byte slice to the destination buffer:

flowchart TD
A["read(buf: &mut [u8])"]
B["Calculate amt = min(buf.len(), self.len())"]
C["amt == 1?"]
D["Single byte copy: buf[0] = a[0]"]
E["Slice copy: buf[..amt].copy_from_slice(a)"]
F["Update slice: *self = &self[amt..]"]
G["Return Ok(amt)"]

A --> B
B --> C
C --> D
C --> E
D --> F
E --> F
F --> G

Byte Slice Read Operation Flow

The implementation includes a performance optimization that avoids the overhead of copy_from_slice for single-byte reads, directly assigning buf[0] = a[0] instead.

Sources: src/impls.rs(L6 - L22) 

Exact Read Implementation

The read_exact() method provides guaranteed completion semantics, either reading the exact requested amount or returning an error:

flowchart TD
A["read_exact(buf: &mut [u8])"]
B["buf.len() > self.len()?"]
C["Return UnexpectedEof error"]
D["Calculate amt = buf.len()"]
E["amt == 1?"]
F["Single byte: buf[0] = a[0]"]
G["Slice copy: buf[..amt].copy_from_slice(a)"]
H["Update slice: *self = &self[amt..]"]
I["Return Ok(())"]

A --> B
B --> C
B --> D
D --> E
E --> F
E --> G
F --> H
G --> H
H --> I

Exact Read Operation Flow

This method differs from read() by guaranteeing that either all requested bytes are read or an UnexpectedEof error is returned.

Sources: src/impls.rs(L25 - L44) 

Allocation-Dependent Methods

When the alloc feature is enabled, the byte slice implementation provides read_to_end(), which efficiently transfers all remaining data:

flowchart TD
A["read_to_end(buf: &mut Vec)"]
B["buf.extend_from_slice(self)"]
C["let len = self.len()"]
D["*self = &self[len..]"]
E["Return Ok(len)"]

A --> B
B --> C
C --> D
D --> E

Read to End Operation Flow

This implementation leverages Vec::extend_from_slice() for optimal performance when copying all remaining data from the byte slice.

Sources: src/impls.rs(L47 - L53) 

Trait Method Mapping

The following diagram shows how the generic Read trait methods map to specific implementations for byte slices:

classDiagram
note for Read "Optimized for memory efficiencySingle-byte copy optimizationDirect slice manipulation"
note for ByteSliceImpl "Optimized for memory efficiencySingle-byte copy optimizationDirect slice manipulation"
note for Read "Unsupported markdown: list"
class Read {
    <<trait>>
    
    +read(buf: &mut [u8]) Result~usize~
    +read_exact(buf: &mut [u8]) Result~() ~
    +read_to_end(buf: &mut Vec~u8~) Result~usize~
}

class ByteSliceImpl {
    
    +read(buf: &mut [u8]) Result~usize~
    +read_exact(buf: &mut [u8]) Result~() ~
    +read_to_end(buf: &mut Vec~u8~) Result~usize~
}

class &[u8] {
    -data: bytes
    -position: usize
    
}

Read  ..|>  ByteSliceImpl : implements
ByteSliceImpl  -->  ByteSliceImpl : operates on

Trait Implementation Relationship

Sources: src/lib.rs(L152 - L188)  src/impls.rs(L4 - L54) 

Performance Characteristics

The byte slice implementation includes several performance optimizations:

Memory Copy Optimization

The implementation uses conditional logic to optimize memory operations based on the amount of data being copied:

ScenarioImplementationRationale
Single byte (amt == 1)Direct assignment:buf[0] = a[0]Avoidsmemcpyoverhead
Multiple bytes (amt > 1)Slice copy:copy_from_slice(a)Leverages optimizedmemcpy

Slice Management

All methods update the source slice pointer (*self = b) to reflect consumed data, maintaining zero-copy semantics where the slice reference advances through the underlying data without additional allocations.

Sources: src/impls.rs(L14 - L18)  src/impls.rs(L36 - L40) 

Error Handling Integration

The byte slice implementations integrate with the axio error system through the axerrno crate:

flowchart TD
A["&[u8] methods"]
B["Validation check"]
C["Successful operation"]
D["axerrno::ax_err!"]
E["Error::UnexpectedEof"]
F["Result::Ok"]
G["Result::Err"]

A --> B
B --> C
B --> D
C --> F
D --> E
E --> G

Error Flow in Byte Slice Operations

The primary error condition for byte slice operations is UnexpectedEof, which occurs when read_exact() is called with a buffer larger than the remaining slice data.

Sources: src/impls.rs(L27)  src/error.rs

Supporting Systems

Relevant source files

This document provides an overview of the supporting modules that enable the core I/O functionality in the axio crate. These systems provide essential infrastructure for error handling, convenience imports, and integration with external dependencies. The supporting systems are designed to be minimal and focused, maintaining the crate's no_std compatibility while providing necessary functionality.

For detailed information about error handling mechanisms, see Error Handling. For information about convenience imports and trait re-exports, see Prelude Module.

Supporting Module Architecture

The axio crate relies on two primary supporting modules that work together to enable the core I/O trait functionality. These modules provide a clean separation between error handling concerns and user-facing API convenience.

Supporting Systems Overview

flowchart TD
subgraph subGraph3["Client Code"]
    user_imports["use axio::prelude::*"]
    error_handling["Result"]
end
subgraph subGraph2["Core I/O System"]
    lib_rs["src/lib.rs"]
    traits["Read, Write, Seek, BufRead"]
end
subgraph subGraph1["axio Supporting Systems"]
    error_rs["src/error.rs"]
    prelude_rs["src/prelude.rs"]
end
subgraph subGraph0["External Dependencies"]
    axerrno["axerrno crate"]
end

axerrno --> error_rs
error_rs --> error_handling
error_rs --> lib_rs
lib_rs --> error_handling
lib_rs --> traits
prelude_rs --> user_imports
traits --> prelude_rs

Sources: src/error.rs(L1 - L3)  src/prelude.rs(L1 - L12) 

Module Interaction Flow

The supporting systems create a clear data flow that separates concerns while maintaining a cohesive API surface.

flowchart TD
subgraph subGraph2["Client Usage"]
    trait_methods["trait methods"]
    result_handling["Result handling"]
end
subgraph subGraph1["Import Path"]
    core_traits["BufRead, Read, Seek, Write"]
    prelude_exports["prelude.rs exports"]
    glob_import["use axio::prelude::*"]
end
subgraph subGraph0["Error Path"]
    axerrno_AxError["axerrno::AxError"]
    axerrno_AxResult["axerrno::AxResult"]
    error_rs_Error["error.rs::Error"]
    error_rs_Result["error.rs::Result"]
end

axerrno_AxError --> error_rs_Error
axerrno_AxResult --> error_rs_Result
core_traits --> prelude_exports
error_rs_Result --> result_handling
glob_import --> trait_methods
prelude_exports --> glob_import
trait_methods --> result_handling

Sources: src/error.rs(L1 - L2)  src/prelude.rs(L11) 

Error System Integration

The src/error.rs module serves as a facade layer that re-exports error types from the axerrno crate. This design provides a stable API surface while delegating the actual error handling implementation to the specialized axerrno crate.

ComponentTypePurpose
ErrorType aliasRe-exportsaxerrno::AxErroras the primary error type
ResultType aliasRe-exportsaxerrno::AxResultas the standard result type

The error system provides consistent error handling across all I/O operations without requiring clients to directly depend on the axerrno crate.

Sources: src/error.rs(L1 - L2) 

Prelude System Organization

The src/prelude.rs module provides a convenience layer that re-exports the four core I/O traits. This follows the established Rust pattern of providing a prelude module for commonly used imports.

The prelude exports include:

  • BufRead - Buffered reading operations
  • Read - Basic reading operations
  • Seek - Stream positioning operations
  • Write - Basic writing operations

This design allows clients to import all core functionality with a single glob import: use axio::prelude::*.

Sources: src/prelude.rs(L11) 

System Dependencies

The supporting systems maintain minimal external dependencies to preserve the crate's no_std compatibility:

SystemExternal DependenciesPurpose
Error handlingaxerrnocrateProvides error types and result handling
PreludeNoneRe-exports internal traits

Both supporting systems are designed to be thin facade layers that add minimal overhead while providing essential functionality for the core I/O trait system.

Sources: src/error.rs(L1 - L2)  src/prelude.rs(L1 - L12) 

Error Handling

Relevant source files

This document covers error handling mechanisms within the axio crate, explaining how errors are defined, propagated, and handled across I/O operations. The error system provides consistent error reporting for no_std environments while maintaining compatibility with standard I/O error patterns.

For information about the core I/O traits that use these error types, see Core I/O Traits. For details about specific implementations that handle errors, see Implementations.

Error Type System

The axio crate implements a minimalist error facade over the axerrno crate, providing consistent error handling across all I/O operations. The error system consists of two primary types that are re-exported from axerrno.

Core Error Types

TypeSourcePurpose
Erroraxerrno::AxErrorRepresents all possible I/O error conditions
Resultaxerrno::AxResultStandard result type for I/O operations
flowchart TD
subgraph subGraph2["I/O Trait Methods"]
    READ_METHOD["Read::read()"]
    WRITE_METHOD["Write::write()"]
    SEEK_METHOD["Seek::seek()"]
    FILL_BUF_METHOD["BufRead::fill_buf()"]
end
subgraph subGraph1["axerrno Crate"]
    AX_ERROR["AxError"]
    AX_RESULT["AxResult<T>"]
end
subgraph subGraph0["axio Error System"]
    ERROR_MOD["src/error.rs"]
    ERROR_TYPE["Error"]
    RESULT_TYPE["Result<T>"]
end

ERROR_MOD --> ERROR_TYPE
ERROR_MOD --> RESULT_TYPE
ERROR_TYPE --> AX_ERROR
FILL_BUF_METHOD --> RESULT_TYPE
READ_METHOD --> RESULT_TYPE
RESULT_TYPE --> AX_RESULT
SEEK_METHOD --> RESULT_TYPE
WRITE_METHOD --> RESULT_TYPE

Sources: src/error.rs(L1 - L3)  src/lib.rs(L19) 

Error Categories and Usage Patterns

The axio crate utilizes specific error variants through the ax_err! macro to provide meaningful error information for different failure scenarios.

flowchart TD
subgraph subGraph3["Buffered Operations"]
    READ_UNTIL["read_until()"]
    READ_LINE["read_line()"]
    WOULD_BLOCK["WouldBlock"]
end
subgraph subGraph2["Write Operations"]
    WRITE_ALL["write_all()"]
    WRITE_FMT["write_fmt()"]
    WRITE_ZERO["WriteZero"]
    INVALID_DATA["InvalidData"]
end
subgraph subGraph1["Read Operations"]
    READ_EXACT["read_exact()"]
    READ_TO_END["read_to_end()"]
    UNEXPECTED_EOF["UnexpectedEof"]
end
subgraph subGraph0["Memory Operations"]
    MEM_ALLOC["Memory Allocation"]
    NO_MEMORY["NoMemory"]
end

MEM_ALLOC --> NO_MEMORY
READ_EXACT --> UNEXPECTED_EOF
READ_LINE --> INVALID_DATA
READ_TO_END --> NO_MEMORY
READ_UNTIL --> WOULD_BLOCK
WRITE_ALL --> WRITE_ZERO
WRITE_FMT --> INVALID_DATA

Sources: src/lib.rs(L89)  src/lib.rs(L183)  src/lib.rs(L203)  src/lib.rs(L244)  src/lib.rs(L328)  src/lib.rs(L366) 

Error Creation and Propagation

Theax_err!Macro Pattern

The axio crate consistently uses the ax_err! macro from axerrno to create structured errors with context information. This macro allows for both simple error creation and error wrapping.

Error TypeUsage ContextLocation
NoMemoryVector allocation failuresrc/lib.rs89
UnexpectedEofIncomplete buffer fillsrc/lib.rs183
WriteZeroFailed complete writesrc/lib.rs203
InvalidDataFormat/UTF-8 errorssrc/lib.rs244src/lib.rs366

Error Flow in I/O Operations

flowchart TD
subgraph subGraph3["Error Propagation"]
    QUESTION_MARK["? operator"]
    EARLY_RETURN["Early return"]
    RESULT_TYPE_RETURN["Result<T> return"]
end
subgraph subGraph2["Error Creation"]
    AX_ERR_MACRO["ax_err! macro"]
    ERROR_CONTEXT["Error with context"]
end
subgraph subGraph1["Error Detection Points"]
    MEM_CHECK["Memory Allocation"]
    BUFFER_CHECK["Buffer State"]
    DATA_CHECK["Data Validation"]
    BLOCKING_CHECK["Non-blocking State"]
end
subgraph subGraph0["Operation Types"]
    READ_OP["Read Operation"]
    WRITE_OP["Write Operation"]
    SEEK_OP["Seek Operation"]
    BUF_OP["Buffered Operation"]
end
IO_OP["I/O Operation Initiated"]

AX_ERR_MACRO --> QUESTION_MARK
BLOCKING_CHECK --> ERROR_CONTEXT
BUFFER_CHECK --> AX_ERR_MACRO
BUF_OP --> BLOCKING_CHECK
BUF_OP --> DATA_CHECK
BUF_OP --> MEM_CHECK
DATA_CHECK --> AX_ERR_MACRO
EARLY_RETURN --> RESULT_TYPE_RETURN
ERROR_CONTEXT --> QUESTION_MARK
IO_OP --> BUF_OP
IO_OP --> READ_OP
IO_OP --> SEEK_OP
IO_OP --> WRITE_OP
MEM_CHECK --> AX_ERR_MACRO
QUESTION_MARK --> EARLY_RETURN
READ_OP --> BUFFER_CHECK
WRITE_OP --> BUFFER_CHECK

Sources: src/lib.rs(L24)  src/lib.rs(L89)  src/lib.rs(L183)  src/lib.rs(L203)  src/lib.rs(L244)  src/lib.rs(L328)  src/lib.rs(L366) 

Specific Error Handling Implementations

Memory Management Errors

The default_read_to_end function demonstrates sophisticated error handling for memory allocation failures in no_std environments.

flowchart TD
subgraph subGraph1["Error Handling Flow"]
    ALLOCATION_FAIL["Allocation Fails"]
    ERROR_WRAPPING["Error Wrapping"]
    EARLY_RETURN["Return Err(Error)"]
end
subgraph subGraph0["default_read_to_end Function"]
    START["Function Entry"]
    TRY_RESERVE["buf.try_reserve(PROBE_SIZE)"]
    CHECK_RESULT["Check Result"]
    WRAP_ERROR["ax_err!(NoMemory, e)"]
    CONTINUE["Continue Operation"]
end

ALLOCATION_FAIL --> WRAP_ERROR
CHECK_RESULT --> ALLOCATION_FAIL
CHECK_RESULT --> CONTINUE
ERROR_WRAPPING --> EARLY_RETURN
START --> TRY_RESERVE
TRY_RESERVE --> CHECK_RESULT
WRAP_ERROR --> ERROR_WRAPPING

Sources: src/lib.rs(L88 - L90) 

Buffer State Validation

The read_exact method implements comprehensive error handling for incomplete read operations.

sequenceDiagram
    participant Client as Client
    participant read_exact as read_exact
    participant read_method as read_method
    participant ax_err_macro as ax_err_macro

    Client ->> read_exact: read_exact(&mut buf)
    loop "While buffer not
    loop empty"
        read_exact ->> read_method: read(buf)
    alt "Read returns 0"
        read_method -->> read_exact: Ok(0)
        read_exact ->> read_exact: break loop
    else "Read returns n > 0"
        read_method -->> read_exact: Ok(n)
        read_exact ->> read_exact: advance buffer
    else "Read returns error"
        read_method -->> read_exact: Err(e)
        read_exact -->> Client: Err(e)
    end
    end
    end
    alt "Buffer still has data"
        read_exact ->> ax_err_macro: ax_err!(UnexpectedEof, message)
        ax_err_macro -->> read_exact: Error
        read_exact -->> Client: Err(Error)
    else "Buffer fully consumed"
        read_exact -->> Client: Ok(())
    end

Sources: src/lib.rs(L171 - L187) 

Non-blocking I/O Error Handling

The read_until method demonstrates special handling for WouldBlock errors in non-blocking scenarios.

Error TypeHandling StrategyCode Location
WouldBlockContinue loop, retry operationsrc/lib.rs328
Other errorsPropagate immediatelysrc/lib.rs329
flowchart TD
subgraph subGraph0["read_until Error Handling"]
    FILL_BUF["fill_buf()"]
    MATCH_RESULT["Match Result"]
    WOULD_BLOCK_CASE["WouldBlock"]
    OTHER_ERROR["Other Error"]
    CONTINUE_LOOP["continue"]
    RETURN_ERROR["return Err(e)"]
end

FILL_BUF --> MATCH_RESULT
MATCH_RESULT --> OTHER_ERROR
MATCH_RESULT --> WOULD_BLOCK_CASE
OTHER_ERROR --> RETURN_ERROR
WOULD_BLOCK_CASE --> CONTINUE_LOOP

Sources: src/lib.rs(L325 - L329) 

Integration with axerrno

The axio error system serves as a thin facade over the axerrno crate, providing domain-specific error handling while maintaining the underlying error infrastructure.

Re-export Pattern

The error module uses a simple re-export pattern to maintain API consistency while delegating actual error functionality to axerrno.

flowchart TD
subgraph subGraph2["axerrno Crate Features"]
    ERROR_CODES["Error Code Definitions"]
    ERROR_CONTEXT["Error Context Support"]
    MACRO_SUPPORT["ax_err! Macro"]
end
subgraph subGraph1["src/error.rs Implementation"]
    AXERROR_IMPORT["axerrno::AxError as Error"]
    AXRESULT_IMPORT["axerrno::AxResult as Result"]
end
subgraph subGraph0["axio Public API"]
    PUBLIC_ERROR["pub use Error"]
    PUBLIC_RESULT["pub use Result"]
end

AXERROR_IMPORT --> ERROR_CODES
AXRESULT_IMPORT --> ERROR_CODES
MACRO_SUPPORT --> ERROR_CONTEXT
PUBLIC_ERROR --> AXERROR_IMPORT
PUBLIC_RESULT --> AXRESULT_IMPORT

Sources: src/error.rs(L1 - L2)  src/lib.rs(L24) 

Error Handling Best Practices

The axio codebase demonstrates several consistent patterns for error handling in no_std I/O operations:

  1. Immediate Error Propagation: Use the ? operator for most error conditions
  2. Contextual Error Creation: Use ax_err! macro with descriptive messages
  3. Special Case Handling: Handle WouldBlock errors differently from fatal errors
  4. Memory Safety: Wrap allocation errors with appropriate context
  5. Data Validation: Validate UTF-8 and format correctness with InvalidData errors

These patterns ensure consistent error behavior across all I/O traits while maintaining the lightweight design required for no_std environments.

Sources: src/lib.rs(L24)  src/lib.rs(L89)  src/lib.rs(L183)  src/lib.rs(L203)  src/lib.rs(L244)  src/lib.rs(L328)  src/lib.rs(L366)  src/error.rs(L1 - L3) 

Prelude Module

Relevant source files

The Prelude Module provides convenient glob imports for the core I/O traits in the axio crate. This module serves as a single import point for the four fundamental I/O traits (Read, Write, Seek, and BufRead), following the same pattern as Rust's standard library std::io::prelude module.

For information about the individual I/O traits themselves, see Core I/O Traits. For details about error handling that works with these traits, see Error Handling.

Purpose and Design

The prelude module eliminates the need for multiple individual trait imports by providing a single glob import that brings all essential I/O traits into scope. This design pattern reduces boilerplate code in modules that perform extensive I/O operations.

Module Structure

flowchart TD
subgraph subGraph3["Client Code"]
    CLIENT["use axio::prelude::*"]
    USAGE["All traits available"]
end
subgraph subGraph2["axio Crate Root"]
    LIB["src/lib.rs"]
    subgraph subGraph1["Prelude Module"]
        PRELUDE_MOD["src/prelude.rs"]
        REEXPORT["pub use super::{BufRead, Read, Seek, Write}"]
    end
    subgraph subGraph0["Core Traits Definition"]
        READ["Read trait"]
        WRITE["Write trait"]
        SEEK["Seek trait"]
        BUFREAD["BufRead trait"]
    end
end

BUFREAD --> REEXPORT
CLIENT --> USAGE
LIB --> BUFREAD
LIB --> PRELUDE_MOD
LIB --> READ
LIB --> SEEK
LIB --> WRITE
PRELUDE_MOD --> CLIENT
READ --> REEXPORT
SEEK --> REEXPORT
WRITE --> REEXPORT

Sources: src/lib.rs(L16)  src/prelude.rs(L11) 

Trait Re-export Implementation

The prelude module uses a simple re-export mechanism to make the core traits available through a single import path. The implementation consists of a single pub use statement that imports all four traits from the parent module:

TraitPurposeKey Methods
ReadReading bytes from a sourceread(),read_exact(),read_to_end()
WriteWriting bytes to a destinationwrite(),flush(),write_all()
SeekPositioning within a streamseek(),rewind(),stream_position()
BufReadBuffered reading operationsfill_buf(),consume(),read_until()

Sources: src/prelude.rs(L11)  src/lib.rs(L152 - L355) 

Usage Patterns

Standard Import Pattern

The prelude follows the conventional Rust pattern for convenience imports:

use axio::prelude::*;

This single import brings all four core I/O traits into scope, enabling their methods to be called on any type that implements them.

Integration with no_std Environment

The prelude module maintains compatibility with no_std environments while providing conditional access to allocation-dependent features:

flowchart TD
subgraph subGraph3["Feature Compilation"]
    NO_ALLOC["default features"]
    WITH_ALLOC["alloc feature enabled"]
end
subgraph subGraph2["Import Resolution"]
    GLOB["use axio::prelude::*"]
    subgraph subGraph1["Method Availability"]
        CORE_METHODS["Core methods always available"]
        ALLOC_METHODS["Allocation methods (feature-gated)"]
    end
    subgraph subGraph0["Available Traits"]
        READ_TRAIT["Read trait"]
        WRITE_TRAIT["Write trait"]
        SEEK_TRAIT["Seek trait"]
        BUFREAD_TRAIT["BufRead trait"]
    end
end

BUFREAD_TRAIT --> ALLOC_METHODS
BUFREAD_TRAIT --> CORE_METHODS
GLOB --> BUFREAD_TRAIT
GLOB --> READ_TRAIT
GLOB --> SEEK_TRAIT
GLOB --> WRITE_TRAIT
NO_ALLOC --> CORE_METHODS
READ_TRAIT --> ALLOC_METHODS
READ_TRAIT --> CORE_METHODS
SEEK_TRAIT --> CORE_METHODS
WITH_ALLOC --> ALLOC_METHODS
WITH_ALLOC --> CORE_METHODS
WRITE_TRAIT --> CORE_METHODS

Sources: src/prelude.rs(L1 - L12)  src/lib.rs(L7 - L8)  src/lib.rs(L21 - L22) 

Implementation Details

Module Declaration

The prelude module is declared as a public module in the crate root, making it accessible to external users:

src/lib.rs(L16)  - pub mod prelude;

Re-export Mechanism

The module uses Rust's pub use syntax to re-export traits from the parent module scope:

src/prelude.rs(L11)  - pub use super::{BufRead, Read, Seek, Write};

This creates public aliases for the traits defined in the parent module, allowing them to be imported through the prelude path.

Documentation Integration

The prelude module includes comprehensive documentation that explains its purpose and provides usage examples, following the same documentation style as std::io::prelude:

src/prelude.rs(L1 - L9)  - Contains module-level documentation with purpose explanation and usage example.

Relationship to Standard Library

The axio prelude module mirrors the design and purpose of Rust's standard library std::io::prelude module, providing a familiar interface for developers transitioning between std and no_std environments:

flowchart TD
subgraph subGraph2["Design Pattern"]
    PATTERN["Convenience re-exports"]
    GLOB_IMPORT["Glob import usage"]
end
subgraph axio["axio"]
    AXIO_PRELUDE["axio::prelude"]
    AXIO_TRAITS["axio I/O traits"]
end
subgraph std::io["std::io"]
    STD_PRELUDE["std::io::prelude"]
    STD_TRAITS["std I/O traits"]
end

AXIO_PRELUDE --> PATTERN
AXIO_TRAITS --> AXIO_PRELUDE
PATTERN --> GLOB_IMPORT
STD_PRELUDE --> PATTERN
STD_TRAITS --> STD_PRELUDE

Sources: src/prelude.rs(L8)  src/prelude.rs(L11) 

This design maintains API compatibility and familiar usage patterns while providing the no_std compatibility that axio offers.

Development and Maintenance

Relevant source files

This document provides guidance for contributors and maintainers of the axio crate, covering development workflows, build processes, and quality assurance practices. It outlines the automated systems that ensure code quality and compatibility across multiple target environments.

For detailed information about the CI pipeline and multi-target builds, see Build System and CI. For development environment setup and configuration files, see Project Configuration.

Development Workflow Overview

The axio crate follows a rigorous development process designed to maintain compatibility across diverse no_std environments. The development workflow emphasizes automated quality checks, multi-target validation, and comprehensive documentation.

Multi-Target Development Strategy

The crate targets multiple architectures and environments simultaneously, requiring careful consideration of platform-specific constraints:

flowchart TD
DEV["Developer Changes"]
PR["Pull Request"]
PUSH["Direct Push"]
CI["CI Pipeline"]
SETUP["Toolchain Setup"]
MATRIX["Target Matrix Execution"]
LINUX["x86_64-unknown-linux-gnu(Linux with std)"]
BARE_X86["x86_64-unknown-none(Bare metal x86_64)"]
RISCV["riscv64gc-unknown-none-elf(RISC-V bare metal)"]
ARM["aarch64-unknown-none-softfloat(ARM64 bare metal)"]
CHECKS["Quality Checks"]
FORMAT["cargo fmt --check"]
CLIPPY["cargo clippy"]
BUILD["cargo build"]
TEST["cargo test"]
DOC_JOB["Documentation Job"]
DOC_BUILD["cargo doc"]
DEPLOY["GitHub Pages Deploy"]

ARM --> CHECKS
BARE_X86 --> CHECKS
CHECKS --> BUILD
CHECKS --> CLIPPY
CHECKS --> FORMAT
CHECKS --> TEST
CI --> DOC_JOB
CI --> SETUP
DEV --> PR
DOC_BUILD --> DEPLOY
DOC_JOB --> DOC_BUILD
LINUX --> CHECKS
MATRIX --> ARM
MATRIX --> BARE_X86
MATRIX --> LINUX
MATRIX --> RISCV
PR --> CI
PUSH --> CI
RISCV --> CHECKS
SETUP --> MATRIX
TEST --> LINUX

Sources: .github/workflows/ci.yml(L1 - L56) 

Quality Assurance Pipeline

The development process enforces multiple layers of quality assurance through automated checks:

Check TypeToolPurposeScope
Code Formattingcargo fmt --checkEnsures consistent code styleAll targets
Lintingcargo clippy --all-featuresCatches common mistakes and improvementsAll targets
Compilationcargo build --all-featuresVerifies code compiles successfullyAll targets
Unit Testingcargo testValidates functionalityLinux target only
Documentationcargo doc --no-deps --all-featuresEnsures documentation builds correctlyAll features

The CI configuration uses specific flags to enforce documentation quality through RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs .github/workflows/ci.yml(L40)  ensuring all public APIs are properly documented.

Toolchain Requirements

The project requires the nightly Rust toolchain with specific components and targets:

flowchart TD
NIGHTLY["nightly toolchain"]
COMPONENTS["Required Components"]
RUST_SRC["rust-src(for no_std targets)"]
CLIPPY["clippy(for linting)"]
RUSTFMT["rustfmt(for formatting)"]
TARGETS["Target Platforms"]
STD_TARGET["x86_64-unknown-linux-gnu"]
NOSTD_TARGETS["no_std targets"]
X86_BARE["x86_64-unknown-none"]
RISCV_BARE["riscv64gc-unknown-none-elf"]
ARM_BARE["aarch64-unknown-none-softfloat"]

COMPONENTS --> CLIPPY
COMPONENTS --> RUSTFMT
COMPONENTS --> RUST_SRC
NIGHTLY --> COMPONENTS
NIGHTLY --> TARGETS
NOSTD_TARGETS --> ARM_BARE
NOSTD_TARGETS --> RISCV_BARE
NOSTD_TARGETS --> X86_BARE
TARGETS --> NOSTD_TARGETS
TARGETS --> STD_TARGET

Sources: .github/workflows/ci.yml(L15 - L19) 

Documentation Generation and Deployment

The crate maintains automatically generated documentation deployed to GitHub Pages. The documentation build process includes:

  1. Strict Documentation Standards: The build fails on missing documentation or broken internal links
  2. Feature-Complete Documentation: Built with --all-features to include all available functionality
  3. Automatic Deployment: Documentation is automatically deployed from the default branch
  4. Custom Index: Generates a redirect index page pointing to the main crate documentation

The documentation deployment uses a single-commit strategy to the gh-pages branch, ensuring a clean deployment history .github/workflows/ci.yml(L53 - L55) 

Development Environment Setup

Repository Structure

The development environment excludes certain files and directories from version control:

flowchart TD
REPO["Repository Root"]
TRACKED["Tracked Files"]
IGNORED["Ignored Files"]
SRC["src/ directory"]
CARGO["Cargo.toml"]
README["README.md"]
CI[".github/workflows/"]
TARGET["target/(build artifacts)"]
VSCODE[".vscode/(editor config)"]
DSSTORE[".DS_Store(macOS metadata)"]
LOCK["Cargo.lock(dependency versions)"]

IGNORED --> DSSTORE
IGNORED --> LOCK
IGNORED --> TARGET
IGNORED --> VSCODE
REPO --> IGNORED
REPO --> TRACKED
TRACKED --> CARGO
TRACKED --> CI
TRACKED --> README
TRACKED --> SRC

Sources: .gitignore(L1 - L5) 

Build Artifact Management

The target/ directory is excluded from version control as it contains build artifacts that are regenerated during compilation. The Cargo.lock file is also ignored, following Rust library conventions where lock files are typically not committed for libraries to allow downstream consumers flexibility in dependency resolution.

Continuous Integration Architecture

The CI system uses a matrix strategy to validate the crate across multiple target environments simultaneously:

flowchart TD
TRIGGER["CI Triggers"]
EVENTS["Event Types"]
PUSH["push events"]
PR["pull_request events"]
STRATEGY["Matrix Strategy"]
FAIL_FAST["fail-fast: false"]
RUST_VER["rust-toolchain: [nightly]"]
TARGET_MATRIX["Target Matrix"]
T1["x86_64-unknown-linux-gnu"]
T2["x86_64-unknown-none"]
T3["riscv64gc-unknown-none-elf"]
T4["aarch64-unknown-none-softfloat"]
PARALLEL["Parallel Execution"]
JOB1["ci job"]
JOB2["doc job"]
VALIDATION["Code Validation"]
DOC_GEN["Documentation Generation"]

EVENTS --> PR
EVENTS --> PUSH
JOB1 --> VALIDATION
JOB2 --> DOC_GEN
PARALLEL --> JOB1
PARALLEL --> JOB2
STRATEGY --> FAIL_FAST
STRATEGY --> PARALLEL
STRATEGY --> RUST_VER
STRATEGY --> TARGET_MATRIX
TARGET_MATRIX --> T1
TARGET_MATRIX --> T2
TARGET_MATRIX --> T3
TARGET_MATRIX --> T4
TRIGGER --> EVENTS
TRIGGER --> STRATEGY

Sources: .github/workflows/ci.yml(L5 - L12)  .github/workflows/ci.yml(L32 - L36) 

The fail-fast: false configuration ensures that failures in one target don't prevent testing of other targets, providing comprehensive feedback about platform-specific issues .github/workflows/ci.yml(L9) 

Contributing Guidelines

Code Style and Standards

All contributions must pass the automated quality checks:

  • Formatting: Code must be formatted using cargo fmt with default settings
  • Linting: All clippy warnings must be addressed, with the exception of clippy::new_without_default which is explicitly allowed .github/workflows/ci.yml(L25) 
  • Documentation: All public APIs must be documented to pass the strict documentation checks
  • Testing: New functionality should include appropriate unit tests

Testing Strategy

The testing approach recognizes the constraints of different target environments:

  • Unit Tests: Run only on x86_64-unknown-linux-gnu target due to standard library requirements .github/workflows/ci.yml(L29 - L30) 
  • Compilation Tests: All targets must compile successfully to ensure no_std compatibility
  • Feature Testing: All tests run with --all-features to validate optional functionality

This testing strategy ensures that while functionality is validated thoroughly on one platform, compilation compatibility is verified across all supported targets.

Sources: .github/workflows/ci.yml(L24 - L30) 

Build System and CI

Relevant source files

This document covers the build system configuration and continuous integration pipeline for the axio crate. It explains how the project is structured for compilation across multiple target platforms, the automated testing strategy, and the documentation deployment process. For information about the crate's feature configuration and dependencies, see Crate Configuration and Features.

Build Configuration

The axio crate uses a minimal build configuration designed for no_std compatibility across diverse target platforms. The build system is defined primarily through Cargo.toml and supports conditional compilation based on feature flags.

Package Metadata and Dependencies

The crate is configured as a library package with specific metadata targeting the embedded and OS kernel development ecosystem:

ConfigurationValue
Package Nameaxio
Version0.1.1
Edition2021
LicenseDual/Triple licensed (GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0)

The dependency structure is intentionally minimal, with only one required dependency:

  • axerrno = "0.1" - Provides error handling types compatible with no_std environments

Feature Gate Configuration

The build system supports feature-based conditional compilation through two defined features:

flowchart TD
subgraph subGraph2["Available APIs"]
    CORE_API["Core I/O Traitsread(), write(), seek()"]
    ALLOC_API["Dynamic Memory APIsread_to_end(), read_to_string()"]
end
subgraph subGraph1["Compilation Modes"]
    MINIMAL["Minimal Buildno_std only"]
    ENHANCED["Enhanced Buildno_std + alloc"]
end
subgraph subGraph0["Feature Configuration"]
    DEFAULT["default = []"]
    ALLOC["alloc = []"]
end

ALLOC --> ENHANCED
DEFAULT --> MINIMAL
ENHANCED --> ALLOC_API
ENHANCED --> CORE_API
MINIMAL --> CORE_API

Sources: Cargo.toml(L14 - L16) 

CI Pipeline Architecture

The continuous integration system uses GitHub Actions to validate code quality, build compatibility, and documentation generation across multiple target platforms. The pipeline is defined in a single workflow file that orchestrates multiple jobs.

Workflow Trigger Configuration

The CI pipeline activates on two primary events:

  • Push events to any branch
  • Pull request events

Multi-Target Build Matrix

The CI system employs a matrix strategy to test compilation across diverse target platforms:

flowchart TD
subgraph subGraph2["CI Steps"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN_SETUP["dtolnay/rust-toolchain@nightly"]
    FORMAT_CHECK["cargo fmt --check"]
    CLIPPY_CHECK["cargo clippy"]
    BUILD_STEP["cargo build"]
    TEST_STEP["cargo test"]
end
subgraph subGraph1["Build Matrix"]
    TOOLCHAIN["nightly toolchain"]
    TARGET1["x86_64-unknown-linux-gnu"]
    TARGET2["x86_64-unknown-none"]
    TARGET3["riscv64gc-unknown-none-elf"]
    TARGET4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["CI Workflow"]
    TRIGGER["push/pull_request events"]
    CI_JOB["ci job"]
    DOC_JOB["doc job"]
end

BUILD_STEP --> TEST_STEP
CHECKOUT --> TOOLCHAIN_SETUP
CI_JOB --> CHECKOUT
CI_JOB --> TOOLCHAIN
CLIPPY_CHECK --> BUILD_STEP
FORMAT_CHECK --> CLIPPY_CHECK
TOOLCHAIN --> TARGET1
TOOLCHAIN --> TARGET2
TOOLCHAIN --> TARGET3
TOOLCHAIN --> TARGET4
TOOLCHAIN_SETUP --> FORMAT_CHECK
TRIGGER --> CI_JOB
TRIGGER --> DOC_JOB

Sources: .github/workflows/ci.yml(L1 - L31) 

Target Platform Categories

The build matrix validates compatibility across three categories of target platforms:

TargetArchitectureEnvironmentPurpose
x86_64-unknown-linux-gnux86_64Linux with stdTesting and validation
x86_64-unknown-nonex86_64Bare metalOS kernel development
riscv64gc-unknown-none-elfRISC-VBare metalEmbedded systems
aarch64-unknown-none-softfloatARM64Bare metalARM-based embedded

Sources: .github/workflows/ci.yml(L12) 

Quality Assurance Pipeline

The CI system implements a comprehensive quality assurance strategy that validates code formatting, linting, compilation, and functional correctness.

Code Quality Checks

The quality assurance pipeline executes the following checks in sequence:

flowchart TD
START["CI Job Start"]
VERSION_CHECK["rustc --version --verbose"]
FORMAT["cargo fmt --all -- --check"]
CLIPPY["cargo clippy --target TARGET --all-features"]
BUILD["cargo build --target TARGET --all-features"]
TEST_CONDITION["target == x86_64-unknown-linux-gnu?"]
UNIT_TEST["cargo test --target TARGET -- --nocapture"]
END["Job Complete"]
CLIPPY_CONFIG["clippy config:-A clippy::new_without_default"]

BUILD --> TEST_CONDITION
CLIPPY --> BUILD
CLIPPY --> CLIPPY_CONFIG
FORMAT --> CLIPPY
START --> VERSION_CHECK
TEST_CONDITION --> END
TEST_CONDITION --> UNIT_TEST
UNIT_TEST --> END
VERSION_CHECK --> FORMAT

Sources: .github/workflows/ci.yml(L20 - L30) 

Testing Strategy

Unit tests are executed only on the x86_64-unknown-linux-gnu target, which provides a standard library environment suitable for test execution. The testing configuration includes:

  • Test Command: cargo test --target x86_64-unknown-linux-gnu -- --nocapture
  • Output Mode: No capture mode for detailed test output
  • Conditional Execution: Only runs on Linux GNU target to avoid no_std test environment complications

Sources: .github/workflows/ci.yml(L28 - L30) 

Documentation Generation and Deployment

The CI pipeline includes a separate job dedicated to documentation generation and automated deployment to GitHub Pages.

Documentation Build Process

flowchart TD
subgraph subGraph2["Deploy Configuration"]
    BRANCH["gh-pages branch"]
    FOLDER["target/doc"]
    COMMIT_MODE["single-commit: true"]
end
subgraph subGraph1["Build Configuration"]
    RUSTDOCFLAGS["-D rustdoc::broken_intra_doc_links-D missing-docs"]
    PERMISSIONS["contents: write"]
end
subgraph subGraph0["Documentation Job"]
    DOC_TRIGGER["push/PR events"]
    DOC_CHECKOUT["actions/checkout@v4"]
    DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
    INDEX_GEN["Generate index.html redirect"]
    DEPLOY_CHECK["ref == default-branch?"]
    DEPLOY["JamesIves/github-pages-deploy-action@v4"]
end

DEPLOY --> BRANCH
DEPLOY --> COMMIT_MODE
DEPLOY --> FOLDER
DEPLOY_CHECK --> DEPLOY
DOC_BUILD --> INDEX_GEN
DOC_CHECKOUT --> DOC_TOOLCHAIN
DOC_TOOLCHAIN --> DOC_BUILD
DOC_TRIGGER --> DOC_CHECKOUT
INDEX_GEN --> DEPLOY_CHECK
PERMISSIONS --> DEPLOY
RUSTDOCFLAGS --> DOC_BUILD

Documentation Quality Enforcement

The documentation build process enforces strict quality standards through RUSTDOCFLAGS:

  • Broken Link Detection: -D rustdoc::broken_intra_doc_links treats broken documentation links as errors
  • Missing Documentation: -D missing-docs requires documentation for all public APIs

The index generation creates a redirect page using the crate name extracted from cargo tree output, providing seamless navigation to the main documentation.

Sources: .github/workflows/ci.yml(L32 - L55) 

Deployment Strategy

Documentation deployment follows a conditional strategy:

  • Automatic Deployment: Occurs only on pushes to the default branch
  • Target Branch: gh-pages branch for GitHub Pages hosting
  • Deployment Mode: Single commit to maintain clean history
  • Content Source: target/doc directory containing generated documentation

Sources: .github/workflows/ci.yml(L49 - L55) 

Project Configuration

Relevant source files

This document covers the development environment setup and project configuration files for the axio crate. It explains the structure and purpose of configuration files that control compilation, dependency management, and version control behavior.

For information about the build system and continuous integration, see Build System and CI. For details about feature gates and their impact on compilation, see Crate Configuration and Features.

Configuration File Structure

The axio project uses standard Rust project configuration with minimal additional setup requirements. The configuration is designed to support both standalone development and integration into larger systems like ArceOS.

Project Configuration Overview

flowchart TD
subgraph subGraph4["Ignored Files"]
    TARGET["/target"]
    VSCODE["/.vscode"]
    DSSTORE[".DS_Store"]
    LOCK["Cargo.lock"]
end
subgraph subGraph3["External Links"]
    REPO["repository URL"]
    DOCS["documentation URL"]
    HOME["homepage URL"]
end
subgraph subGraph2["Build Configuration"]
    FEATURES["Features Section"]
    DEPS["Dependencies Section"]
    DEFAULT["default = []"]
    ALLOC["alloc = []"]
end
subgraph subGraph1["Package Metadata"]
    NAME["name = axio"]
    VERSION["version = 0.1.1"]
    EDITION["edition = 2021"]
    DESC["description"]
    LICENSE["Triple License"]
end
subgraph subGraph0["Project Root"]
    CARGO["Cargo.tomlMain Configuration"]
    GIT[".gitignoreVersion Control"]
end

CARGO --> DEPS
CARGO --> FEATURES
CARGO --> NAME
CARGO --> REPO
CARGO --> VERSION
FEATURES --> ALLOC
FEATURES --> DEFAULT
GIT --> DSSTORE
GIT --> LOCK
GIT --> TARGET
GIT --> VSCODE

Sources: Cargo.toml(L1 - L20)  .gitignore(L1 - L5) 

Cargo.toml Configuration

The main project configuration is contained in Cargo.toml, which defines package metadata, dependencies, and feature flags.

Package Metadata

The package section defines essential project information and publishing configuration:

FieldValuePurpose
name"axio"Crate name for cargo registry
version"0.1.1"Semantic version number
edition"2021"Rust edition for language features
authors["Yuekai Jia equation618@gmail.com"]Primary maintainer
description"std::io-like I/O traits forno_stdenvironment"Brief functionality summary
license"GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"Triple licensing scheme

Sources: Cargo.toml(L1 - L12) 

The project maintains links to external resources:

  • Repository: https://github.com/arceos-org/axio - Source code location
  • Homepage: https://github.com/arceos-org/arceos - Parent project
  • Documentation: https://docs.rs/axio - Published API documentation

These links connect the crate to the broader ArceOS ecosystem while maintaining independent documentation.

Sources: Cargo.toml(L8 - L10) 

Feature Configuration

flowchart TD
subgraph subGraph2["Available Functionality"]
    BASIC["Basic I/O TraitsRead, Write, Seek"]
    DYNAMIC["Dynamic OperationsVec-based methods"]
end
subgraph subGraph1["Compilation Modes"]
    MINIMAL["Minimal BuildNo Features"]
    ENHANCED["Enhanced Build--features alloc"]
end
subgraph subGraph0["Feature Definition"]
    DEFAULT["default = []"]
    ALLOC["alloc = []"]
end

ALLOC --> ENHANCED
DEFAULT --> MINIMAL
ENHANCED --> BASIC
ENHANCED --> DYNAMIC
MINIMAL --> BASIC

Sources: Cargo.toml(L14 - L16) 

The feature configuration uses an intentionally minimal approach:

  • default = []: No features enabled by default, ensuring maximum compatibility
  • alloc = []: Empty feature flag that enables alloc crate integration through conditional compilation

This design allows the crate to function in the most constrained environments while providing enhanced functionality when allocation is available.

Dependencies

The dependency configuration maintains minimal external requirements:

flowchart TD
subgraph subGraph2["Functionality Provided"]
    ERROR_TYPES["Error TypesAxError, AxResult"]
    COLLECTIONS["CollectionsVec, String"]
end
subgraph subGraph1["Conditional Dependencies"]
    ALLOC_CRATE["alloc crateFeature-gated"]
end
subgraph subGraph0["Direct Dependencies"]
    AXERRNO["axerrno = 0.1Error Handling"]
end

ALLOC_CRATE --> COLLECTIONS
AXERRNO --> ERROR_TYPES

Sources: Cargo.toml(L18 - L19) 

The single required dependency axerrno = "0.1" provides standardized error types compatible with the ArceOS ecosystem. The alloc crate dependency is implicit and controlled through feature gates rather than explicit declaration.

Git Configuration

The .gitignore file excludes development artifacts and environment-specific files from version control.

Ignored File Categories

PatternTypeReason
/targetBuild artifactsGenerated by cargo build
/.vscodeEditor configurationVS Code specific settings
.DS_StoreSystem filesmacOS filesystem metadata
Cargo.lockDependency lockNot committed for libraries

Sources: .gitignore(L1 - L4) 

Version Control Strategy

flowchart TD
subgraph subGraph2["Git Repository"]
    REPO["Version Control"]
end
subgraph subGraph1["Ignored Files"]
    TARGET["Build Output/target"]
    EDITOR["Editor Config/.vscode"]
    SYSTEM["System Files.DS_Store"]
    LOCK["Lock FileCargo.lock"]
end
subgraph subGraph0["Tracked Files"]
    SOURCE["Source Codesrc/*.rs"]
    CONFIG["ConfigurationCargo.toml"]
    DOCS["DocumentationREADME.md"]
    CI["CI Configuration.github/"]
end

CI --> REPO
CONFIG --> REPO
DOCS --> REPO
EDITOR --> REPO
LOCK --> REPO
SOURCE --> REPO
SYSTEM --> REPO
TARGET --> REPO

Sources: .gitignore(L1 - L4) 

The exclusion of Cargo.lock follows Rust library conventions, allowing downstream consumers to resolve their own dependency versions. Build artifacts in /target are excluded to avoid repository bloat, while editor-specific configurations are ignored to support diverse development environments.

Development Environment Setup

The minimal configuration requirements enable straightforward development setup:

  1. Rust Toolchain: Edition 2021 or later required
  2. Feature Testing: Use cargo build --features alloc to test enhanced functionality
  3. Editor Support: Any Rust-compatible editor; VS Code configurations are gitignored
  4. Target Testing: Use cargo build --target <target> for cross-compilation verification

The configuration supports both local development and integration into larger build systems without requiring environment-specific modifications.

Sources: Cargo.toml(L1 - L20)  .gitignore(L1 - L5) 

Overview

Relevant source files

This document provides comprehensive documentation for the riscv_goldfish repository, a specialized Real Time Clock (RTC) driver crate designed for RISC-V systems running on the Goldfish platform. The repository implements a no_std compatible driver that provides Unix timestamp functionality through memory-mapped I/O operations.

The riscv_goldfish crate serves as a hardware abstraction layer between operating system components and Goldfish RTC hardware, enabling time management capabilities in embedded and bare-metal RISC-V environments. This driver is specifically designed for integration with the ArceOS operating system ecosystem but maintains compatibility across multiple target architectures.

For detailed API documentation and usage examples, see API Reference. For information about cross-platform compilation and target support, see Target Platforms and Cross-Compilation.

Repository Purpose and Scope

The riscv_goldfish crate provides a minimal, efficient RTC driver implementation with the following core responsibilities:

ComponentPurposeCode Entity
RTC Driver CoreHardware abstraction and timestamp managementRtcstruct
Memory InterfaceDirect hardware register accessMMIO operations
Time ConversionUnix timestamp to nanosecond conversionget_unix_timestamp(),set_unix_timestamp()
Platform IntegrationDevice tree compatibilityBase address configuration

The driver operates in a no_std environment, making it suitable for embedded systems, kernel-level components, and bare-metal applications across multiple architectures including RISC-V, x86_64, and ARM64.

Sources: Cargo.toml(L1 - L15)  README.md(L1 - L33) 

System Architecture

System Integration Architecture

flowchart TD
subgraph subGraph3["Hardware Layer"]
    GOLDFISH_RTC["Goldfish RTC Hardware"]
    RTC_REGS["RTC Registers"]
end
subgraph subGraph2["Hardware Abstraction"]
    MMIO["Memory-Mapped I/O"]
    DEVICE_TREE["Device Tree Configuration"]
    BASE_ADDR["base_addr: 0x101000"]
end
subgraph subGraph1["Driver Layer"]
    RTC_CRATE["riscv_goldfish crate"]
    RTC_STRUCT["Rtc struct"]
    API_NEW["Rtc::new(base_addr)"]
    API_GET["get_unix_timestamp()"]
    API_SET["set_unix_timestamp()"]
end
subgraph subGraph0["Application Layer"]
    APP["User Applications"]
    ARCEOS["ArceOS Operating System"]
end

API_GET --> MMIO
API_NEW --> MMIO
API_SET --> MMIO
APP --> ARCEOS
ARCEOS --> RTC_CRATE
BASE_ADDR --> API_NEW
DEVICE_TREE --> BASE_ADDR
MMIO --> RTC_REGS
RTC_CRATE --> RTC_STRUCT
RTC_REGS --> GOLDFISH_RTC
RTC_STRUCT --> API_GET
RTC_STRUCT --> API_NEW
RTC_STRUCT --> API_SET

This architecture demonstrates the layered approach from applications down to hardware, with the Rtc struct serving as the primary interface point. The base_addr parameter from device tree configuration initializes the driver through the Rtc::new() constructor.

Sources: README.md(L10 - L12)  README.md(L24 - L28)  Cargo.toml(L6) 

Core Components and Data Flow

RTC Driver Component Mapping

flowchart TD
subgraph subGraph4["Data Types"]
    UNIX_TIME["Unix timestamp (u64)"]
    NANOSECONDS["Nanoseconds (u64)"]
    NSEC_PER_SEC["NSEC_PER_SEC constant"]
end
subgraph subGraph3["Hardware Interface"]
    MMIO_OPS["MMIO Operations"]
    RTC_TIME_LOW["RTC_TIME_LOW register"]
    RTC_TIME_HIGH["RTC_TIME_HIGH register"]
end
subgraph subGraph2["Public API Methods"]
    NEW_METHOD["new(base_addr: usize)"]
    GET_TIMESTAMP["get_unix_timestamp()"]
    SET_TIMESTAMP["set_unix_timestamp()"]
end
subgraph subGraph1["Core Structures"]
    RTC_STRUCT["Rtc struct"]
    BASE_ADDR_FIELD["base_addr field"]
end
subgraph subGraph0["Source Files"]
    LIB_RS["src/lib.rs"]
    CARGO_TOML["Cargo.toml"]
end

CARGO_TOML --> LIB_RS
GET_TIMESTAMP --> MMIO_OPS
GET_TIMESTAMP --> UNIX_TIME
LIB_RS --> RTC_STRUCT
MMIO_OPS --> RTC_TIME_HIGH
MMIO_OPS --> RTC_TIME_LOW
NANOSECONDS --> NSEC_PER_SEC
NEW_METHOD --> BASE_ADDR_FIELD
RTC_STRUCT --> BASE_ADDR_FIELD
RTC_STRUCT --> GET_TIMESTAMP
RTC_STRUCT --> NEW_METHOD
RTC_STRUCT --> SET_TIMESTAMP
SET_TIMESTAMP --> MMIO_OPS
SET_TIMESTAMP --> UNIX_TIME
UNIX_TIME --> NANOSECONDS

The driver implements a clean separation between the public API surface and hardware-specific operations. The Rtc struct encapsulates the base address and provides methods that handle the conversion between Unix timestamps and hardware nanosecond representations through direct register manipulation.

Sources: README.md(L10 - L12)  Cargo.toml(L2) 

Target Platform Support

The crate supports multiple target architectures through its no_std design:

Target ArchitecturePurposeCompatibility
riscv64gc-unknown-none-elfPrimary RISC-V targetBare metal, embedded
x86_64-unknown-linux-gnuDevelopment and testingLinux userspace
x86_64-unknown-noneBare metal x86_64Kernel, bootloader
aarch64-unknown-none-softfloatARM64 embeddedBare metal ARM

The driver maintains cross-platform compatibility while providing hardware-specific optimizations for the Goldfish RTC implementation. The no-std category classification enables deployment in resource-constrained environments typical of embedded systems.

Sources: Cargo.toml(L12)  Cargo.toml(L6) 

Integration with ArceOS Ecosystem

The riscv_goldfish crate is designed as a component within the broader ArceOS operating system project. The driver provides essential time management capabilities required by kernel-level services and system calls. The crate's licensing scheme supports integration with both open-source and commercial projects through its triple license approach (GPL-3.0, Apache-2.0, MulanPSL-2.0).

Device tree integration follows standard practices, with the driver expecting a compatible string of "google,goldfish-rtc" and memory-mapped register access at the specified base address. This standardized approach ensures compatibility across different RISC-V platform implementations that include Goldfish RTC hardware.

Sources: Cargo.toml(L7 - L8)  Cargo.toml(L11)  README.md(L24 - L28) 

Architecture Overview

Relevant source files

This document provides a high-level view of the riscv_goldfish system architecture, showing how the RTC driver components integrate from the hardware layer up to application interfaces. It covers the overall system stack, component relationships, and data flow patterns without diving into implementation details.

For detailed API documentation, see API Reference. For hardware register specifics, see Hardware Interface. For time conversion implementation details, see Time Conversion.

System Stack Architecture

The riscv_goldfish driver operates within a layered architecture that spans from hardware registers to application-level time services:

flowchart TD
subgraph subGraph4["Hardware Platform"]
    GOLDFISH_HW["Goldfish RTC Hardware"]
end
subgraph subGraph3["Hardware Registers"]
    RTC_TIME_LOW_REG["RTC_TIME_LOW (0x00)"]
    RTC_TIME_HIGH_REG["RTC_TIME_HIGH (0x04)"]
end
subgraph subGraph2["Hardware Abstraction"]
    MMIO_READ["read()"]
    MMIO_WRITE["write()"]
    BASE_ADDR["base_address field"]
end
subgraph subGraph1["Driver Layer (src/lib.rs)"]
    RTC_STRUCT["Rtc struct"]
    API_NEW["new()"]
    API_GET["get_unix_timestamp()"]
    API_SET["set_unix_timestamp()"]
end
subgraph subGraph0["Application Layer"]
    APP["Application Code"]
    OS["ArceOS Operating System"]
end

API_GET --> MMIO_READ
API_SET --> MMIO_WRITE
APP --> OS
MMIO_READ --> BASE_ADDR
MMIO_READ --> RTC_TIME_HIGH_REG
MMIO_READ --> RTC_TIME_LOW_REG
MMIO_WRITE --> BASE_ADDR
MMIO_WRITE --> RTC_TIME_HIGH_REG
MMIO_WRITE --> RTC_TIME_LOW_REG
OS --> RTC_STRUCT
RTC_STRUCT --> API_GET
RTC_STRUCT --> API_NEW
RTC_STRUCT --> API_SET
RTC_TIME_HIGH_REG --> GOLDFISH_HW
RTC_TIME_LOW_REG --> GOLDFISH_HW

This architecture demonstrates the clear separation of concerns between application logic, driver abstraction, hardware interface, and physical hardware. The Rtc struct serves as the primary abstraction boundary, providing a safe interface to unsafe hardware operations.

Sources: src/lib.rs(L11 - L14)  src/lib.rs(L26 - L33)  src/lib.rs(L35 - L49) 

Component Architecture

The core driver architecture centers around the Rtc struct and its associated methods:

flowchart TD
subgraph subGraph3["Hardware Interface"]
    READ_VOLATILE["core::ptr::read_volatile"]
    WRITE_VOLATILE["core::ptr::write_volatile"]
end
subgraph Constants["Constants"]
    RTC_TIME_LOW_CONST["RTC_TIME_LOW = 0x00"]
    RTC_TIME_HIGH_CONST["RTC_TIME_HIGH = 0x04"]
    NSEC_PER_SEC_CONST["NSEC_PER_SEC = 1_000_000_000"]
end
subgraph subGraph1["Private Implementation"]
    READ_METHOD["read(reg: usize) -> u32"]
    WRITE_METHOD["write(reg: usize, value: u32)"]
    BASE_FIELD["base_address: usize"]
end
subgraph subGraph0["Public API (src/lib.rs)"]
    RTC["Rtc"]
    NEW["new(base_address: usize)"]
    GET_TS["get_unix_timestamp() -> u64"]
    SET_TS["set_unix_timestamp(unix_time: u64)"]
end

GET_TS --> NSEC_PER_SEC_CONST
GET_TS --> READ_METHOD
NEW --> BASE_FIELD
READ_METHOD --> BASE_FIELD
READ_METHOD --> READ_VOLATILE
READ_METHOD --> RTC_TIME_HIGH_CONST
READ_METHOD --> RTC_TIME_LOW_CONST
SET_TS --> NSEC_PER_SEC_CONST
SET_TS --> WRITE_METHOD
WRITE_METHOD --> BASE_FIELD
WRITE_METHOD --> RTC_TIME_HIGH_CONST
WRITE_METHOD --> RTC_TIME_LOW_CONST
WRITE_METHOD --> WRITE_VOLATILE

The component design follows a minimal surface area principle with only three public methods exposed. The private read and write methods encapsulate all unsafe hardware interactions, while constants define the register layout and time conversion factors.

Sources: src/lib.rs(L6 - L9)  src/lib.rs(L12 - L14)  src/lib.rs(L17 - L23)  src/lib.rs(L27 - L49) 

Data Flow Architecture

The driver implements bidirectional data flow between Unix timestamps and hardware nanosecond values:

flowchart TD
subgraph subGraph2["Hardware Layer"]
    HW_REGISTERS["64-bit nanosecond counter"]
end
subgraph subGraph1["Write Path (set_unix_timestamp)"]
    WRITE_START["set_unix_timestamp(unix_time)"]
    CONVERT_TO_NS["unix_time * NSEC_PER_SEC"]
    SPLIT_HIGH["(time_nanos >> 32) as u32"]
    SPLIT_LOW["time_nanos as u32"]
    WRITE_HIGH["write(RTC_TIME_HIGH, high)"]
    WRITE_LOW["write(RTC_TIME_LOW, low)"]
end
subgraph subGraph0["Read Path (get_unix_timestamp)"]
    READ_START["get_unix_timestamp()"]
    READ_LOW["read(RTC_TIME_LOW) -> u32"]
    READ_HIGH["read(RTC_TIME_HIGH) -> u32"]
    COMBINE["(high << 32) | low"]
    CONVERT_TO_SEC["result / NSEC_PER_SEC"]
    READ_RESULT["return u64"]
end

COMBINE --> CONVERT_TO_SEC
CONVERT_TO_NS --> SPLIT_HIGH
CONVERT_TO_NS --> SPLIT_LOW
CONVERT_TO_SEC --> READ_RESULT
READ_HIGH --> COMBINE
READ_HIGH --> HW_REGISTERS
READ_LOW --> COMBINE
READ_LOW --> HW_REGISTERS
READ_START --> READ_HIGH
READ_START --> READ_LOW
SPLIT_HIGH --> WRITE_HIGH
SPLIT_LOW --> WRITE_LOW
WRITE_HIGH --> HW_REGISTERS
WRITE_LOW --> HW_REGISTERS
WRITE_START --> CONVERT_TO_NS

The data flow demonstrates the critical conversion between user-space Unix timestamps (seconds) and hardware nanosecond representation. The 64-bit hardware value must be split across two 32-bit registers for write operations and reconstructed for read operations.

Sources: src/lib.rs(L36 - L40)  src/lib.rs(L43 - L49)  src/lib.rs(L9) 

Integration Architecture

The driver integrates within the broader ArceOS ecosystem and cross-platform build system:

flowchart TD
subgraph subGraph3["Driver Core"]
    RTC_DRIVER["riscv_goldfish::Rtc"]
end
subgraph subGraph2["Platform Integration"]
    DEVICE_TREE["Device Tree (base_addr: 0x101000)"]
    ARCEOS_OS["ArceOS"]
    COMPATIBLE["google,goldfish-rtc"]
end
subgraph subGraph1["Crate Features"]
    NO_STD["#![no_std]"]
    CARGO_TOML["Cargo.toml metadata"]
end
subgraph subGraph0["Build Targets"]
    X86_64_LINUX["x86_64-unknown-linux-gnu"]
    X86_64_NONE["x86_64-unknown-none"]
    RISCV64["riscv64gc-unknown-none-elf"]
    AARCH64["aarch64-unknown-none-softfloat"]
end

ARCEOS_OS --> RTC_DRIVER
CARGO_TOML --> NO_STD
COMPATIBLE --> RTC_DRIVER
DEVICE_TREE --> RTC_DRIVER
NO_STD --> AARCH64
NO_STD --> RISCV64
NO_STD --> X86_64_LINUX
NO_STD --> X86_64_NONE
RTC_DRIVER --> AARCH64
RTC_DRIVER --> RISCV64
RTC_DRIVER --> X86_64_LINUX
RTC_DRIVER --> X86_64_NONE

The integration architecture shows how the no_std design enables cross-compilation to multiple targets while maintaining compatibility with the ArceOS operating system. Device tree integration provides the necessary base_address configuration for hardware discovery.

Sources: src/lib.rs(L4)  README.md(L15 - L32)  README.md(L5)  README.md(L10 - L13) 

RTC Driver Implementation

Relevant source files

This document covers the core implementation details of the RISC-V Goldfish RTC driver, including the main Rtc struct, its methods, and the underlying hardware abstraction mechanisms. The implementation provides a clean interface for reading and writing Unix timestamps to Goldfish RTC hardware through memory-mapped I/O operations.

For complete API documentation and usage examples, see API Reference. For detailed hardware register mapping and MMIO specifics, see Hardware Interface. For in-depth time conversion algorithms, see Time Conversion.

Core Driver Structure

The RTC driver is implemented as a single Rtc struct that encapsulates all functionality for interfacing with the Goldfish RTC hardware. The driver follows a minimalist design pattern suitable for embedded and bare-metal environments.

flowchart TD
subgraph subGraph2["Hardware Constants"]
    RtcTimeLow["RTC_TIME_LOW = 0x00"]
    RtcTimeHigh["RTC_TIME_HIGH = 0x04"]
    NsecPerSec["NSEC_PER_SEC = 1_000_000_000"]
end
subgraph subGraph1["Internal Methods"]
    ReadMethod["read(reg: usize) -> u32"]
    WriteMethod["write(reg: usize, value: u32)"]
end
subgraph subGraph0["Rtc Struct Implementation"]
    RtcStruct["Rtc { base_address: usize }"]
    NewMethod["new(base_address: usize) -> Self"]
    GetMethod["get_unix_timestamp() -> u64"]
    SetMethod["set_unix_timestamp(unix_time: u64)"]
end

GetMethod --> NsecPerSec
GetMethod --> ReadMethod
NewMethod --> RtcStruct
ReadMethod --> RtcTimeHigh
ReadMethod --> RtcTimeLow
SetMethod --> NsecPerSec
SetMethod --> WriteMethod
WriteMethod --> RtcTimeHigh
WriteMethod --> RtcTimeLow

Sources: src/lib.rs(L11 - L50) 

Memory-Mapped I/O Layer

The driver implements a low-level hardware abstraction through unsafe memory operations. All hardware access is channeled through two core methods that handle volatile memory operations to prevent compiler optimizations from interfering with hardware communication.

MethodPurposeSafetyReturn Type
read(reg: usize)Read 32-bit value from hardware registerUnsafe - requires valid base addressu32
write(reg: usize, value: u32)Write 32-bit value to hardware registerUnsafe - requires valid base address()
flowchart TD
subgraph subGraph3["Physical Hardware"]
    RtcHardware["Goldfish RTC Registers"]
end
subgraph subGraph2["Hardware Layer"]
    VolatileRead["core::ptr::read_volatile()"]
    VolatileWrite["core::ptr::write_volatile()"]
    BaseAddr["(base_address + reg) as *const/*mut u32"]
end
subgraph subGraph1["Abstraction Layer"]
    ReadReg["read(reg)"]
    WriteReg["write(reg, value)"]
end
subgraph subGraph0["Software Layer"]
    GetTimestamp["get_unix_timestamp()"]
    SetTimestamp["set_unix_timestamp()"]
end

BaseAddr --> RtcHardware
GetTimestamp --> ReadReg
ReadReg --> VolatileRead
SetTimestamp --> WriteReg
VolatileRead --> BaseAddr
VolatileWrite --> BaseAddr
WriteReg --> VolatileWrite

Sources: src/lib.rs(L17 - L24) 

Driver Initialization and Configuration

The Rtc::new() constructor provides the single entry point for driver instantiation. The method requires a base memory address that corresponds to the hardware device's memory-mapped location, typically obtained from device tree parsing during system initialization.

flowchart TD
subgraph subGraph1["Usage Pattern"]
    CreateInstance["let rtc = Rtc::new(0x101000)"]
    GetTime["rtc.get_unix_timestamp()"]
    SetTime["rtc.set_unix_timestamp(epoch)"]
end
subgraph subGraph0["Initialization Flow"]
    DeviceTree["Device Treertc@101000"]
    BaseAddr["base_addr = 0x101000"]
    NewCall["Rtc::new(base_addr)"]
    RtcInstance["Rtc { base_address: 0x101000 }"]
end

BaseAddr --> NewCall
CreateInstance --> GetTime
CreateInstance --> SetTime
DeviceTree --> BaseAddr
NewCall --> RtcInstance
RtcInstance --> CreateInstance

The constructor performs no hardware validation or initialization - it simply stores the provided base address for future memory operations. This design assumes that hardware initialization and memory mapping have been handled by the system's device management layer.

Sources: src/lib.rs(L27 - L33)  README.md(L10 - L16) 

64-bit Register Handling

The Goldfish RTC hardware stores time as a 64-bit nanosecond value split across two consecutive 32-bit registers. The driver handles this split through careful register sequencing and bit manipulation to maintain data integrity during multi-register operations.

flowchart TD
subgraph subGraph1["64-bit Write Operation"]
    subgraph subGraph0["64-bit Read Operation"]
        InputUnix["unix_time: u64"]
        ConvertNanos["unix_time * NSEC_PER_SEC"]
        SplitHigh["(time_nanos >> 32) as u32"]
        SplitLow["time_nanos as u32"]
        WriteHigh["write(RTC_TIME_HIGH, high)"]
        WriteLow["write(RTC_TIME_LOW, low)"]
        ReadLow["read(RTC_TIME_LOW) -> u32"]
        CombineBits["(high << 32) | low"]
        ReadHigh["read(RTC_TIME_HIGH) -> u32"]
        ConvertTime["nanoseconds / NSEC_PER_SEC"]
        ReturnUnix["return unix_timestamp"]
    end
end

CombineBits --> ConvertTime
ConvertNanos --> SplitHigh
ConvertNanos --> SplitLow
ConvertTime --> ReturnUnix
InputUnix --> ConvertNanos
ReadHigh --> CombineBits
ReadLow --> CombineBits
SplitHigh --> WriteHigh
SplitLow --> WriteLow

The write operation follows a specific sequence where the high register is written first, followed by the low register. This ordering ensures atomic updates from the hardware perspective and prevents temporal inconsistencies during timestamp updates.

Sources: src/lib.rs(L36 - L49) 

Error Handling and Safety Model

The driver implementation uses Rust's unsafe blocks to perform direct memory access while maintaining memory safety through controlled access patterns. The safety model relies on several assumptions:

  • The provided base_address points to valid, mapped memory
  • The memory region corresponds to actual Goldfish RTC hardware
  • Concurrent access is managed by the calling code
  • The hardware registers maintain their expected layout
flowchart TD
subgraph subGraph1["Caller Responsibilities"]
    ValidAddr["Provide valid base_address"]
    MemoryMap["Ensure memory mapping"]
    Concurrency["Handle concurrent access"]
end
subgraph subGraph0["Safety Boundaries"]
    SafeAPI["Safe Public API"]
    UnsafeInternal["Unsafe Internal Methods"]
    HardwareLayer["Hardware Memory"]
end

Concurrency --> SafeAPI
MemoryMap --> SafeAPI
SafeAPI --> UnsafeInternal
UnsafeInternal --> HardwareLayer
ValidAddr --> SafeAPI

The driver does not perform runtime validation of memory addresses or hardware responses, prioritizing performance and simplicity for embedded use cases where system-level guarantees about hardware presence and memory mapping are typically established during boot.

Sources: src/lib.rs(L17 - L24) 

API Reference

Relevant source files

This document provides comprehensive reference documentation for the public API of the riscv_goldfish crate. The API consists of a single Rtc struct that provides a safe, high-level interface to the Goldfish RTC hardware through memory-mapped I/O operations.

For hardware-specific implementation details and register layouts, see Hardware Interface. For time conversion algorithms and data flow, see Time Conversion.

Public API Overview

The riscv_goldfish crate exposes a minimal but complete API through the Rtc struct and its associated methods. The API follows standard Rust conventions for hardware driver interfaces.

API Structure Diagram

flowchart TD
subgraph subGraph2["Hardware Constants"]
    RtcTimeLow["RTC_TIME_LOW: 0x00"]
    RtcTimeHigh["RTC_TIME_HIGH: 0x04"]
    NsecPerSec["NSEC_PER_SEC: 1_000_000_000"]
end
subgraph subGraph1["Private Implementation"]
    ReadMethod["read(reg: usize) -> u32"]
    WriteMethod["write(reg: usize, value: u32)"]
    BaseAddr["base_address: usize"]
end
subgraph subGraph0["Public API"]
    RtcStruct["Rtc struct"]
    NewMethod["new(base_address: usize) -> Self"]
    GetMethod["get_unix_timestamp() -> u64"]
    SetMethod["set_unix_timestamp(unix_time: u64)"]
end

GetMethod --> NsecPerSec
GetMethod --> ReadMethod
NewMethod --> RtcStruct
ReadMethod --> RtcTimeHigh
ReadMethod --> RtcTimeLow
RtcStruct --> BaseAddr
SetMethod --> NsecPerSec
SetMethod --> WriteMethod
WriteMethod --> RtcTimeHigh
WriteMethod --> RtcTimeLow

Sources: src/lib.rs(L11 - L50) 

Constructor

Rtc::new

#![allow(unused)]
fn main() {
pub fn new(base_address: usize) -> Self
}

Creates a new Rtc instance that interfaces with Goldfish RTC hardware at the specified memory address.

Parameters:

  • base_address: The physical base address of the RTC device's memory-mapped registers, typically obtained from device tree configuration

Returns:

  • A new Rtc instance ready for timestamp operations

Safety: The constructor itself is safe, but subsequent operations assume the provided base_address points to valid, accessible RTC hardware registers. Invalid addresses will cause undefined behavior during read/write operations.

Example:

use riscv_goldfish::Rtc;

// Address from device tree: rtc@101000
let rtc = Rtc::new(0x101000);

Sources: src/lib.rs(L27 - L33)  README.md(L12)  README.md(L24 - L29) 

Timestamp Operations

get_unix_timestamp

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

Reads the current time from the RTC hardware and returns it as seconds since the Unix epoch (January 1, 1970, 00:00:00 UTC).

Returns:

  • Current time as Unix timestamp in seconds

Implementation Details: The method performs two 32-bit register reads to construct a 64-bit nanosecond value, then converts to seconds by dividing by NSEC_PER_SEC.

flowchart TD
ReadLow["read(RTC_TIME_LOW)"]
CombineRegs["Combine 32-bit registers"]
ReadHigh["read(RTC_TIME_HIGH)"]
Nanoseconds["64-bit nanoseconds"]
DivideBy["÷ NSEC_PER_SEC"]
UnixTime["Unix timestamp (seconds)"]

CombineRegs --> Nanoseconds
DivideBy --> UnixTime
Nanoseconds --> DivideBy
ReadHigh --> CombineRegs
ReadLow --> CombineRegs

Sources: src/lib.rs(L35 - L40) 

set_unix_timestamp

#![allow(unused)]
fn main() {
pub fn set_unix_timestamp(&self, unix_time: u64)
}

Sets the RTC hardware clock to the specified Unix timestamp.

Parameters:

  • unix_time: Time in seconds since Unix epoch

Implementation Details: The method converts the Unix timestamp to nanoseconds, then writes the value as two 32-bit register operations. The high-order register is written first, followed by the low-order register.

flowchart TD
UnixInput["unix_time (seconds)"]
MultiplyBy["× NSEC_PER_SEC"]
Nanoseconds["64-bit nanoseconds"]
SplitRegs["Split into 32-bit values"]
WriteHigh["write(RTC_TIME_HIGH, high)"]
WriteLow["write(RTC_TIME_LOW, low)"]

MultiplyBy --> Nanoseconds
Nanoseconds --> SplitRegs
SplitRegs --> WriteHigh
SplitRegs --> WriteLow
UnixInput --> MultiplyBy

Sources: src/lib.rs(L42 - L49) 

Usage Patterns

Basic Time Reading

use riscv_goldfish::Rtc;

let rtc = Rtc::new(0x101000);
let current_time = rtc.get_unix_timestamp();
println!("Current time: {} seconds since epoch", current_time);

Time Setting and Verification

use riscv_goldfish::Rtc;

let rtc = Rtc::new(0x101000);

// Set time to a specific timestamp
let target_time = 1640995200; // January 1, 2022, 00:00:00 UTC
rtc.set_unix_timestamp(target_time);

// Verify the time was set correctly
let actual_time = rtc.get_unix_timestamp();
assert_eq!(actual_time, target_time);

Device Tree Integration

The base address parameter is typically obtained from device tree parsing:

PropertyValueDescription
compatible"google,goldfish-rtc"Device identification
reg<0x00 0x101000 0x00 0x1000>Base address and size
interrupts<0x0b>Interrupt number (unused by driver)

Sources: README.md(L15 - L32) 

Constants and Internal Implementation

Hardware Register Offsets

ConstantValuePurpose
RTC_TIME_LOW0x00Lower 32 bits of nanosecond timestamp
RTC_TIME_HIGH0x04Upper 32 bits of nanosecond timestamp

Time Conversion

ConstantValuePurpose
NSEC_PER_SEC1_000_000_000Nanoseconds per second conversion factor

Memory-Mapped I/O Operations

The driver uses unsafe volatile operations for hardware access:

MethodPurposeSafety Requirements
read(reg: usize)Read 32-bit register valueValid base address + register offset
write(reg: usize, value: u32)Write 32-bit register valueValid base address + register offset

Sources: src/lib.rs(L6 - L9)  src/lib.rs(L17 - L24) 

API Constraints and Limitations

Thread Safety

The Rtc struct does not implement Send or Sync traits. Multiple threads accessing the same RTC instance require external synchronization.

Error Handling

The API does not return Result types. Invalid memory addresses or hardware failures will cause undefined behavior rather than recoverable errors.

Platform Requirements

  • Requires no_std environment compatibility
  • Assumes little-endian byte ordering for register operations
  • Requires unsafe memory access capabilities

Sources: src/lib.rs(L4)  src/lib.rs(L17 - L23) 

Hardware Interface

Relevant source files

This document covers the low-level hardware interface implementation of the riscv_goldfish RTC driver, focusing on memory-mapped I/O operations, register layout, and the abstraction layer between software and the Goldfish RTC hardware. For higher-level API usage patterns, see API Reference. For time conversion logic, see Time Conversion.

Register Layout and Memory Mapping

The Goldfish RTC hardware exposes a simple memory-mapped interface consisting of two 32-bit registers that together form a 64-bit nanosecond timestamp counter. The driver defines the register offsets as compile-time constants and uses a base address provided during initialization.

Register Map

OffsetRegister NameWidthPurpose
0x00RTC_TIME_LOW32-bitLower 32 bits of nanosecond timestamp
0x04RTC_TIME_HIGH32-bitUpper 32 bits of nanosecond timestamp

Memory Address Calculation

flowchart TD
BaseAddr["base_address"]
LowAddr["Low Register Address"]
HighAddr["High Register Address"]
LowReg["RTC_TIME_LOW Register"]
HighReg["RTC_TIME_HIGH Register"]
NS64["64-bit Nanosecond Value"]

BaseAddr --> HighAddr
BaseAddr --> LowAddr
HighAddr --> HighReg
HighReg --> NS64
LowAddr --> LowReg
LowReg --> NS64

The driver calculates absolute register addresses by adding the register offset constants to the base address provided during Rtc::new() construction.

Sources: src/lib.rs(L6 - L7)  src/lib.rs(L31 - L33) 

Memory-Mapped I/O Operations

The hardware interface implements volatile memory operations to ensure proper communication with the RTC hardware. The driver provides two core memory access functions that handle the low-level register reads and writes.

Volatile Memory Access Functions

flowchart TD
ReadOp["read(reg: usize)"]
AddrCalc["Address Calculation"]
WriteOp["write(reg: usize, value: u32)"]
ReadPtr["Read Pointer Cast"]
WritePtr["Write Pointer Cast"]
HardwareRead["Hardware Register Read"]
HardwareWrite["Hardware Register Write"]
ReadResult["Return Value"]
WriteResult["Write Complete"]

AddrCalc --> ReadPtr
AddrCalc --> WritePtr
HardwareRead --> ReadResult
HardwareWrite --> WriteResult
ReadOp --> AddrCalc
ReadPtr --> HardwareRead
WriteOp --> AddrCalc
WritePtr --> HardwareWrite

The read() function performs volatile reads to prevent compiler optimizations that might cache register values, while write() ensures immediate hardware updates. Both operations are marked unsafe due to raw pointer manipulation.

Sources: src/lib.rs(L17 - L23) 

64-bit Value Handling Across 32-bit Registers

The Goldfish RTC hardware stores nanosecond timestamps as a 64-bit value, but exposes this through two separate 32-bit registers. The driver implements bidirectional conversion between the unified 64-bit representation and the split register format.

Read Operation Data Flow

flowchart TD
subgraph subGraph2["Value Reconstruction"]
    Combine["(high << 32) | low"]
    UnixTime["Unix Timestamp"]
end
subgraph subGraph1["Driver Read Operations"]
    ReadLow["read(RTC_TIME_LOW)"]
    LowU64["low: u64"]
    ReadHigh["read(RTC_TIME_HIGH)"]
    HighU64["high: u64"]
end
subgraph subGraph0["Hardware Registers"]
    LowHW["RTC_TIME_LOW32-bit hardware"]
    HighHW["RTC_TIME_HIGH32-bit hardware"]
end

Combine --> UnixTime
HighHW --> ReadHigh
HighU64 --> Combine
LowHW --> ReadLow
LowU64 --> Combine
ReadHigh --> HighU64
ReadLow --> LowU64

Write Operation Data Flow

flowchart TD
subgraph subGraph3["Hardware Registers"]
    HighHWOut["RTC_TIME_HIGHupdated"]
    LowHWOut["RTC_TIME_LOWupdated"]
end
subgraph subGraph2["Hardware Write"]
    WriteHigh["write(RTC_TIME_HIGH, high)"]
    WriteLow["write(RTC_TIME_LOW, low)"]
end
subgraph subGraph1["Value Splitting"]
    HighPart["High 32 bits"]
    LowPart["Low 32 bits"]
end
subgraph subGraph0["Input Processing"]
    UnixIn["unix_time: u64"]
    Nanos["time_nanos: u64"]
end

HighPart --> WriteHigh
LowPart --> WriteLow
Nanos --> HighPart
Nanos --> LowPart
UnixIn --> Nanos
WriteHigh --> HighHWOut
WriteLow --> LowHWOut

The write operation updates the high register first, then the low register, to maintain temporal consistency during the split-register update sequence.

Sources: src/lib.rs(L36 - L40)  src/lib.rs(L43 - L49) 

Hardware Abstraction Layer

The Rtc struct provides a clean abstraction over the raw hardware interface, encapsulating the base address and providing safe access patterns for register operations.

Driver Structure and Hardware Mapping

flowchart TD
subgraph subGraph2["Physical Hardware"]
    GoldfishRTC["Goldfish RTC Device64-bit nanosecond counter"]
end
subgraph subGraph1["Hardware Abstraction"]
    BaseAddr["base_address field"]
    MemoryMap["Memory Mapping"]
    RegOffsets["RTC_TIME_LOWRTC_TIME_HIGH"]
end
subgraph subGraph0["Software Layer"]
    RtcStruct["Rtc { base_address: usize }"]
    PublicAPI["get_unix_timestamp()set_unix_timestamp()"]
    PrivateOps["read(reg)write(reg, value)"]
end

BaseAddr --> MemoryMap
MemoryMap --> GoldfishRTC
PrivateOps --> MemoryMap
PublicAPI --> PrivateOps
RegOffsets --> MemoryMap
RtcStruct --> PublicAPI

The abstraction separates hardware-specific details (register offsets, volatile operations) from the public API, allowing safe high-level operations while maintaining direct hardware access efficiency.

Sources: src/lib.rs(L11 - L14)  src/lib.rs(L26 - L50) 

Safety Considerations

The hardware interface implements several safety measures to ensure reliable operation:

  • Volatile Operations: All register access uses read_volatile() and write_volatile() to prevent compiler optimizations that could interfere with hardware communication
  • Unsafe Boundaries: Raw pointer operations are confined to private methods, exposing only safe public APIs
  • Type Safety: Register values are properly cast between u32 hardware format and u64 application format
  • Address Validation: Base address is stored during construction and used consistently for all register calculations

The driver assumes the provided base address corresponds to valid, mapped Goldfish RTC hardware, typically obtained from device tree parsing during system initialization.

Sources: src/lib.rs(L17 - L23)  src/lib.rs(L31 - L33) 

Time Conversion

Relevant source files

This document explains the time conversion logic implemented in the RTC driver, covering the transformation between Unix timestamps (seconds) and hardware nanosecond representations. The conversion process handles bidirectional data flow between software time formats and the Goldfish RTC hardware's internal 64-bit nanosecond counter.

For details about the hardware register interface used in these conversions, see Hardware Interface. For the complete public API that exposes these conversion functions, see API Reference.

Time Representation Formats

The RTC driver handles two distinct time representation formats that require conversion between software and hardware layers.

Unix Timestamp Format

The driver's public API operates using Unix timestamps expressed as u64 values representing seconds since the Unix epoch (January 1, 1970, 00:00:00 UTC). This format provides a standard interface for operating system integration and application compatibility.

Hardware Nanosecond Format

The Goldfish RTC hardware internally maintains time as a 64-bit counter representing nanoseconds since the Unix epoch. This high-resolution format is stored across two 32-bit memory-mapped registers: RTC_TIME_HIGH and RTC_TIME_LOW.

Sources: src/lib.rs(L6 - L7)  src/lib.rs(L35 - L39)  src/lib.rs(L42 - L44) 

Conversion Constants

The time conversion relies on a single fundamental constant that defines the relationship between seconds and nanoseconds.

ConstantValuePurpose
NSEC_PER_SEC1_000_000_000Nanoseconds per second conversion factor

This constant enables precise conversion between the second-granularity Unix timestamps and the nanosecond-granularity hardware representation without loss of precision in the direction from seconds to nanoseconds.

Sources: src/lib.rs(L9) 

Time Conversion Data Flow

Reading Time from Hardware

flowchart TD
A["RTC_TIME_LOW register"]
C["Combine into 64-bit value"]
B["RTC_TIME_HIGH register"]
D["((high << 32) | low)"]
E["Divide by NSEC_PER_SEC"]
F["Unix timestamp (seconds)"]
G["64-bit nanoseconds"]

A --> C
B --> C
C --> D
C --> G
D --> E
E --> F
G --> E

Reading Time Conversion Process

The get_unix_timestamp() function implements a multi-step process to extract time from hardware registers and convert to Unix timestamp format.

Sources: src/lib.rs(L36 - L40) 

Writing Time to Hardware

flowchart TD
A["Unix timestamp (seconds)"]
B["Multiply by NSEC_PER_SEC"]
C["64-bit nanoseconds"]
D["Split into 32-bit values"]
E["time_nanos >> 32"]
F["time_nanos as u32"]
G["Write to RTC_TIME_HIGH"]
H["Write to RTC_TIME_LOW"]

A --> B
B --> C
C --> D
D --> E
D --> F
E --> G
F --> H

Writing Time Conversion Process

The set_unix_timestamp() function performs the inverse conversion, transforming Unix timestamps into the hardware's nanosecond format and distributing the value across two registers.

Sources: src/lib.rs(L43 - L49) 

Bit Manipulation and Register Mapping

The conversion process requires careful handling of 64-bit values across 32-bit register boundaries, involving specific bit manipulation operations.

flowchart TD
subgraph subGraph1["Hardware Registers"]
    RH["RTC_TIME_HIGH0x04"]
    RL["RTC_TIME_LOW0x00"]
end
subgraph subGraph0["64-bit Nanosecond Value"]
    H["High 32 bits[63:32]"]
    L["Low 32 bits[31:0]"]
end
subgraph subGraph3["Write Operations"]
    W4["write(RTC_TIME_LOW, low)"]
    subgraph subGraph2["Read Operations"]
        W1["time_nanos >> 32"]
        W2["time_nanos as u32"]
        W3["write(RTC_TIME_HIGH, high)"]
        R1["read(RTC_TIME_LOW) as u64"]
        R2["read(RTC_TIME_HIGH) as u64"]
        R3["(high << 32) | low"]
    end
end

H --> RH
L --> RL

Register Bit Mapping and Operations

Sources: src/lib.rs(L37 - L39)  src/lib.rs(L45 - L47) 

Implementation Analysis

Precision and Range Considerations

The conversion implementation maintains full precision when converting from Unix seconds to nanoseconds, as the multiplication by NSEC_PER_SEC is exact for integer values. However, the reverse conversion from nanoseconds to seconds uses integer division, which truncates sub-second precision.

Supported Time Range:

  • Maximum representable time: (2^64 - 1) / NSEC_PER_SEC ≈ 584 years from Unix epoch
  • Minimum granularity: 1 second (in Unix timestamp format)
  • Hardware granularity: 1 nanosecond (internal representation)

Atomic Operation Considerations

The current implementation does not provide atomic guarantees when reading or writing the split 64-bit value across two 32-bit registers. The get_unix_timestamp() function reads RTC_TIME_LOW first, then RTC_TIME_HIGH, while set_unix_timestamp() writes RTC_TIME_HIGH first, then RTC_TIME_LOW. This ordering could potentially result in inconsistent reads if the hardware updates between register accesses.

Sources: src/lib.rs(L36 - L40)  src/lib.rs(L43 - L49) 

Project Configuration

Relevant source files

This document covers the comprehensive project configuration for the riscv_goldfish crate as defined in the Cargo.toml manifest. It details package metadata, licensing strategy, target platform specifications, and dependency management that enable cross-platform RTC driver functionality within the ArceOS ecosystem.

For implementation details of the RTC driver itself, see RTC Driver Implementation. For CI/CD build matrix and testing procedures, see CI/CD Pipeline.

Package Metadata and Identity

The project configuration establishes the fundamental identity and purpose of the riscv_goldfish crate through standardized Cargo package metadata.

Core Package Definition

flowchart TD
subgraph subGraph2["External References"]
    HOMEPAGE["homepage: arceos GitHub"]
    REPOSITORY["repository: riscv_goldfish GitHub"]
    DOCUMENTATION["documentation: docs.rs"]
end
subgraph subGraph1["Project Description"]
    DESC["description: System Real Time Clock Drivers"]
    KEYWORDS["keywords: [arceos, riscv, rtc]"]
    CATEGORIES["categories: [os, hardware-support, no-std]"]
end
subgraph subGraph0["Package Identity"]
    NAME["name: riscv_goldfish"]
    VERSION["version: 0.1.1"]
    EDITION["edition: 2021"]
    AUTHOR["authors: Keyang Hu"]
end

CATEGORIES --> DOCUMENTATION
DESC --> HOMEPAGE
EDITION --> CATEGORIES
KEYWORDS --> REPOSITORY
NAME --> DESC
VERSION --> KEYWORDS

Package Metadata Configuration

FieldValuePurpose
nameriscv_goldfishCrate identifier for Cargo registry
version0.1.1Semantic versioning for API compatibility
edition2021Rust language edition compatibility
authorsKeyang Hu keyang.hu@qq.comPrimary maintainer contact

The package name follows Rust naming conventions using underscores and clearly indicates both the target architecture (riscv) and hardware platform (goldfish). The version 0.1.1 indicates this is an early release with patch-level updates from the initial 0.1.0 version.

Sources: Cargo.toml(L1 - L6) 

Project Classification and Discovery

The configuration includes strategic metadata for package discovery and classification within the Rust ecosystem.

flowchart TD
subgraph subGraph2["Use Case Categories"]
    OS_DEV["Operating Systems"]
    HW_SUPPORT["Hardware Support"]
    NOSTD_EMBEDDED["no-std Embedded"]
end
subgraph subGraph1["Target Audiences"]
    ARCEOS_USERS["ArceOS Developers"]
    RISCV_DEVS["RISC-V Engineers"]
    RTC_DEVS["RTC Driver Authors"]
end
subgraph subGraph0["Discovery Metadata"]
    KEYWORDS["keywords"]
    CATEGORIES["categories"]
    DESCRIPTION["description"]
end

CATEGORIES --> HW_SUPPORT
CATEGORIES --> NOSTD_EMBEDDED
CATEGORIES --> OS_DEV
DESCRIPTION --> CATEGORIES
DESCRIPTION --> KEYWORDS
KEYWORDS --> ARCEOS_USERS
KEYWORDS --> RISCV_DEVS
KEYWORDS --> RTC_DEVS

The categories field strategically positions the crate in three key areas:

  • os: Operating system components and kernel-level drivers
  • hardware-support: Low-level hardware interface libraries
  • no-std: Embedded and bare-metal compatible crates

The keywords array enables discovery by ArceOS ecosystem users (arceos), RISC-V developers (riscv), and those seeking RTC functionality (rtc).

Sources: Cargo.toml(L6 - L12) 

The project employs a triple licensing strategy to maximize compatibility across different legal and organizational requirements.

Multi-License Configuration

flowchart TD
subgraph subGraph2["Use Cases"]
    OPEN_SOURCE["Open Source Projects"]
    COMMERCIAL["Commercial Integration"]
    CHINA_MARKET["Chinese Organizations"]
end
subgraph subGraph1["License Options"]
    GPL["GPL-3.0-or-laterCopyleft Protection"]
    APACHE["Apache-2.0Commercial Friendly"]
    MULAN["MulanPSL-2.0Chinese Legal Framework"]
end
subgraph subGraph0["Triple License Strategy"]
    LICENSE_FIELD["license: GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
end

APACHE --> COMMERCIAL
GPL --> OPEN_SOURCE
LICENSE_FIELD --> APACHE
LICENSE_FIELD --> GPL
LICENSE_FIELD --> MULAN
MULAN --> CHINA_MARKET

The OR operator in the license field allows users to choose any of the three licenses based on their specific requirements:

  • GPL-3.0-or-later: Ensures derivative works remain open source
  • Apache-2.0: Permits commercial use with patent grant protections
  • MulanPSL-2.0: Provides compatibility with Chinese legal frameworks

This licensing strategy is particularly important for operating system components that may be integrated into diverse software stacks with varying legal requirements.

Sources: Cargo.toml(L7) 

Target Platform and Architecture Support

The configuration enables cross-platform compatibility through careful dependency management and metadata specification.

No-Standard Library Configuration

flowchart TD
subgraph subGraph2["Supported Architectures"]
    X86_64["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Target Compatibility"]
    NOSTD["no-std Category"]
    BARE_METAL["Bare Metal Targets"]
    EMBEDDED["Embedded Systems"]
end
subgraph subGraph0["Dependencies Configuration"]
    EMPTY_DEPS["[dependencies](empty section)"]
end

BARE_METAL --> ARM64
BARE_METAL --> RISCV
BARE_METAL --> X86_64
BARE_METAL --> X86_NONE
EMPTY_DEPS --> NOSTD
NOSTD --> BARE_METAL
NOSTD --> EMBEDDED

The absence of dependencies in the [dependencies] section is intentional and critical for the crate's design:

  1. Zero Dependencies: Enables compilation for bare-metal targets without standard library
  2. no-std Compatibility: Listed in categories to indicate embedded system support
  3. Cross-Compilation: Supports multiple architecture targets through pure Rust implementation

This configuration allows the RTC driver to function in resource-constrained environments where the standard library is unavailable or undesirable.

Sources: Cargo.toml(L12 - L15) 

The configuration establishes clear pathways for users to access source code, documentation, and project information.

Link TypeURLPurpose
homepagehttps://github.com/arceos-org/arceosArceOS project ecosystem
repositoryhttps://github.com/arceos-org/riscv_goldfishSource code repository
documentationhttps://docs.rs/riscv_goldfishAPI documentation

The homepage points to the broader ArceOS ecosystem, indicating this crate's role as a component within a larger operating system project. The repository field provides direct access to source code, while documentation enables automatic API documentation generation and hosting on docs.rs.

Sources: Cargo.toml(L8 - L10) 

Configuration Impact on Build System

The Cargo.toml configuration directly influences the build system's behavior and capabilities across different target platforms.

flowchart TD
subgraph subGraph2["CI/CD Integration"]
    MULTIPLE_TARGETS["cargo build --target"]
    NO_STD_CHECK["no-std Validation"]
    DOC_GENERATION["cargo doc Generation"]
end
subgraph subGraph1["Build System Effects"]
    CROSS_COMPILE["Cross-Compilation Support"]
    TARGET_MATRIX["Multi-Target Building"]
    FEATURE_SET["Core Library Only"]
end
subgraph subGraph0["Cargo.toml Configuration"]
    NO_DEPS["Empty Dependencies"]
    NOSTD_CAT["no-std Category"]
    EDITION_2021["edition = 2021"]
end

CROSS_COMPILE --> MULTIPLE_TARGETS
FEATURE_SET --> DOC_GENERATION
NOSTD_CAT --> TARGET_MATRIX
NO_DEPS --> CROSS_COMPILE
TARGET_MATRIX --> NO_STD_CHECK

The configuration enables several critical build system behaviors:

  1. Dependency-Free Building: Empty dependencies section allows compilation without external crates
  2. Target Flexibility: no-std category signals compatibility with embedded toolchains
  3. Modern Rust: Edition 2021 ensures access to latest language features while maintaining compatibility

This configuration is essential for the CI/CD pipeline's ability to validate the driver across multiple architectures and deployment scenarios.

Sources: Cargo.toml(L4 - L15) 

Target Platforms and Cross-Compilation

Relevant source files

This document covers the target platform support and cross-compilation configuration for the riscv_goldfish crate. It details the supported architectures, no_std compatibility requirements, and the automated validation pipeline that ensures cross-platform compatibility. For information about the core RTC driver implementation, see RTC Driver Implementation. For details about project licensing and distribution, see Licensing and Distribution.

Supported Target Platforms

The riscv_goldfish crate is designed as a no_std library that supports multiple target architectures through Rust's cross-compilation capabilities. The primary target is RISC-V, but the crate maintains compatibility with x86_64 and ARM64 platforms for development and testing purposes.

Target Architecture Matrix

Target TripleArchitectureEnvironmentTestingPurpose
riscv64gc-unknown-none-elfRISC-V 64-bitBare metalBuild onlyPrimary target platform
x86_64-unknown-linux-gnux86_64Linux userspaceFull testsDevelopment and testing
x86_64-unknown-nonex86_64Bare metalBuild onlyBare metal validation
aarch64-unknown-none-softfloatARM64Bare metalBuild onlyEmbedded ARM compatibility

Target Platform Validation Pipeline

flowchart TD
subgraph Components["Toolchain Components"]
    RUST_SRC["rust-src"]
    CLIPPY_COMP["clippy"]
    RUSTFMT["rustfmt"]
end
subgraph Validation_Steps["Validation Pipeline"]
    FMT["cargo fmt --all --check"]
    CLIPPY["cargo clippy --target TARGET"]
    BUILD["cargo build --target TARGET"]
    TEST["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph CI_Matrix["CI Build Matrix"]
    NIGHTLY["nightly toolchain"]
    RISCV["riscv64gc-unknown-none-elf"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    ARM64["aarch64-unknown-none-softfloat"]
end

ARM64 --> FMT
BUILD --> TEST
CLIPPY --> BUILD
FMT --> CLIPPY
NIGHTLY --> CLIPPY_COMP
NIGHTLY --> RUSTFMT
NIGHTLY --> RUST_SRC
RISCV --> FMT
RUST_SRC --> BUILD
X86_LINUX --> FMT
X86_NONE --> FMT

Sources: .github/workflows/ci.yml(L8 - L30)  Cargo.toml(L12) 

Cross-Compilation Configuration

The crate leverages Rust's built-in cross-compilation support through the no_std attribute and careful dependency management. The configuration enables compilation for embedded and bare-metal targets without requiring a standard library.

Package Configuration for Cross-Compilation

flowchart TD
subgraph Cross_Compilation["Cross-Compilation Features"]
    BARE_METAL["Bare metal support"]
    EMBEDDED["Embedded targets"]
    KERNEL_SPACE["Kernel space compatibility"]
end
subgraph Target_Categories["Target Categories"]
    OS["os"]
    HW_SUPPORT["hardware-support"]
    NOSTD_CAT["no-std"]
end
subgraph Cargo_toml["Cargo.toml Configuration"]
    NO_STD["categories = no-std"]
    EDITION["edition = 2021"]
    NO_DEPS["dependencies = empty"]
end

EDITION --> BARE_METAL
HW_SUPPORT --> EMBEDDED
NOSTD_CAT --> BARE_METAL
NO_DEPS --> BARE_METAL
NO_STD --> NOSTD_CAT
OS --> KERNEL_SPACE

Sources: Cargo.toml(L12)  Cargo.toml(L14 - L15) 

The crate maintains zero dependencies, which eliminates potential compatibility issues during cross-compilation. This design choice ensures that the RTC driver can be integrated into any environment that supports basic memory-mapped I/O operations.

Toolchain Requirements

Rust Nightly Toolchain

The project requires the Rust nightly toolchain to access unstable features necessary for bare-metal development. The CI pipeline specifically configures the following components:

  • rust-src: Required for cross-compilation to no_std targets
  • clippy: Static analysis for all target platforms
  • rustfmt: Code formatting consistency across architectures

Target Installation

For local development, targets must be installed using rustup:

rustup target add riscv64gc-unknown-none-elf
rustup target add x86_64-unknown-none
rustup target add aarch64-unknown-none-softfloat

CI Toolchain Configuration Flow

flowchart TD
subgraph Components_Install["Component Installation"]
    RUST_SRC_COMP["rust-src"]
    CLIPPY_COMP["clippy"]
    RUSTFMT_COMP["rustfmt"]
    TARGET_INSTALL["targets: matrix.targets"]
end
subgraph Target_Matrix["Target Matrix Strategy"]
    FAIL_FAST["fail-fast: false"]
    MATRIX_TARGETS["matrix.targets"]
    MATRIX_TOOLCHAIN["matrix.rust-toolchain"]
end
subgraph Toolchain_Setup["Toolchain Setup"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
    VERSION_CHECK["rustc --version --verbose"]
end

CHECKOUT --> TOOLCHAIN
FAIL_FAST --> MATRIX_TARGETS
MATRIX_TARGETS --> TARGET_INSTALL
MATRIX_TOOLCHAIN --> TOOLCHAIN
TOOLCHAIN --> CLIPPY_COMP
TOOLCHAIN --> RUSTFMT_COMP
TOOLCHAIN --> RUST_SRC_COMP
TOOLCHAIN --> TARGET_INSTALL
TOOLCHAIN --> VERSION_CHECK

Sources: .github/workflows/ci.yml(L14 - L19)  .github/workflows/ci.yml(L8 - L12) 

Platform-Specific Considerations

RISC-V Target (riscv64gc-unknown-none-elf)

This is the primary target platform for the Goldfish RTC driver. The riscv64gc specification includes:

  • G: General-purpose integer and floating-point extensions
  • C: Compressed instruction extension
  • unknown-none-elf: Bare-metal environment with ELF binary format

Testing Strategy

The CI pipeline implements a pragmatic testing approach where unit tests only execute on x86_64-unknown-linux-gnu due to the complexity of running tests in bare-metal environments. All other targets undergo build validation to ensure compilation compatibility.

flowchart TD
subgraph Validation_Logic["Validation Logic"]
    IF_CONDITION["if: matrix.targets == x86_64-unknown-linux-gnu"]
end
subgraph Target_Actions["Target-Specific Actions"]
    UNIT_TESTS["cargo test --nocapture"]
    BUILD_CHECK["cargo build --all-features"]
    CLIPPY_CHECK["cargo clippy --all-features"]
end
subgraph Test_Strategy["Testing Strategy"]
    X86_LINUX_TEST["x86_64-unknown-linux-gnu"]
    BUILD_ONLY["Build-only validation"]
end

BUILD_ONLY --> BUILD_CHECK
BUILD_ONLY --> CLIPPY_CHECK
IF_CONDITION --> UNIT_TESTS
X86_LINUX_TEST --> IF_CONDITION

Sources: .github/workflows/ci.yml(L28 - L30)  .github/workflows/ci.yml(L25 - L27) 

This approach ensures that the driver code compiles correctly for all supported architectures while maintaining practical CI execution times and avoiding the complexity of cross-architecture test execution environments.

Licensing and Distribution

Relevant source files

This document covers the licensing strategy and distribution mechanisms for the riscv_goldfish crate. It explains the triple licensing scheme, distribution channels, package metadata configuration, and legal implications for different integration scenarios. For information about target platform configuration and cross-compilation setup, see Target Platforms and Cross-Compilation.

Licensing Strategy

The riscv_goldfish crate employs a triple licensing approach that maximizes compatibility across different legal frameworks and use cases. The license specification uses an OR-based structure allowing users to choose the most appropriate license for their specific requirements.

Triple License Configuration

The crate is licensed under three alternative licenses as defined in Cargo.toml(L7) :

GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0

License Selection Framework

flowchart TD
subgraph use_cases["Use Case Compatibility"]
    COPYLEFT["Copyleft Projects"]
    PERMISSIVE["Permissive Projects"]
    CHINESE["Chinese Legal Framework"]
end
subgraph license_options["Available License Options"]
    GPL["GPL-3.0-or-later"]
    APACHE["Apache-2.0"]
    MULAN["MulanPSL-2.0"]
end
subgraph riscv_goldfish_crate["riscv_goldfish Crate"]
    LICENSE_FIELD["license = GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"]
end

APACHE --> PERMISSIVE
CHINESE --> MULAN
COPYLEFT --> GPL
GPL --> COPYLEFT
LICENSE_FIELD --> APACHE
LICENSE_FIELD --> GPL
LICENSE_FIELD --> MULAN
MULAN --> CHINESE
PERMISSIVE --> APACHE

Sources: Cargo.toml(L7) 

License Characteristics

LicenseTypePrimary Use CaseKey Features
GPL-3.0-or-laterCopyleftOpen source projects requiring strong copyleftPatent protection, source disclosure requirements
Apache-2.0PermissiveCommercial and proprietary integrationsPatent grant, trademark protection, business-friendly
MulanPSL-2.0PermissiveChinese legal jurisdictionsDomestic legal framework compliance, simplified terms

Sources: Cargo.toml(L7) 

Distribution Channels

The crate is distributed through multiple channels to ensure accessibility and discoverability across different platforms and communities.

Distribution Infrastructure

flowchart TD
subgraph discovery["Discovery Metadata"]
    KEYWORDS["keywords = arceos, riscv, rtc"]
    CATEGORIES["categories = os, hardware-support, no-std"]
end
subgraph documentation["Documentation"]
    DOCS_RS["docs.rs/riscv_goldfish"]
    DOC_URL["documentation = docs.rs/riscv_goldfish"]
end
subgraph package_registry["Package Registry"]
    CRATES_IO["crates.io"]
    CARGO_TOML["Cargo.toml metadata"]
end
subgraph source_control["Source Control"]
    REPO_URL["repository = github.com/arceos-org/riscv_goldfish"]
    HOMEPAGE["homepage = github.com/arceos-org/arceos"]
end

CARGO_TOML --> CRATES_IO
CATEGORIES --> CRATES_IO
CRATES_IO --> DOCS_RS
DOC_URL --> DOCS_RS
HOMEPAGE --> CRATES_IO
KEYWORDS --> CRATES_IO
REPO_URL --> CRATES_IO

Sources: Cargo.toml(L8 - L12) 

Package Metadata Configuration

The distribution strategy leverages comprehensive metadata to maximize discoverability and provide clear project context:

Core Metadata Fields

  • Package Name: riscv_goldfish - clearly identifies the target platform and hardware component
  • Version: 0.1.1 - semantic versioning with patch-level updates
  • Authors: Keyang Hu <keyang.hu@qq.com> - primary maintainer contact
  • Description: System Real Time Clock (RTC) Drivers for riscv based on goldfish

Discovery and Classification

The crate uses targeted keywords and categories to ensure proper classification in the Rust ecosystem:

Keywords Cargo.toml(L11) :

  • arceos - Associates with the ArceOS operating system project
  • riscv - Identifies the target processor architecture
  • rtc - Specifies the hardware component type

Categories Cargo.toml(L12) :

  • os - Operating system components
  • hardware-support - Hardware abstraction and drivers
  • no-std - Embedded and bare-metal compatibility

Sources: Cargo.toml(L2 - L12) 

The triple licensing structure addresses different legal requirements and integration scenarios that may arise when incorporating the RTC driver into various projects.

Integration Scenarios

ArceOS Integration

The crate is designed for integration with the ArceOS operating system project, as indicated by the homepage reference Cargo.toml(L8)  The licensing allows ArceOS to choose the most appropriate license based on their distribution strategy.

Commercial Embedded Systems

The Apache-2.0 option enables integration into proprietary embedded systems and commercial products without source disclosure requirements, making it suitable for industrial IoT and embedded applications.

Open Source Projects

The GPL-3.0-or-later option ensures compatibility with other GPL-licensed operating system components while providing future license version flexibility.

Chinese Market Compliance

The MulanPSL-2.0 option addresses specific legal framework requirements for distribution within Chinese jurisdictions, supporting domestic technology adoption.

License Compatibility Matrix

flowchart TD
subgraph license_selection["License Selection"]
    CHOOSE_GPL["Choose GPL-3.0-or-later"]
    CHOOSE_APACHE["Choose Apache-2.0"]
    CHOOSE_MULAN["Choose MulanPSL-2.0"]
end
subgraph downstream_projects["Downstream Project Types"]
    GPL_PROJECT["GPL-licensed OS Components"]
    COMMERCIAL["Commercial Embedded Systems"]
    PERMISSIVE_PROJECT["MIT/BSD Licensed Projects"]
    CHINESE_PROJECT["Chinese Domestic Projects"]
end

CHINESE_PROJECT --> CHOOSE_MULAN
CHOOSE_APACHE --> COMMERCIAL
CHOOSE_APACHE --> PERMISSIVE_PROJECT
CHOOSE_GPL --> GPL_PROJECT
CHOOSE_MULAN --> CHINESE_PROJECT
COMMERCIAL --> CHOOSE_APACHE
GPL_PROJECT --> CHOOSE_GPL
PERMISSIVE_PROJECT --> CHOOSE_APACHE

Sources: Cargo.toml(L7 - L8) 

Distribution Workflow

The package distribution leverages Cargo's ecosystem integration to provide automated publishing and documentation generation:

  1. Source Publication: Code is maintained in the GitHub repository specified in Cargo.toml(L9) 
  2. Package Registry: Automated publishing to crates.io using metadata from Cargo.toml(L1 - L12) 
  3. Documentation Generation: Automatic docs.rs publication linked from Cargo.toml(L10) 
  4. Discoverability: Search optimization through keywords and categories in Cargo.toml(L11 - L12) 

The triple licensing approach ensures that regardless of the downstream project's legal requirements, there is a compatible license option available, maximizing the driver's utility across different integration scenarios while maintaining compliance with various legal frameworks.

Sources: Cargo.toml(L7 - L12) 

Development Workflow

Relevant source files

This document provides an overview of the development workflow for the riscv_goldfish RTC driver, covering the essential processes that contributors and maintainers follow when working with the codebase. This includes code quality assurance, multi-platform building and testing, and documentation generation.

For detailed information about the automated CI/CD pipeline configuration and build matrix, see CI/CD Pipeline. For local development environment setup and tooling requirements, see Development Environment Setup.

Workflow Overview

The development workflow is built around a robust continuous integration system that ensures code quality and cross-platform compatibility. The process validates changes across multiple target architectures while maintaining strict code formatting and linting standards.

flowchart TD
DEV["Developer"]
COMMIT["git commit/push"]
TRIGGER["GitHub Actions Trigger"]
CI_JOB["ci job"]
DOC_JOB["doc job"]
MATRIX["Build Matrix Strategy"]
TARGET1["x86_64-unknown-linux-gnu"]
TARGET2["x86_64-unknown-none"]
TARGET3["riscv64gc-unknown-none-elf"]
TARGET4["aarch64-unknown-none-softfloat"]
QUALITY["Code Quality Pipeline"]
BUILD["cargo build"]
TEST["cargo test"]
DOC_BUILD["cargo doc"]
PAGES["GitHub Pages Deploy"]
SUCCESS["Workflow Success"]

BUILD --> TEST
CI_JOB --> MATRIX
COMMIT --> TRIGGER
DEV --> COMMIT
DOC_BUILD --> PAGES
DOC_JOB --> DOC_BUILD
MATRIX --> TARGET1
MATRIX --> TARGET2
MATRIX --> TARGET3
MATRIX --> TARGET4
PAGES --> SUCCESS
QUALITY --> BUILD
TARGET1 --> QUALITY
TARGET2 --> QUALITY
TARGET3 --> QUALITY
TARGET4 --> QUALITY
TEST --> SUCCESS
TRIGGER --> CI_JOB
TRIGGER --> DOC_JOB

Development Workflow Overview

The workflow consists of two parallel jobs that validate different aspects of the codebase: the ci job focuses on code quality and cross-compilation, while the doc job handles documentation generation and deployment.

Sources: .github/workflows/ci.yml(L1 - L56) 

Code Quality Pipeline

The quality assurance process enforces consistent coding standards and catches potential issues before they reach the main branch. Each target platform goes through the same rigorous validation steps.

flowchart TD
START["Checkout Code"]
TOOLCHAIN["Setup Rust Nightly"]
VERSION["rustc --version --verbose"]
FMT["cargo fmt --all -- --check"]
CLIPPY["cargo clippy --target TARGET --all-features"]
BUILD["cargo build --target TARGET --all-features"]
UNIT_TEST["cargo test --target x86_64-unknown-linux-gnu"]
FMT_FAIL["Format Check Failed"]
CLIPPY_FAIL["Linting Failed"]
BUILD_FAIL["Build Failed"]
TEST_FAIL["Tests Failed"]
PASS["Quality Check Passed"]

BUILD --> BUILD_FAIL
BUILD --> UNIT_TEST
CLIPPY --> BUILD
CLIPPY --> CLIPPY_FAIL
FMT --> CLIPPY
FMT --> FMT_FAIL
START --> TOOLCHAIN
TOOLCHAIN --> VERSION
UNIT_TEST --> PASS
UNIT_TEST --> TEST_FAIL
VERSION --> FMT

Code Quality Validation Steps

The pipeline uses specific Rust toolchain components and flags to ensure comprehensive validation. The clippy step includes a specific allow flag -A clippy::new_without_default to suppress false positives for the RTC driver's constructor pattern.

Sources: .github/workflows/ci.yml(L15 - L30) 

Multi-Target Build Matrix

The build process validates the driver against multiple target architectures to ensure broad compatibility across different embedded and bare-metal environments.

Target PlatformPurposeTesting
x86_64-unknown-linux-gnuDevelopment and testing hostUnit tests enabled
x86_64-unknown-noneBare metal x86_64Build validation only
riscv64gc-unknown-none-elfPrimary RISC-V targetBuild validation only
aarch64-unknown-none-softfloatARM64 embedded systemsBuild validation only

The matrix strategy uses fail-fast: false to ensure all targets are tested even if one fails, providing comprehensive feedback about platform-specific issues.

flowchart TD
NIGHTLY["rust-toolchain: nightly"]
COMPONENTS["Components:rust-src, clippy, rustfmt"]
T1["x86_64-unknown-linux-gnu(Host Platform)"]
T2["x86_64-unknown-none(Bare Metal x86)"]
T3["riscv64gc-unknown-none-elf(Primary Target)"]
T4["aarch64-unknown-none-softfloat(ARM64 Embedded)"]
FULL_TEST["Full Testing PipelineFormat + Lint + Build + Test"]
BUILD_ONLY["Build ValidationFormat + Lint + Build"]

COMPONENTS --> T1
COMPONENTS --> T2
COMPONENTS --> T3
COMPONENTS --> T4
NIGHTLY --> COMPONENTS
T1 --> FULL_TEST
T2 --> BUILD_ONLY
T3 --> BUILD_ONLY
T4 --> BUILD_ONLY

Build Target Matrix Configuration

The testing strategy recognizes that unit tests are most practical on the host platform (x86_64-unknown-linux-gnu) while ensuring the driver compiles correctly for all target environments.

Sources: .github/workflows/ci.yml(L8 - L30) 

Development Environment Configuration

The development environment is configured to exclude build artifacts and IDE-specific files from version control, maintaining a clean repository while supporting different development setups.

Ignored PathPurposeImpact
/targetCargo build outputPrevents binary artifacts in VCS
/.vscodeVisual Studio Code settingsSupports multiple IDE preferences
.DS_StoremacOS system filesCross-platform compatibility
Cargo.lockDependency lock fileLibrary crate best practice

The exclusion of Cargo.lock follows Rust library crate conventions, allowing downstream consumers to resolve their own dependency versions while ensuring the crate builds with compatible dependency ranges.

Sources: .gitignore(L1 - L5) 

Documentation Pipeline

The documentation system automatically generates and deploys API documentation to GitHub Pages, ensuring the public documentation stays synchronized with the codebase.

flowchart TD
DOC_TRIGGER["doc job trigger"]
CHECKOUT["actions/checkout@v4"]
RUST_SETUP["dtolnay/rust-toolchain@nightly"]
DOC_BUILD["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate index.html redirect"]
CONDITION["github.ref == default-branch?"]
DEPLOY["JamesIves/github-pages-deploy-action@v4"]
SKIP["Skip deployment"]
PAGES_UPDATE["GitHub Pages Updated"]
ERROR_CHECK["Build successful?"]
CONTINUE["continue-on-error: true"]
FAIL["Pipeline fails"]

CHECKOUT --> RUST_SETUP
CONDITION --> DEPLOY
CONDITION --> SKIP
DEPLOY --> PAGES_UPDATE
DOC_BUILD --> ERROR_CHECK
DOC_BUILD --> INDEX_GEN
DOC_TRIGGER --> CHECKOUT
ERROR_CHECK --> CONTINUE
ERROR_CHECK --> FAIL
INDEX_GEN --> CONDITION
RUST_SETUP --> DOC_BUILD

Documentation Generation and Deployment

The documentation pipeline uses strict linting flags (RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs) to ensure comprehensive and accurate documentation. Deployment only occurs from the default branch to prevent documentation pollution from feature branches.

Sources: .github/workflows/ci.yml(L32 - L56) 

Contributing Process

Contributors should ensure their changes pass the complete quality pipeline before submitting pull requests. The automated checks provide immediate feedback on code formatting, linting violations, build failures, and test regressions.

Key requirements for contributions:

  • Code must pass cargo fmt formatting checks
  • All clippy lints must be resolved (except explicitly allowed ones)
  • Changes must build successfully on all target platforms
  • Unit tests must pass on the host platform
  • Documentation must build without warnings or broken links

The workflow automatically triggers on both push and pull_request events, providing consistent validation regardless of how changes are submitted.

Sources: .github/workflows/ci.yml(L3) 

CI/CD Pipeline

Relevant source files

This document covers the automated continuous integration and continuous deployment pipeline for the riscv_goldfish crate. The pipeline validates code quality, builds across multiple target architectures, runs tests, and automatically deploys documentation.

For information about local development environment setup, see Development Environment Setup. For details about supported target platforms and cross-compilation, see Target Platforms and Cross-Compilation.

Pipeline Overview

The CI/CD pipeline is implemented using GitHub Actions and consists of two primary workflows: code validation (ci job) and documentation deployment (doc job). The pipeline ensures code quality across multiple architectures while maintaining comprehensive documentation.

Workflow Trigger Configuration

flowchart TD
PUSH["push events"]
WORKFLOW["CI Workflow"]
PR["pull_request events"]
CI_JOB["ci job"]
DOC_JOB["doc job"]

PR --> WORKFLOW
PUSH --> WORKFLOW
WORKFLOW --> CI_JOB
WORKFLOW --> DOC_JOB

Sources: .github/workflows/ci.yml(L3) 

Multi-Architecture Build Matrix

The CI pipeline validates the codebase across four target architectures using a build matrix strategy. This ensures the no_std crate compiles correctly for both hosted and bare-metal environments.

Target Architecture Matrix

TargetPurposeTesting
x86_64-unknown-linux-gnuDevelopment and testingFull unit tests
x86_64-unknown-noneBare-metal x86_64Build-only
riscv64gc-unknown-none-elfPrimary RISC-V targetBuild-only
aarch64-unknown-none-softfloatARM64 embeddedBuild-only

Build Matrix Flow

flowchart TD
MATRIX["Build Matrix Strategy"]
NIGHTLY["nightly toolchain"]
TARGET1["x86_64-unknown-linux-gnu"]
TARGET2["x86_64-unknown-none"]
TARGET3["riscv64gc-unknown-none-elf"]
TARGET4["aarch64-unknown-none-softfloat"]
STEPS["Validation Steps"]
FMT["cargo fmt --all --check"]
CLIPPY["cargo clippy"]
BUILD["cargo build"]
TEST["cargo test"]
CONDITIONAL["if x86_64-unknown-linux-gnu"]

BUILD --> TEST
CLIPPY --> BUILD
FMT --> CLIPPY
MATRIX --> NIGHTLY
NIGHTLY --> TARGET1
NIGHTLY --> TARGET2
NIGHTLY --> TARGET3
NIGHTLY --> TARGET4
STEPS --> FMT
TARGET1 --> STEPS
TARGET2 --> STEPS
TARGET3 --> STEPS
TARGET4 --> STEPS
TEST --> CONDITIONAL

Sources: .github/workflows/ci.yml(L8 - L12)  .github/workflows/ci.yml(L22 - L30) 

Code Quality Validation Pipeline

The pipeline enforces code quality through multiple validation stages executed for each target architecture.

Validation Stage Details

flowchart TD
subgraph subGraph2["Build and Test"]
    BUILD["cargo build --target TARGET --all-features"]
    UNITTEST["cargo test --target TARGET -- --nocapture"]
end
subgraph subGraph1["Quality Checks"]
    VERSION["rustc --version --verbose"]
    FORMAT["cargo fmt --all -- --check"]
    LINT["cargo clippy --target TARGET --all-features"]
end
subgraph Setup["Setup"]
    CHECKOUT["actions/checkout@v4"]
    TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
    COMPONENTS["rust-src, clippy, rustfmt"]
end

BUILD --> UNITTEST
CHECKOUT --> TOOLCHAIN
COMPONENTS --> VERSION
FORMAT --> LINT
LINT --> BUILD
TOOLCHAIN --> COMPONENTS
VERSION --> FORMAT

Sources: .github/workflows/ci.yml(L14 - L30) 

Clippy Configuration

The pipeline uses specific clippy configuration to suppress false positives while maintaining code quality:

  • Allows clippy::new_without_default warnings since the RTC driver requires a base address parameter

Sources: .github/workflows/ci.yml(L25) 

Documentation Generation and Deployment

The documentation workflow builds API documentation and automatically deploys it to GitHub Pages for the default branch.

Documentation Workflow

flowchart TD
DOC_TRIGGER["Documentation Job"]
PERMISSIONS["contents: write permissions"]
ENV_VARS["Environment Variables"]
DEFAULT_BRANCH["default-branch reference"]
RUSTDOC_FLAGS["RUSTDOCFLAGS configuration"]
BUILD_DOCS["cargo doc --no-deps --all-features"]
INDEX_HTML["Generate index.html redirect"]
BRANCH_CHECK["Check if default branch"]
DEPLOY["JamesIves/github-pages-deploy-action@v4"]
GH_PAGES["gh-pages branch"]
TARGET_DOC["target/doc folder"]

BRANCH_CHECK --> DEPLOY
BUILD_DOCS --> INDEX_HTML
DEFAULT_BRANCH --> BUILD_DOCS
DEPLOY --> GH_PAGES
DEPLOY --> TARGET_DOC
DOC_TRIGGER --> PERMISSIONS
ENV_VARS --> DEFAULT_BRANCH
ENV_VARS --> RUSTDOC_FLAGS
INDEX_HTML --> BRANCH_CHECK
PERMISSIONS --> ENV_VARS
RUSTDOC_FLAGS --> BUILD_DOCS

Sources: .github/workflows/ci.yml(L32 - L55) 

Documentation Quality Enforcement

The pipeline enforces documentation quality through RUSTDOCFLAGS:

RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs

This configuration:

  • Treats broken internal documentation links as errors (-D rustdoc::broken_intra_doc_links)
  • Requires documentation for all public items (-D missing-docs)

Sources: .github/workflows/ci.yml(L40) 

GitHub Pages Deployment Strategy

flowchart TD
BUILD["cargo doc"]
REDIRECT["index.html redirect generation"]
CONDITIONAL["Branch validation"]
DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"]
SINGLE_COMMIT["single-commit: true"]
GH_PAGES_BRANCH["branch: gh-pages"]
DOC_FOLDER["folder: target/doc"]

BUILD --> REDIRECT
CONDITIONAL --> DEPLOY_ACTION
DEPLOY_ACTION --> DOC_FOLDER
DEPLOY_ACTION --> GH_PAGES_BRANCH
DEPLOY_ACTION --> SINGLE_COMMIT
REDIRECT --> CONDITIONAL

The deployment only occurs for pushes to the default branch, with pull requests receiving build validation but no deployment.

Sources: .github/workflows/ci.yml(L49 - L55) 

Pipeline Configuration Summary

Job Execution Environment

Both CI jobs execute on ubuntu-latest runners with the following characteristics:

  • Rust Toolchain: Nightly (required for no_std development)
  • Failure Strategy: fail-fast: false allows other matrix jobs to continue if one fails
  • Required Components: rust-src, clippy, rustfmt

Testing Strategy

The pipeline implements a tiered testing approach:

  1. Format and Lint: Applied to all targets
  2. Build Validation: Applied to all targets
  3. Unit Testing: Limited to x86_64-unknown-linux-gnu due to test execution constraints

This strategy ensures cross-platform compatibility while providing comprehensive testing coverage where practically feasible.

Sources: .github/workflows/ci.yml(L1 - L56) 

Development Environment Setup

Relevant source files

This document provides comprehensive instructions for setting up a local development environment for the riscv_goldfish RTC driver. It covers toolchain installation, environment configuration, and development workflow procedures necessary for contributing to the codebase.

For information about the CI/CD pipeline and automated testing, see CI/CD Pipeline. For details about target platform configuration and cross-compilation, see Target Platforms and Cross-Compilation.

Prerequisites and Toolchain Requirements

The riscv_goldfish driver requires a specific Rust toolchain configuration to support cross-compilation across multiple embedded targets. The development environment must be capable of building for bare-metal and no_std targets.

Rust Toolchain Setup

The project requires Rust nightly toolchain with specific components and target architectures. The CI pipeline defines the exact requirements that must be replicated locally.

flowchart TD
subgraph subGraph2["Build Targets"]
    X86_LINUX["x86_64-unknown-linux-gnu"]
    X86_NONE["x86_64-unknown-none"]
    RISCV["riscv64gc-unknown-none-elf"]
    ARM64["aarch64-unknown-none-softfloat"]
end
subgraph subGraph1["Essential Components"]
    RUST_SRC["rust-src"]
    CLIPPY["clippy"]
    RUSTFMT["rustfmt"]
end
subgraph subGraph0["Rust Toolchain Installation"]
    NIGHTLY["nightly toolchain"]
    COMPONENTS["Components Required"]
    TARGETS["Target Architectures"]
end

COMPONENTS --> CLIPPY
COMPONENTS --> RUSTFMT
COMPONENTS --> RUST_SRC
NIGHTLY --> COMPONENTS
NIGHTLY --> TARGETS
TARGETS --> ARM64
TARGETS --> RISCV
TARGETS --> X86_LINUX
TARGETS --> X86_NONE

Required Toolchain Installation Commands:

ComponentInstallation Command
Nightly Toolchainrustup toolchain install nightly
rust-src Componentrustup component add rust-src --toolchain nightly
clippy Componentrustup component add clippy --toolchain nightly
rustfmt Componentrustup component add rustfmt --toolchain nightly
x86_64-unknown-linux-gnurustup target add x86_64-unknown-linux-gnu --toolchain nightly
x86_64-unknown-nonerustup target add x86_64-unknown-none --toolchain nightly
riscv64gc-unknown-none-elfrustup target add riscv64gc-unknown-none-elf --toolchain nightly
aarch64-unknown-none-softfloatrustup target add aarch64-unknown-none-softfloat --toolchain nightly

Sources: .github/workflows/ci.yml(L11 - L19) 

Local Development Workflow

The development workflow mirrors the CI pipeline to ensure consistency between local development and automated testing. Each step corresponds to a specific quality gate that must pass before code submission.

flowchart TD
subgraph subGraph2["Verification Commands"]
    VERSION_CHECK["rustc --version --verbose"]
    DOC_BUILD["cargo doc --no-deps --all-features"]
end
subgraph subGraph1["Quality Gates"]
    FMT_PASS["Format Check Pass"]
    LINT_PASS["Linting Pass"]
    BUILD_PASS["Build Success"]
    TEST_PASS["Tests Pass"]
end
subgraph subGraph0["Development Cycle"]
    EDIT["Code Editing"]
    FORMAT["cargo fmt --all --check"]
    CLIPPY["cargo clippy --target TARGET"]
    BUILD["cargo build --target TARGET"]
    TEST["cargo test --target x86_64-unknown-linux-gnu"]
end

BUILD --> BUILD_PASS
BUILD_PASS --> TEST
CLIPPY --> LINT_PASS
EDIT --> FORMAT
FMT_PASS --> CLIPPY
FORMAT --> FMT_PASS
LINT_PASS --> BUILD
TEST --> TEST_PASS
TEST_PASS --> DOC_BUILD
VERSION_CHECK --> FORMAT

Development Command Reference

Development TaskCommandTarget Requirement
Format Checkcargo fmt --all -- --checkAll
Auto Formatcargo fmt --allAll
Lintingcargo clippy --target $TARGET --all-features -- -A clippy::new_without_defaultEach target
Buildcargo build --target $TARGET --all-featuresEach target
Unit Testscargo test --target x86_64-unknown-linux-gnu -- --nocapturex86_64-unknown-linux-gnu only
Documentationcargo doc --no-deps --all-featuresAny
Version Checkrustc --version --verboseAny

Sources: .github/workflows/ci.yml(L20 - L30)  .github/workflows/ci.yml(L44 - L48) 

Build Artifacts and Ignored Files

The development environment generates various artifacts and temporary files that are excluded from version control through .gitignore configuration.

flowchart TD
subgraph subGraph2["Build Outputs"]
    DEBUG["target/debug/"]
    RELEASE["target/release/"]
    DOC["target/doc/"]
    DEPS["target/.rustc_info.json"]
end
subgraph subGraph1["Generated Artifacts (Ignored)"]
    TARGET["/target"]
    VSCODE["/.vscode"]
    DS_STORE[".DS_Store"]
    CARGO_LOCK["Cargo.lock"]
end
subgraph subGraph0["Workspace Structure"]
    ROOT["Project Root"]
    SRC["src/"]
    CARGO_TOML["Cargo.toml"]
    README["README.md"]
end

ROOT --> CARGO_LOCK
ROOT --> CARGO_TOML
ROOT --> DS_STORE
ROOT --> README
ROOT --> SRC
ROOT --> TARGET
ROOT --> VSCODE
TARGET --> DEBUG
TARGET --> DEPS
TARGET --> DOC
TARGET --> RELEASE

Ignored File Categories

File/DirectoryPurposeReason for Ignoring
/targetCargo build artifactsGenerated content, platform-specific
/.vscodeVS Code workspace settingsEditor-specific configuration
.DS_StoremacOS system metadataPlatform-specific system files
Cargo.lockDependency lock fileLibrary crate, consumers manage versions

Sources: .gitignore(L1 - L4) 

Documentation Generation and Verification

Local documentation generation follows the same process as the CI pipeline, ensuring documentation quality and link integrity.

flowchart TD
subgraph subGraph2["Output Structure"]
    CRATE_DOC["riscv_goldfish/index.html"]
    API_DOC["API Documentation"]
    REDIRECT["Root index.html redirect"]
end
subgraph subGraph0["Documentation Build Process"]
    SOURCE["src/lib.rs"]
    CARGO_DOC["cargo doc --no-deps --all-features"]
    TARGET_DOC["target/doc/"]
    INDEX_GEN["Auto-generated index.html"]
end
subgraph subGraph1["Documentation Flags"]
    RUSTDOCFLAGS["-D rustdoc::broken_intra_doc_links -D missing-docs"]
    BROKEN_LINKS["Broken Link Detection"]
    MISSING_DOCS["Missing Documentation Detection"]
end

CARGO_DOC --> TARGET_DOC
INDEX_GEN --> API_DOC
INDEX_GEN --> CRATE_DOC
INDEX_GEN --> REDIRECT
RUSTDOCFLAGS --> BROKEN_LINKS
RUSTDOCFLAGS --> MISSING_DOCS
SOURCE --> CARGO_DOC
TARGET_DOC --> INDEX_GEN

Local Documentation Commands

TaskCommandEnvironment Variable
Build Documentationcargo doc --no-deps --all-featuresRUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs"
Open Documentationcargo doc --no-deps --all-features --openSame as above
Check Links Onlycargo doc --no-depsRUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links"

Sources: .github/workflows/ci.yml(L40)  .github/workflows/ci.yml(L44 - L48) 

Cross-Target Development Verification

Development workflow must verify functionality across all supported target architectures. This ensures the no_std compatibility and cross-platform portability requirements are maintained.

Target-Specific Build Matrix

Target ArchitectureBuild CommandTest SupportPrimary Use Case
x86_64-unknown-linux-gnucargo build --target x86_64-unknown-linux-gnu✅ Unit tests supportedDevelopment and testing
x86_64-unknown-nonecargo build --target x86_64-unknown-none❌ No std libraryBare metal x86_64
riscv64gc-unknown-none-elfcargo build --target riscv64gc-unknown-none-elf❌ Cross-compilationPrimary target platform
aarch64-unknown-none-softfloatcargo build --target aarch64-unknown-none-softfloat❌ Cross-compilationARM64 embedded systems

Sources: .github/workflows/ci.yml(L12)  .github/workflows/ci.yml(L25 - L30)