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.
64//!
65//! # `Peri` and `PeripheralType`
66//!
67//! Both are re-exported from `embassy_hal_internal`, so chip HALs and their
68//! dependents (the doorbell crates, the UART transport glue) can name a singleton
69//! handle without taking an Embassy dependency of their own.
70//!
71//! `no_std`, no features.
72
73pub mod nvic;
74
75/// Ownership handle and marker trait for peripheral singletons, re-exported so
76/// chip HALs and their dependents can name them without depending on Embassy
77/// directly.
78pub use embassy_hal_internal::{Peri, PeripheralType};
79
80/// Declare a chip's peripheral singletons and the `Peripherals` struct that
81/// hands them out once.
82///
83/// This is `embassy_hal_internal::peripherals!` with two deliberate changes:
84///
85/// - **No per-type `defmt::Format` derive.** Consortium's chips have hundreds of
86/// peripherals, and the derive emits a `.defmt.*` link section per type; on a
87/// `defmt`-enabled host build that overruns the Mach-O section limit and the
88/// HAL stops compiling on macOS. The markers are zero-sized and never
89/// formatted, so nothing is lost.
90/// - **No `#[unsafe(no_mangle)]` on the once-guard.** Two chip HALs can end up in
91/// one binary (the `consortium-cfg` UI tests link both), and an unmangled
92/// guard symbol would collide between them.
93///
94/// Invoke it at the chip HAL's crate root; `scripts/pac/gen_peripherals.py`
95/// emits the invocation.
96#[macro_export]
97macro_rules! peripherals {
98 ($($(#[$cfg:meta])? $name:ident),* $(,)?) => {
99 /// Zero-sized marker types for the peripheral singletons.
100 pub mod peripherals {
101 $(
102 $(#[$cfg])?
103 #[allow(non_camel_case_types)]
104 #[doc = concat!(stringify!($name), " peripheral")]
105 #[derive(Debug)]
106 pub struct $name {
107 _private: (),
108 }
109
110 $(#[$cfg])?
111 impl $name {
112 /// Unsafely create an instance of this peripheral out of thin
113 /// air.
114 ///
115 /// # Safety
116 ///
117 /// You must ensure that you're only using one instance of
118 /// this type at a time.
119 #[inline]
120 pub const unsafe fn steal() -> $crate::Peri<'static, Self> {
121 // SAFETY: forwarded to the caller, who guarantees this
122 // is the only live instance of the peripheral.
123 unsafe { $crate::Peri::new_unchecked(Self { _private: () }) }
124 }
125 }
126
127 $(#[$cfg])?
128 impl ::core::marker::Copy for $name {}
129
130 $(#[$cfg])?
131 impl ::core::clone::Clone for $name {
132 fn clone(&self) -> Self {
133 *self
134 }
135 }
136
137 $(#[$cfg])?
138 impl $crate::PeripheralType for $name {}
139 )*
140 }
141
142 /// Every peripheral singleton this chip exposes to the running core.
143 ///
144 /// Obtained once from the HAL's `init()`.
145 #[allow(non_snake_case)]
146 pub struct Peripherals {
147 $(
148 #[doc = concat!(stringify!($name), " peripheral")]
149 $(#[$cfg])?
150 pub $name: $crate::Peri<'static, peripherals::$name>,
151 )*
152 }
153
154 impl Peripherals {
155 /// Returns all the peripherals *once*.
156 ///
157 /// # Panics
158 ///
159 /// Panics if called more than once.
160 #[inline]
161 pub(crate) fn take() -> Self {
162 ::critical_section::with(|_| {
163 static mut TAKEN: bool = false;
164
165 // SAFETY: inside a critical section, so this is the only
166 // access to `TAKEN` for its duration.
167 unsafe {
168 if TAKEN {
169 panic!("the chip's peripherals have already been taken");
170 }
171 TAKEN = true;
172 Self::steal()
173 }
174 })
175 }
176
177 /// Unsafely create an instance of every peripheral out of thin air.
178 ///
179 /// # Safety
180 ///
181 /// You must ensure that you're only using one instance of each type
182 /// at a time.
183 #[inline]
184 pub unsafe fn steal() -> Self {
185 Self {
186 $(
187 $(#[$cfg])?
188 // SAFETY: forwarded to the caller, who guarantees each
189 // peripheral has only one live instance.
190 $name: unsafe { peripherals::$name::steal() },
191 )*
192 }
193 }
194 }
195 };
196}
197
198/// Marker for a concrete peripheral instance.
199pub trait Peripheral {
200 /// Human-readable peripheral name used by generated chip metadata.
201 const NAME: &'static str;
202}
203
204/// Minimal clock/reset hooks for chip-specific peripheral drivers.
205pub trait ClockControl {
206 /// Enable the peripheral clock.
207 fn enable(&self);
208
209 /// Reset the peripheral when the chip supports a local reset.
210 fn reset(&self);
211}
212
213/// Interrupt source that can be bound by board startup code.
214pub trait InterruptSource {
215 /// Human-readable interrupt name.
216 const INTERRUPT: &'static str;
217}
218
219/// Minimal monotonic timer abstraction shared by chip HAL time drivers.
220pub trait MonotonicTimer {
221 /// Return monotonically increasing ticks.
222 fn now(&self) -> u64;
223
224 /// Program the next hardware alarm.
225 fn set_alarm(&self, at: u64) -> bool;
226}