//! Independent per-link scheduling, encoding, framing and bounded transmission. use super::{ peripherals::{self, F722UsbMavlink, OwnedUart, ReceiveInterrupt}, serial::{ReceiveEvent, RxChunk, SerialDevice, SerialIo}, }; use crate::SerialInventory; use kaptan_data_channel::{DataChannel, DataChannelRates}; use kaptan_mavlink::{ MavlinkFrame, receiver::{IntegrityCheckedFrame, IntegrityMavlinkReceiver}, }; use kaptan_ports::PortId; use kaptan_tx::PendingFrame; pub(crate) const SERVICE_HZ: usize = 100; pub(crate) struct Link { pub(crate) channel: DataChannel, pub(crate) receiver: IntegrityMavlinkReceiver<2>, } impl Link { pub(crate) fn new( system: u8, component: u8, rates: DataChannelRates, ) -> Result { Ok(Self { channel: DataChannel::new(system, component, rates, 100_000) .map_err(|_| crate::BoardError::Initialization)?, receiver: IntegrityMavlinkReceiver::new(), }) } } impl SerialDevice for OwnedUart { fn receive(&mut self) -> ReceiveEvent { match OwnedUart::receive(self) { ReceiveInterrupt::Byte(byte) => ReceiveEvent::Byte(byte), ReceiveInterrupt::Fault => ReceiveEvent::Fault, ReceiveInterrupt::NotPending => ReceiveEvent::Idle, } } } /// Bounded data exchanged between the worker and I/O owners. Decoding, /// scheduling and persistence cannot borrow the physical owners through this API. #[derive(Default)] pub struct IoBatch { pub(crate) serial: RxChunk, pub(crate) usb: Option, pub(crate) accepted: bool, pub(crate) pending: bool, } impl SerialIo { pub fn exchange(&mut self, frame: Option>) -> IoBatch { match frame { Some(frame) => IoBatch { accepted: self.send(frame), ..IoBatch::default() }, None => { let serial = self.take_chunk(); IoBatch { pending: serial.pending, serial, ..IoBatch::default() } } } } } /// One short-lock RTIC resource for the board's declared UARTs. All UART IRQs /// have priority 14. Protocol work stays outside this bounded I/O handoff. pub struct PortIo { inventory: SerialInventory, pub(crate) ports: [SerialIo; N], } impl PortIo { pub(crate) fn new(inventory: SerialInventory) -> Self { assert_eq!(inventory.len(), N, "validated board port count"); Self { inventory, ports: core::array::from_fn(|_| SerialIo::new(None, false)), } } pub fn on_interrupt(&mut self, id: PortId, now: kaptan_time::Timestamp) -> bool { self.inventory .index(id) .is_some_and(|index| self.ports[index].on_interrupt(now)) } pub fn exchange(&mut self, id: PortId, frame: Option>) -> IoBatch { self.inventory .index(id) .map_or_else(IoBatch::default, |index| self.ports[index].exchange(frame)) } } pub struct UsbIo { usb: F722UsbMavlink, rx_interrupt: RxFifoInterruptGate, } impl UsbIo { pub(crate) fn new(usb: F722UsbMavlink, rx_interrupt: RxFifoInterruptGate) -> Self { Self { usb, rx_interrupt } } pub fn on_interrupt(&mut self) -> bool { self.usb.with_rx_interrupt_gate( |usb| usb.poll(), |enabled| self.rx_interrupt.set_enabled(enabled), ) } pub fn exchange(&mut self, frame: Option>) -> IoBatch { exchange_usb(&mut self.usb, frame, |enabled| { self.rx_interrupt.set_enabled(enabled) }) } } /// Private F722 capability delegated alongside the UsbPeripheral in its factory. /// The USB I/O owner retains it at runtime. No register-block reference is made. pub(crate) struct RxFifoInterruptGate { _private: (), } impl RxFifoInterruptGate { pub(crate) fn new(_global: &stm32f7::stm32f722::OTG_FS_GLOBAL) -> Self { Self { _private: () } } /// Call after bus enable in init, or inside the USB RTIC resource lock. /// Defers RXFLVL only. IN, reset and suspend can still interrupt. EP0 shares /// the RX FIFO and cannot bypass a bulk packet blocked at its head. pub(crate) fn set_enabled(&mut self, enabled: bool) { // RM0431: OTG_FS_GINTMSK is an aligned, 32-bit RW register at +0x18. // Form only its raw address, without borrowing the driver's register block. let gintmsk = (stm32f7::stm32f722::OTG_FS_GLOBAL::ptr() as usize + 0x18) as *mut u32; // SAFETY: the factory consumes the PAC tokens and enables the bus before // use. This single-core device keeps its USB clock on. UsbIo's RTIC lock // excludes every runtime USB caller through this RMW. The pinned 0.4.0 // driver writes GINTMSK only in enable(), before this capability is used. unsafe { set_rx_fifo_interrupt_enabled(gintmsk, enabled) }; // Complete the device write before unlocking or returning from the IRQ. cortex_m::asm::dsb(); } } /// # Safety /// `gintmsk` must permit aligned 32-bit volatile reads/writes, with no concurrent /// writer. The caller must complete the device write before releasing exclusion. unsafe fn set_rx_fifo_interrupt_enabled(gintmsk: *mut u32, enabled: bool) { const RXFLVLM: u32 = 1 << 4; // SAFETY: the caller provides exclusive access to this RW register. GINTMSK // has no read/clear or write-one-to-clear fields. Preserve all other bits. unsafe { let mask = gintmsk.read_volatile(); gintmsk.write_volatile(if enabled { mask | RXFLVLM } else { mask & !RXFLVLM }); } } pub(crate) fn exchange_usb( usb: &mut super::usb::UsbMavlink<'_, B>, frame: Option>, set_rx_interrupt_enabled: impl FnOnce(bool), ) -> IoBatch { usb.with_rx_interrupt_gate( |usb| match frame { Some(frame) => IoBatch { accepted: usb.send(frame), ..IoBatch::default() }, None => { usb.poll(); // The existing USB adapter consumes at most one 64-byte packet // here, including bounded framing. Message decode/dispatch stays // worker-local. Bus polling has separate hardware wait constraints. let frame = usb.take_received(); let pending = usb.poll(); IoBatch { usb: frame, pending, ..IoBatch::default() } } }, set_rx_interrupt_enabled, ) } pub(crate) enum F722StatusLed { Pd2(peripherals::ActiveLowLedPd2), Pa13(GeprcStatusLed), } impl F722StatusLed { pub(crate) fn toggle(&mut self) { match self { Self::Pd2(led) => led.toggle(), Self::Pa13(led) => led.toggle(), } } } pub(crate) struct GeprcStatusLed { port: stm32f7::stm32f722::GPIOA, is_high: bool, } impl GeprcStatusLed { pub(crate) fn new(rcc: &stm32f7::stm32f722::RCC, port: stm32f7::stm32f722::GPIOA) -> Self { rcc.ahb1enr().modify(|_, write| write.gpioaen().set_bit()); port.moder().modify(|_, write| write.moder13().output()); port.otyper().modify(|_, write| write.ot13().push_pull()); port.ospeedr() .modify(|_, write| write.ospeedr13().low_speed()); port.pupdr().modify(|_, write| write.pupdr13().floating()); port.bsrr().write(|write| write.br13().set_bit()); Self { port, is_high: false, } } fn toggle(&mut self) { self.is_high = !self.is_high; self.port.bsrr().write(|write| { if self.is_high { write.bs13().set_bit() } else { write.br13().set_bit() } }); } } #[cfg(test)] #[path = "../../tests/support/usb_irq.rs"] mod usb_irq;