use core::{
marker::PhantomData,
sync::atomic::{AtomicU8, Ordering},
};
use crate::{Config, ConfigError, RxSample, SerialEventSet, SerialIrqReport};
pub struct UartRegisterGate<E: ?Sized = dyn UartEmergencyTx> {
owner: AtomicU8,
emergency_tx: E,
}
const REGISTER_OWNER_NONE: u8 = 0;
const REGISTER_OWNER_NORMAL: u8 = 1;
const REGISTER_OWNER_EMERGENCY_IDLE: u8 = 2;
const REGISTER_OWNER_EMERGENCY_ACTIVE: u8 = 3;
impl<E> UartRegisterGate<E> {
pub const fn new(emergency_tx: E) -> Self {
Self {
owner: AtomicU8::new(REGISTER_OWNER_NONE),
emergency_tx,
}
}
}
impl<E: ?Sized> UartRegisterGate<E> {
pub fn try_enter(&self) -> Option<UartRegisterGuard<'_, E>> {
self.owner
.compare_exchange(
REGISTER_OWNER_NONE,
REGISTER_OWNER_NORMAL,
Ordering::Acquire,
Ordering::Relaxed,
)
.ok()
.map(|_| UartRegisterGuard {
gate: self,
_not_send: PhantomData,
})
}
pub fn emergency_active(&self) -> bool {
matches!(
self.owner.load(Ordering::Acquire),
REGISTER_OWNER_EMERGENCY_IDLE | REGISTER_OWNER_EMERGENCY_ACTIVE
)
}
}
impl<E: UartEmergencyTx + ?Sized> UartRegisterGate<E> {
pub fn try_begin_emergency(&self) -> Option<UartEmergencyAccess<'_, E>> {
let owner = self.owner.load(Ordering::Acquire);
if owner != REGISTER_OWNER_NONE && owner != REGISTER_OWNER_EMERGENCY_IDLE {
return None;
}
self.owner
.compare_exchange(
owner,
REGISTER_OWNER_EMERGENCY_ACTIVE,
Ordering::AcqRel,
Ordering::Acquire,
)
.ok()?;
unsafe { self.emergency_tx.mask_interrupts_unlocked() };
Some(UartEmergencyAccess {
gate: self,
_not_send: PhantomData,
})
}
}
pub struct UartRegisterGuard<'a, E: ?Sized = dyn UartEmergencyTx> {
gate: &'a UartRegisterGate<E>,
_not_send: PhantomData<*mut ()>,
}
impl<E: ?Sized> Drop for UartRegisterGuard<'_, E> {
fn drop(&mut self) {
self.gate
.owner
.store(REGISTER_OWNER_NONE, Ordering::Release);
}
}
pub struct UartEmergencyAccess<'a, E: ?Sized = dyn UartEmergencyTx> {
gate: &'a UartRegisterGate<E>,
_not_send: PhantomData<*mut ()>,
}
impl<E: ?Sized> Drop for UartEmergencyAccess<'_, E> {
fn drop(&mut self) {
self.gate
.owner
.store(REGISTER_OWNER_EMERGENCY_IDLE, Ordering::Release);
}
}
impl<E: UartEmergencyTx + ?Sized> UartEmergencyAccess<'_, E> {
pub fn try_write(&self, bytes: &[u8]) -> usize {
unsafe { self.gate.emergency_tx.try_write_unlocked(bytes) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UartInfo {
pub name: &'static str,
pub register_base: usize,
pub initial_baudrate: u32,
}
pub struct SerialParts<C, I, E> {
pub control: C,
pub irq: I,
pub emergency_tx: E,
}
impl<C, I, E> SerialParts<C, I, E> {
pub const fn new(control: C, irq: I, emergency_tx: E) -> Self {
Self {
control,
irq,
emergency_tx,
}
}
}
pub trait SplitUart: Sized {
type Control: UartPort;
type Irq: UartIrq;
type EmergencyTx: UartEmergencyTx;
fn runtime_info(&self) -> UartInfo;
fn split(self) -> SerialParts<Self::Control, Self::Irq, Self::EmergencyTx>;
}
pub trait UartPort: Send + 'static {
fn startup(&mut self, config: &Config) -> Result<(), ConfigError>;
fn shutdown(&mut self);
fn set_config(&mut self, config: &Config) -> Result<(), ConfigError>;
fn read_rx(&mut self) -> Option<RxSample>;
fn discard_rx(&mut self);
fn write_tx(&mut self, bytes: &[u8]) -> usize;
fn discard_tx(&mut self) -> bool;
fn tx_idle(&mut self) -> bool;
fn mask(&mut self, sources: SerialEventSet);
fn mask_all(&mut self);
fn rearm(&mut self, sources: SerialEventSet) -> SerialEventSet;
}
pub trait UartIrq: Send + 'static {
fn mask(&mut self, sources: SerialEventSet);
fn handle(&mut self) -> Option<SerialIrqReport>;
}
pub trait UartEmergencyTx: Send + Sync + 'static {
unsafe fn mask_interrupts_unlocked(&self);
unsafe fn try_write_unlocked(&self, bytes: &[u8]) -> usize;
}
#[cfg(test)]
mod tests {
use core::sync::atomic::AtomicUsize;
use super::*;
struct RecordingEmergencyTx {
writes: &'static AtomicUsize,
masks: &'static AtomicUsize,
}
impl UartEmergencyTx for RecordingEmergencyTx {
unsafe fn mask_interrupts_unlocked(&self) {
self.masks.fetch_add(1, Ordering::Relaxed);
}
unsafe fn try_write_unlocked(&self, bytes: &[u8]) -> usize {
self.writes.fetch_add(bytes.len(), Ordering::Relaxed);
bytes.len()
}
}
struct NoopEmergencyTx;
impl UartEmergencyTx for NoopEmergencyTx {
unsafe fn mask_interrupts_unlocked(&self) {}
unsafe fn try_write_unlocked(&self, _bytes: &[u8]) -> usize {
0
}
}
#[test]
fn register_gate_never_waits_and_releases_on_guard_drop() {
let gate = UartRegisterGate::new(());
let owner = gate.try_enter().expect("first register owner");
assert!(gate.try_enter().is_none());
drop(owner);
assert!(gate.try_enter().is_some());
}
#[test]
fn emergency_takeover_persists_between_formatting_calls() {
static UART_WRITES: AtomicUsize = AtomicUsize::new(0);
static UART_MASKS: AtomicUsize = AtomicUsize::new(0);
UART_WRITES.store(0, Ordering::Relaxed);
UART_MASKS.store(0, Ordering::Relaxed);
let gate = UartRegisterGate::new(RecordingEmergencyTx {
writes: &UART_WRITES,
masks: &UART_MASKS,
});
let first = gate.try_begin_emergency().expect("emergency takeover");
assert_eq!(UART_MASKS.load(Ordering::Relaxed), 1);
assert_eq!(first.try_write(b"panic"), 5);
assert!(gate.try_begin_emergency().is_none());
drop(first);
assert!(gate.emergency_active());
assert!(gate.try_enter().is_none());
let second = gate
.try_begin_emergency()
.expect("continued emergency access");
assert_eq!(UART_MASKS.load(Ordering::Relaxed), 2);
assert_eq!(second.try_write(b" backtrace"), 10);
assert_eq!(UART_WRITES.load(Ordering::Relaxed), 15);
}
#[test]
fn emergency_takeover_does_not_steal_an_active_transaction() {
let gate = UartRegisterGate::new(NoopEmergencyTx);
let normal = gate.try_enter().expect("normal register owner");
assert!(gate.try_begin_emergency().is_none());
drop(normal);
assert!(gate.try_begin_emergency().is_some());
}
#[test]
fn register_guard_does_not_authorize_an_unrelated_uart() {
static FIRST_UART_WRITES: AtomicUsize = AtomicUsize::new(0);
static SECOND_UART_WRITES: AtomicUsize = AtomicUsize::new(0);
FIRST_UART_WRITES.store(0, Ordering::Relaxed);
SECOND_UART_WRITES.store(0, Ordering::Relaxed);
static FIRST_UART_MASKS: AtomicUsize = AtomicUsize::new(0);
static SECOND_UART_MASKS: AtomicUsize = AtomicUsize::new(0);
FIRST_UART_MASKS.store(0, Ordering::Relaxed);
SECOND_UART_MASKS.store(0, Ordering::Relaxed);
let first_uart_gate = UartRegisterGate::new(RecordingEmergencyTx {
writes: &FIRST_UART_WRITES,
masks: &FIRST_UART_MASKS,
});
let _second_uart_gate = UartRegisterGate::new(RecordingEmergencyTx {
writes: &SECOND_UART_WRITES,
masks: &SECOND_UART_MASKS,
});
let first_uart_access = first_uart_gate
.try_begin_emergency()
.expect("first UART access");
assert_eq!(first_uart_access.try_write(b"x"), 1);
assert_eq!(FIRST_UART_MASKS.load(Ordering::Relaxed), 1);
assert_eq!(SECOND_UART_MASKS.load(Ordering::Relaxed), 0);
assert_eq!(FIRST_UART_WRITES.load(Ordering::Relaxed), 1);
assert_eq!(SECOND_UART_WRITES.load(Ordering::Relaxed), 0);
}
}