Skip to main content

consortium_hal/
lib.rs

1#![no_std]
2// Copyright 2026 Ethan Wu
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16// SPDX-License-Identifier: Apache-2.0
17
18//! Shared HAL vocabulary for Consortium chip support crates: the peripheral
19//! singleton machinery, a handful of narrow traits, and NVIC helpers.
20//!
21//! This crate is deliberately small. It holds only what the chip HALs
22//! (`consortium-hal-imx9`, `consortium-hal-stm32mp2`) and their generated code need
23//! to *agree* on. Every register layout, clock tree, and driver lives in a chip HAL,
24//! because that is where the differences are and a shared trait that tried to
25//! abstract them would either leak or lie.
26//!
27//! # Peripherals are owned, not addressed
28//!
29//! The load-bearing idea here is that a driver takes ownership of a peripheral
30//! rather than an address. [`peripherals!`] declares a chip's zero-sized singleton
31//! markers and the `Peripherals` struct that hands them out **once**; a driver
32//! constructor consumes the one it drives:
33//!
34//! ```rust,ignore
35//! let p = consortium_hal_imx9::init();
36//! let i2c = consortium_hal_imx9::i2c::I2c::new(p.LPI2C1, 24_000_000, 400_000);
37//! ```
38//!
39//! Because the base address and NVIC line travel with the singleton, no driver takes
40//! an MMIO pointer and no constructor is `unsafe` — the aliasing question is settled
41//! by the type system instead of by a comment. `peripherals::<NAME>::steal()` is the
42//! escape hatch for bring-up outside `init()`, and it carries the exclusivity
43//! obligation the safe path discharges.
44//!
45//! Per-instance driver state hangs off the marker rather than a named `static`, so an
46//! application forwards an interrupt as
47//! `<hal>::i2c::on_interrupt::<peripherals::LPI2C1>()`.
48//!
49//! # Traits
50//!
51//! | Trait               | Answers                                              |
52//! | ------------------- | ---------------------------------------------------- |
53//! | [`Peripheral`]      | What is this instance called in generated metadata?    |
54//! | [`ClockControl`]    | Enable and reset the peripheral clock.                 |
55//! | [`InterruptSource`] | Which interrupt line does it raise?                    |
56//! | [`MonotonicTimer`]  | Read ticks and program the next alarm.                 |
57//!
58//! [`MonotonicTimer`] is what a chip HAL's Embassy time driver is built on
59//! (STM32MP2 TIM2, i.MX9 LPIT1); `consortium-cfg` emits its bring-up into the
60//! generated `init()` unless `[profile] time_driver = false`.
61//!
62//! The [`nvic`] module adds interrupt masking for lines known only at runtime, which
63//! typed PAC `Interrupt` enums cannot express. The [`timeout`] module bounds the
64//! drivers' blocking status polls so a stuck bus surfaces as an error.
65//!
66//! # `Peri` and `PeripheralType`
67//!
68//! Both are re-exported from `embassy_hal_internal`, so chip HALs and their
69//! dependents (the doorbell crates, the UART transport glue) can name a singleton
70//! handle without taking an Embassy dependency of their own.
71//!
72//! `no_std`, no features.
73
74pub mod nvic;
75pub mod timeout;
76
77/// Ownership handle and marker trait for peripheral singletons, re-exported so
78/// chip HALs and their dependents can name them without depending on Embassy
79/// directly.
80pub use embassy_hal_internal::{Peri, PeripheralType};
81
82/// Declare a chip's peripheral singletons and the `Peripherals` struct that
83/// hands them out once.
84///
85/// This is `embassy_hal_internal::peripherals!` with two deliberate changes:
86///
87/// - **No per-type `defmt::Format` derive.** Consortium's chips have hundreds of
88///   peripherals, and the derive emits a `.defmt.*` link section per type; on a
89///   `defmt`-enabled host build that overruns the Mach-O section limit and the
90///   HAL stops compiling on macOS. The markers are zero-sized and never
91///   formatted, so nothing is lost.
92/// - **No `#[unsafe(no_mangle)]` on the once-guard.** Two chip HALs can end up in
93///   one binary (the `consortium-cfg` UI tests link both), and an unmangled
94///   guard symbol would collide between them.
95///
96/// Invoke it at the chip HAL's crate root; `scripts/pac/gen_peripherals.py`
97/// emits the invocation.
98#[macro_export]
99macro_rules! peripherals {
100    ($($(#[$cfg:meta])? $name:ident),* $(,)?) => {
101        /// Zero-sized marker types for the peripheral singletons.
102        pub mod peripherals {
103            $(
104                $(#[$cfg])?
105                #[allow(non_camel_case_types)]
106                #[doc = concat!(stringify!($name), " peripheral")]
107                #[derive(Debug)]
108                pub struct $name {
109                    _private: (),
110                }
111
112                $(#[$cfg])?
113                impl $name {
114                    /// Unsafely create an instance of this peripheral out of thin
115                    /// air.
116                    ///
117                    /// # Safety
118                    ///
119                    /// You must ensure that you're only using one instance of
120                    /// this type at a time.
121                    #[inline]
122                    pub const unsafe fn steal() -> $crate::Peri<'static, Self> {
123                        // SAFETY: forwarded to the caller, who guarantees this
124                        // is the only live instance of the peripheral.
125                        unsafe { $crate::Peri::new_unchecked(Self { _private: () }) }
126                    }
127                }
128
129                $(#[$cfg])?
130                impl ::core::marker::Copy for $name {}
131
132                $(#[$cfg])?
133                impl ::core::clone::Clone for $name {
134                    fn clone(&self) -> Self {
135                        *self
136                    }
137                }
138
139                $(#[$cfg])?
140                impl $crate::PeripheralType for $name {}
141            )*
142        }
143
144        /// Every peripheral singleton this chip exposes to the running core.
145        ///
146        /// Obtained once from the HAL's `init()`.
147        #[allow(non_snake_case)]
148        pub struct Peripherals {
149            $(
150                #[doc = concat!(stringify!($name), " peripheral")]
151                $(#[$cfg])?
152                pub $name: $crate::Peri<'static, peripherals::$name>,
153            )*
154        }
155
156        impl Peripherals {
157            /// Returns all the peripherals *once*.
158            ///
159            /// # Panics
160            ///
161            /// Panics if called more than once.
162            #[inline]
163            pub(crate) fn take() -> Self {
164                ::critical_section::with(|_| {
165                    static mut TAKEN: bool = false;
166
167                    // SAFETY: inside a critical section, so this is the only
168                    // access to `TAKEN` for its duration.
169                    unsafe {
170                        if TAKEN {
171                            panic!("the chip's peripherals have already been taken");
172                        }
173                        TAKEN = true;
174                        Self::steal()
175                    }
176                })
177            }
178
179            /// Unsafely create an instance of every peripheral out of thin air.
180            ///
181            /// # Safety
182            ///
183            /// You must ensure that you're only using one instance of each type
184            /// at a time.
185            #[inline]
186            pub unsafe fn steal() -> Self {
187                Self {
188                    $(
189                        $(#[$cfg])?
190                        // SAFETY: forwarded to the caller, who guarantees each
191                        // peripheral has only one live instance.
192                        $name: unsafe { peripherals::$name::steal() },
193                    )*
194                }
195            }
196        }
197    };
198}
199
200/// Marker for a concrete peripheral instance.
201pub trait Peripheral {
202    /// Human-readable peripheral name used by generated chip metadata.
203    const NAME: &'static str;
204}
205
206/// Minimal clock/reset hooks for chip-specific peripheral drivers.
207pub trait ClockControl {
208    /// Enable the peripheral clock.
209    fn enable(&self);
210
211    /// Reset the peripheral when the chip supports a local reset.
212    fn reset(&self);
213}
214
215/// Interrupt source that can be bound by board startup code.
216pub trait InterruptSource {
217    /// Human-readable interrupt name.
218    const INTERRUPT: &'static str;
219}
220
221/// Minimal monotonic timer abstraction shared by chip HAL time drivers.
222pub trait MonotonicTimer {
223    /// Return monotonically increasing ticks.
224    fn now(&self) -> u64;
225
226    /// Program the next hardware alarm.
227    fn set_alarm(&self, at: u64) -> bool;
228}