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}