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.
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}