1mod control;
8mod ingress;
9mod log_mailbox;
10pub(crate) mod spsc;
11mod state;
12mod worker;
13
14use alloc::{boxed::Box, sync::Arc, vec::Vec};
15use core::{
16 fmt::{self, Write},
17 sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
18};
19
20use ax_driver::serial::SerialDevice;
21pub use ax_driver::serial::SerialDeviceInfo;
22use ax_lazyinit::OnceLock;
23use ax_sync::Mutex;
24use ax_task::{AxCpuMask, IrqNotify, TaskInner, WaitQueue};
25use axpoll::{IoEvents, PollSet};
26pub use rdif_serial::{Config, ConfigError, DataBits, Parity, RxFlag, StopBits};
27
28pub(crate) use self::log_mailbox::{LogRecord, LogRecordKind};
29use self::{
30 control::{ControlOp, ControlQueue},
31 ingress::TxIngress,
32 log_mailbox::{LogMailbox, LogRecordMeta},
33 spsc::{Consumer as SpscConsumer, Producer as SpscProducer},
34 state::{SerialIrqLatch, SerialStatsAtomic},
35 worker::SerialWorker,
36};
37use crate::{RuntimeError, RuntimeResult, sync::SpinLock};
38
39const NO_ACTIVE_CONSOLE: usize = usize::MAX;
40const IRQ_RX_CAPACITY: usize = 16_384;
41const SUBSCRIPTION_RX_CAPACITY: usize = 4_096;
42const LOG_SUBSCRIPTION_CAPACITY: usize = 64;
43
44static SERIAL_RUNTIMES: OnceLock<Box<[SerialRuntimeHandle]>> = OnceLock::new();
45static LOG_MAILBOX: OnceLock<Arc<LogMailbox>> = OnceLock::new();
46static ACTIVE_CONSOLE: AtomicUsize = AtomicUsize::new(NO_ACTIVE_CONSOLE);
47
48const RUNTIME_DORMANT: u8 = 0;
49const RUNTIME_STARTED: u8 = 1;
50const RUNTIME_FAILED_CLOSED: u8 = 2;
51
52struct RuntimeLifecycle(AtomicU8);
53
54impl RuntimeLifecycle {
55 const fn new() -> Self {
56 Self(AtomicU8::new(RUNTIME_DORMANT))
57 }
58
59 fn started(&self) -> bool {
60 self.0.load(Ordering::Acquire) == RUNTIME_STARTED
61 }
62
63 fn ensure_available(&self) -> RuntimeResult {
64 (self.0.load(Ordering::Acquire) != RUNTIME_FAILED_CLOSED)
65 .then_some(())
66 .ok_or(RuntimeError::ConsoleFailedClosed)
67 }
68
69 fn ensure_started(&self) -> RuntimeResult {
70 match self.0.load(Ordering::Acquire) {
71 RUNTIME_STARTED => Ok(()),
72 RUNTIME_FAILED_CLOSED => Err(RuntimeError::ConsoleFailedClosed),
73 RUNTIME_DORMANT => Err(RuntimeError::SerialNotStarted),
74 _ => unreachable!(),
75 }
76 }
77
78 fn set_started(&self, started: bool) {
79 let next = if started {
80 RUNTIME_STARTED
81 } else {
82 RUNTIME_DORMANT
83 };
84 let _ = self
85 .0
86 .try_update(Ordering::AcqRel, Ordering::Acquire, |state| {
87 (state != RUNTIME_FAILED_CLOSED).then_some(next)
88 });
89 }
90
91 fn fail_closed(&self) {
92 self.0.store(RUNTIME_FAILED_CLOSED, Ordering::Release);
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum RxItem {
98 Byte { byte: u8, flag: RxFlag },
99 Overrun,
100}
101
102impl Default for RxItem {
103 fn default() -> Self {
104 Self::Byte {
105 byte: 0,
106 flag: RxFlag::Normal,
107 }
108 }
109}
110
111struct RuntimeIrqBridge {
112 latch: SerialIrqLatch,
113 rx_overflow: AtomicBool,
114 notify: IrqNotify,
115}
116
117impl RuntimeIrqBridge {
118 const fn new() -> Self {
119 Self {
120 latch: SerialIrqLatch::new(),
121 rx_overflow: AtomicBool::new(false),
122 notify: IrqNotify::new(),
123 }
124 }
125}
126
127struct RuntimeShared {
128 index: usize,
129 info: SerialDeviceInfo,
130 owner_cpu: usize,
131 polling: bool,
132 port: SpinLock<Box<dyn rdif_serial::UartPort>>,
133 register_gate: Arc<rdif_serial::UartRegisterGate<dyn rdif_serial::UartEmergencyTx>>,
134 ingress: TxIngress,
135 log_mailbox: Arc<LogMailbox>,
136 rx_subscription: SpinLock<Option<SpscConsumer<RxItem>>>,
137 log_subscription: SpinLock<Option<SpscConsumer<LogRecord>>>,
138 log_subscription_gate: SpinLock<()>,
139 log_subscription_active: AtomicBool,
140 log_subscription_dropped_records: AtomicUsize,
141 log_subscription_dropped_bytes: AtomicUsize,
142 control: ControlQueue,
143 bridge: Arc<RuntimeIrqBridge>,
144 stats: Arc<SerialStatsAtomic>,
145 rx_source: Arc<PollSet>,
146 tx_source: Arc<PollSet>,
147 rx_progress: WaitQueue,
148 console_progress: WaitQueue,
149 tx_progress: WaitQueue,
150 tty_output_lock: Mutex<()>,
151 log_barriers: AtomicUsize,
152 lifecycle: RuntimeLifecycle,
153 irq_handle: OnceLock<ax_hal::irq::IrqHandle>,
154}
155
156impl RuntimeShared {
157 fn with_port<R>(&self, access: impl FnOnce(&mut dyn rdif_serial::UartPort) -> R) -> Option<R> {
160 let mut port = self.port.lock_irqsave();
161 let _register_access = loop {
162 if self.register_gate.emergency_active() {
163 return None;
164 }
165 if let Some(access) = self.register_gate.try_enter() {
166 break access;
167 }
168 core::hint::spin_loop();
169 };
170 Some(access(&mut **port))
171 }
172
173 fn started(&self) -> bool {
174 self.lifecycle.started()
175 }
176
177 fn ensure_started(&self) -> RuntimeResult {
178 self.lifecycle.ensure_started()
179 }
180
181 fn set_started(&self, started: bool) {
182 self.lifecycle.set_started(started);
183 if !started {
184 self.rx_progress.notify_all(true);
185 self.console_progress.notify_all(true);
186 self.tx_progress.notify_all(true);
187 }
188 }
189
190 fn fail_closed(&self) {
191 self.lifecycle.fail_closed();
192 self.disable_irq();
193 while !self.register_gate.emergency_active() {
199 if let Some(access) = self.register_gate.try_begin_emergency() {
200 drop(access);
201 break;
202 }
203 core::hint::spin_loop();
204 }
205 self.ingress.stop_and_discard();
206 self.rx_progress.notify_all(true);
207 self.console_progress.notify_all(true);
208 self.tx_progress.notify_all(true);
209 }
210
211 fn publish_tx_space(&self) {
212 self.tx_progress.notify_all(true);
213 unsafe { self.tx_source.wake(IoEvents::OUT) };
216 }
217
218 fn enable_irq(&self) -> RuntimeResult {
219 let Some(handle) = self.irq_handle.get().copied() else {
220 return Ok(());
221 };
222 ax_hal::irq::enable_irq(handle).map_err(|error| {
223 warn!(
224 "failed to enable serial IRQ for {}: {error:?}",
225 self.info.name
226 );
227 RuntimeError::from(error)
228 })
229 }
230
231 fn disable_irq(&self) {
232 let Some(handle) = self.irq_handle.get().copied() else {
233 return;
234 };
235 if let Err(err) = ax_hal::irq::disable_irq(handle) {
236 warn!(
237 "failed to disable serial IRQ for {}: {err:?}",
238 self.info.name
239 );
240 }
241 }
242}
243
244#[derive(Clone)]
246pub struct SerialRuntimeHandle {
247 shared: Arc<RuntimeShared>,
248}
249
250impl SerialRuntimeHandle {
251 pub fn info(&self) -> &SerialDeviceInfo {
252 &self.shared.info
253 }
254
255 pub fn take_rx_subscription(&self) -> Option<SerialRxSubscription> {
260 self.shared.lifecycle.ensure_available().ok()?;
261 let consumer = self.shared.rx_subscription.lock_irqsave().take()?;
262 Some(SerialRxSubscription {
263 consumer: SpinLock::new(Some(consumer)),
264 shared: self.shared.clone(),
265 })
266 }
267
268 pub(crate) fn take_log_subscription(&self) -> Option<SerialLogSubscription> {
269 self.shared.lifecycle.ensure_available().ok()?;
270 let _route = self.shared.log_subscription_gate.lock_irqsave();
271 if self.shared.log_subscription_active.load(Ordering::Acquire) {
272 return None;
273 }
274 let mut available = self.shared.log_subscription.lock_irqsave();
275 let mut consumer = available.take()?;
276 consumer.clear();
277 self.shared
278 .log_subscription_dropped_records
279 .store(0, Ordering::Release);
280 self.shared
281 .log_subscription_dropped_bytes
282 .store(0, Ordering::Release);
283 self.shared
284 .log_subscription_active
285 .store(true, Ordering::Release);
286 Some(SerialLogSubscription {
287 consumer: SpinLock::new(Some(consumer)),
288 shared: self.shared.clone(),
289 })
290 }
291
292 pub fn task_output(&self) -> SerialTaskOutput {
299 SerialTaskOutput {
300 shared: self.shared.clone(),
301 }
302 }
303
304 pub fn start(&self, config: Config) -> RuntimeResult {
305 self.shared.lifecycle.ensure_available()?;
306 self.shared
307 .control
308 .submit(ControlOp::Start(config), &self.shared.bridge.notify)
309 }
310
311 pub fn shutdown(&self) -> RuntimeResult {
312 let result = self
313 .shared
314 .control
315 .submit(ControlOp::Shutdown, &self.shared.bridge.notify);
316 if result.is_ok() {
317 deactivate_console(&self.shared);
318 }
319 result
320 }
321
322 pub fn set_config(&self, config: Config) -> RuntimeResult {
323 self.output_barrier()?.set_config(config)
324 }
325
326 pub(crate) fn output_barrier(&self) -> RuntimeResult<SerialOutputBarrier> {
328 self.shared.ensure_started()?;
329 Ok(SerialOutputBarrier::new(self.shared.clone()))
330 }
331
332 pub(crate) fn begin_console_handoff(&self) -> RuntimeResult {
334 ax_hal::console::begin_runtime_handoff()?;
335 Ok(())
336 }
337
338 pub(crate) fn adopt_prepared_console(&self) -> RuntimeResult {
346 self.shared
347 .control
348 .submit(ControlOp::AdoptFirmwareConsole, &self.shared.bridge.notify)
349 }
350
351 pub(crate) fn fail_console_closed(&self) {
354 self.shared.fail_closed();
355 }
356
357 pub(crate) fn commit_console_handoff(&self) -> RuntimeResult {
359 self.shared.ensure_started()?;
360 if !self.shared.log_mailbox.claim(self.shared.index) {
365 let _ = self.shutdown();
366 return Err(RuntimeError::SerialConsoleBusy);
367 }
368 if ACTIVE_CONSOLE
369 .compare_exchange(
370 NO_ACTIVE_CONSOLE,
371 self.shared.index,
372 Ordering::AcqRel,
373 Ordering::Acquire,
374 )
375 .is_err()
376 {
377 self.shared.log_mailbox.release(self.shared.index);
378 let _ = self.shutdown();
379 return Err(RuntimeError::SerialConsoleBusy);
380 }
381 if let Err(error) = ax_hal::console::commit_runtime_handoff() {
382 let _ = ACTIVE_CONSOLE.compare_exchange(
383 self.shared.index,
384 NO_ACTIVE_CONSOLE,
385 Ordering::AcqRel,
386 Ordering::Acquire,
387 );
388 self.shared.log_mailbox.release(self.shared.index);
389 let _ = self.shutdown();
390 return Err(error.into());
391 }
392 self.shared.bridge.notify.notify();
393 Ok(())
394 }
395}
396
397#[derive(Clone)]
399pub(crate) struct SerialTxSender {
400 shared: Arc<RuntimeShared>,
401}
402
403impl SerialTxSender {
404 pub fn try_write(&self, bytes: &[u8]) -> RuntimeResult<usize> {
405 if bytes.is_empty() {
406 return Ok(0);
407 }
408 self.shared.ensure_started()?;
409 let accepted = self
410 .shared
411 .ingress
412 .try_write(bytes, &self.shared.bridge.notify);
413 if accepted == 0 {
414 Err(RuntimeError::WouldBlock)
415 } else {
416 Ok(accepted)
417 }
418 }
419
420 pub fn wait_writable(&self) -> RuntimeResult {
421 self.shared.ensure_started()?;
422 self.shared
423 .tx_progress
424 .wait_until(|| self.shared.ingress.write_room() > 0 || !self.shared.started());
425 self.shared
426 .started()
427 .then_some(())
428 .ok_or(RuntimeError::SerialNotStarted)
429 }
430
431 pub fn write_all(&self, bytes: &[u8]) -> RuntimeResult<usize> {
433 self.write_all_with(bytes, |shared, remaining| {
434 shared.ingress.try_write(remaining, &shared.bridge.notify)
435 })
436 }
437
438 pub fn write_text_all(&self, bytes: &[u8]) -> RuntimeResult<usize> {
440 self.write_all_with(bytes, |shared, remaining| {
441 shared
442 .ingress
443 .try_write_text(remaining, &shared.bridge.notify)
444 })
445 }
446
447 fn write_all_with(
448 &self,
449 bytes: &[u8],
450 submit: impl Fn(&RuntimeShared, &[u8]) -> usize,
451 ) -> RuntimeResult<usize> {
452 let mut written = 0;
453 while written < bytes.len() {
454 self.shared.ensure_started()?;
455 let accepted = submit(&self.shared, &bytes[written..]);
456 if accepted == 0 {
457 self.wait_writable()?;
458 } else {
459 written += accepted;
460 }
461 }
462 Ok(written)
463 }
464}
465
466pub(crate) struct SerialOutputBarrier {
468 shared: Arc<RuntimeShared>,
469}
470
471impl SerialOutputBarrier {
472 fn new(shared: Arc<RuntimeShared>) -> Self {
473 shared.log_barriers.fetch_add(1, Ordering::AcqRel);
474 shared.bridge.notify.notify();
475 Self { shared }
476 }
477
478 pub fn wait_idle(&self) -> RuntimeResult {
481 self.shared.ensure_started()?;
482 self.shared.control.submit_drain(&self.shared.bridge.notify)
483 }
484
485 pub fn set_config(&self, config: Config) -> RuntimeResult {
487 self.shared.ensure_started()?;
488 self.shared
489 .control
490 .submit(ControlOp::SetConfig(config), &self.shared.bridge.notify)
491 }
492}
493
494impl Drop for SerialOutputBarrier {
495 fn drop(&mut self) {
496 self.shared.log_barriers.fetch_sub(1, Ordering::AcqRel);
497 self.shared.bridge.notify.notify();
498 }
499}
500
501pub struct SerialRxSubscription {
503 consumer: SpinLock<Option<SpscConsumer<RxItem>>>,
504 shared: Arc<RuntimeShared>,
505}
506
507pub(crate) struct SerialLogSubscription {
509 consumer: SpinLock<Option<SpscConsumer<LogRecord>>>,
510 shared: Arc<RuntimeShared>,
511}
512
513impl SerialLogSubscription {
514 pub(crate) fn try_read(&self) -> Option<LogRecord> {
515 self.consumer.lock_irqsave().as_mut()?.pop()
516 }
517
518 pub(crate) fn dropped(&self) -> (usize, usize) {
519 (
520 self.shared
521 .log_subscription_dropped_records
522 .swap(0, Ordering::AcqRel),
523 self.shared
524 .log_subscription_dropped_bytes
525 .swap(0, Ordering::AcqRel),
526 )
527 }
528
529 pub(crate) fn wait_readable(&self) -> RuntimeResult {
530 self.shared.ensure_started()?;
531 self.shared.console_progress.wait_until(|| {
532 self.has_pending()
533 || !self.shared.log_subscription_active.load(Ordering::Acquire)
534 || !self.shared.started()
535 });
536 self.has_pending()
537 .then_some(())
538 .ok_or(RuntimeError::SerialNotStarted)
539 }
540
541 pub(crate) fn has_pending(&self) -> bool {
542 self.shared
543 .log_subscription_dropped_records
544 .load(Ordering::Acquire)
545 != 0
546 || self
547 .consumer
548 .lock_irqsave()
549 .as_ref()
550 .is_some_and(|consumer| !consumer.is_empty())
551 }
552}
553
554impl Drop for SerialLogSubscription {
555 fn drop(&mut self) {
556 let _route = self.shared.log_subscription_gate.lock_irqsave();
557 self.shared
558 .log_subscription_active
559 .store(false, Ordering::Release);
560 let Some(mut consumer) = self.consumer.get_mut().take() else {
561 return;
562 };
563 consumer.clear();
564 let mut available = self.shared.log_subscription.lock_irqsave();
565 debug_assert!(
566 available.is_none(),
567 "serial runtime cannot have two log consumers"
568 );
569 if available.is_none() {
570 *available = Some(consumer);
571 }
572 self.shared.console_progress.notify_all(true);
573 self.shared.bridge.notify.notify();
574 }
575}
576
577#[derive(Clone)]
579pub struct SerialTaskOutput {
580 shared: Arc<RuntimeShared>,
581}
582
583impl SerialTaskOutput {
584 pub fn try_write(&self, bytes: &[u8]) -> RuntimeResult<usize> {
585 let Some(_output) = self.shared.tty_output_lock.try_lock() else {
586 return Err(RuntimeError::WouldBlock);
587 };
588 SerialTxSender {
589 shared: self.shared.clone(),
590 }
591 .try_write(bytes)
592 }
593
594 pub fn write_all(&self, bytes: &[u8]) -> RuntimeResult<usize> {
595 let _output = self.shared.tty_output_lock.lock();
596 SerialTxSender {
597 shared: self.shared.clone(),
598 }
599 .write_all(bytes)
600 }
601
602 pub fn write_text_all(&self, bytes: &[u8]) -> RuntimeResult<usize> {
603 let _output = self.shared.tty_output_lock.lock();
604 SerialTxSender {
605 shared: self.shared.clone(),
606 }
607 .write_text_all(bytes)
608 }
609
610 pub fn write_fmt(&self, args: fmt::Arguments<'_>) -> fmt::Result {
611 let _output = self.shared.tty_output_lock.lock();
612 let mut writer = ActiveConsoleWriter {
613 sender: SerialTxSender {
614 shared: self.shared.clone(),
615 },
616 };
617 writer.write_fmt(args)
618 }
619
620 pub fn wait_idle(&self) -> RuntimeResult {
621 let _output = self.shared.tty_output_lock.lock();
622 SerialOutputBarrier::new(self.shared.clone()).wait_idle()
623 }
624
625 pub fn discard_pending(&self) -> RuntimeResult {
626 let _output = self.shared.tty_output_lock.lock();
627 self.shared.ensure_started()?;
628 self.shared
629 .control
630 .submit(ControlOp::DiscardTx, &self.shared.bridge.notify)
631 }
632
633 pub fn reconfigure(
634 &self,
635 config: Option<Config>,
636 drain: bool,
637 publish: impl FnOnce(),
638 ) -> RuntimeResult {
639 let _output = self.shared.tty_output_lock.lock();
640 let barrier = SerialOutputBarrier::new(self.shared.clone());
641 if drain {
642 barrier.wait_idle()?;
643 }
644 if let Some(config) = config {
645 barrier.set_config(config)?;
646 }
647 publish();
648 Ok(())
649 }
650
651 pub fn poll_source(&self) -> Arc<PollSet> {
652 self.shared.tx_source.clone()
653 }
654}
655
656impl SerialRxSubscription {
657 pub fn drain(&self, out: &mut [RxItem]) -> usize {
658 let count = {
659 let mut subscription = self.consumer.lock_irqsave();
660 let Some(consumer) = subscription.as_mut() else {
664 return 0;
665 };
666 consumer.drain(out)
667 };
668 notify_drained_space(count, || self.shared.bridge.notify.notify());
669 count
670 }
671
672 pub fn wait_readable(&self) -> RuntimeResult {
674 self.shared.ensure_started()?;
675 self.shared.rx_progress.wait_until(|| {
676 self.consumer
677 .lock_irqsave()
678 .as_ref()
679 .is_some_and(|consumer| !consumer.is_empty())
680 || !self.shared.started()
681 });
682 self.consumer
683 .lock_irqsave()
684 .as_ref()
685 .is_some_and(|consumer| !consumer.is_empty())
686 .then_some(())
687 .ok_or(RuntimeError::SerialNotStarted)
688 }
689
690 pub fn discard_pending(&self) -> RuntimeResult {
691 self.shared.ensure_started()?;
692 self.clear_pending();
693 let result = self
694 .shared
695 .control
696 .submit(ControlOp::DiscardRx, &self.shared.bridge.notify);
697 self.clear_pending();
698 result
699 }
700
701 pub fn poll_source(&self) -> Arc<PollSet> {
702 self.shared.rx_source.clone()
703 }
704
705 pub(crate) fn wait_console_event(&self, logs: &SerialLogSubscription) -> RuntimeResult {
706 if !Arc::ptr_eq(&self.shared, &logs.shared) {
707 return Err(RuntimeError::OperationNotSupported);
708 }
709 self.shared.ensure_started()?;
710 self.shared
711 .console_progress
712 .wait_until(|| self.has_pending() || logs.has_pending() || !self.shared.started());
713 (self.has_pending() || logs.has_pending())
714 .then_some(())
715 .ok_or(RuntimeError::SerialNotStarted)
716 }
717
718 fn has_pending(&self) -> bool {
719 self.consumer
720 .lock_irqsave()
721 .as_ref()
722 .is_some_and(|consumer| !consumer.is_empty())
723 }
724
725 fn clear_pending(&self) {
726 if let Some(consumer) = self.consumer.lock_irqsave().as_mut() {
727 consumer.clear();
728 }
729 self.shared.bridge.notify.notify();
730 }
731}
732
733impl Drop for SerialRxSubscription {
734 fn drop(&mut self) {
735 let Some(consumer) = self.consumer.get_mut().take() else {
736 return;
737 };
738 let mut available = self.shared.rx_subscription.lock_irqsave();
739 debug_assert!(
740 available.is_none(),
741 "serial runtime cannot have two RX consumers"
742 );
743 if available.is_none() {
744 *available = Some(consumer);
745 }
746 }
747}
748
749fn notify_drained_space(count: usize, notify_space: impl FnOnce()) {
750 if count != 0 {
751 notify_space();
752 }
753}
754
755pub fn runtimes() -> &'static [SerialRuntimeHandle] {
756 SERIAL_RUNTIMES.get().map_or(&[], Box::as_ref)
757}
758
759pub(crate) fn active_console() -> Option<&'static SerialRuntimeHandle> {
760 runtimes().get(ACTIVE_CONSOLE.load(Ordering::Acquire))
761}
762
763pub(crate) fn init(primary_cpu: usize) {
764 let log_mailbox = LOG_MAILBOX
765 .call_once(|| Arc::new(LogMailbox::new(ax_hal::cpu_num().max(1))))
766 .clone();
767 log_mailbox.mark_wake_ready(primary_cpu);
770 let mut handles = Vec::new();
771 for serial in ax_driver::serial::take_serial_devices() {
772 match build_runtime(handles.len(), primary_cpu, serial, log_mailbox.clone()) {
773 Ok(handle) => handles.push(handle),
774 Err(err) => warn!("failed to initialize serial runtime: {err:?}"),
775 }
776 }
777 SERIAL_RUNTIMES.call_once(|| handles.into_boxed_slice());
778}
779
780#[cfg(feature = "smp")]
781pub(crate) fn mark_log_wake_ready(cpu_id: usize) {
782 if let Some(log_mailbox) = LOG_MAILBOX.get() {
783 log_mailbox.mark_wake_ready(cpu_id);
784 }
785}
786
787fn build_runtime(
788 index: usize,
789 primary_cpu: usize,
790 serial: SerialDevice,
791 log_mailbox: Arc<LogMailbox>,
792) -> RuntimeResult<SerialRuntimeHandle> {
793 let SerialDevice {
794 info,
795 port,
796 mut irq,
797 register_gate,
798 } = serial;
799 let polling = info.irq.is_none();
800 let bridge = Arc::new(RuntimeIrqBridge::new());
801 let stats = Arc::new(SerialStatsAtomic::new());
802 let register_gate: Arc<rdif_serial::UartRegisterGate<dyn rdif_serial::UartEmergencyTx>> =
803 Arc::from(register_gate);
804 let (irq_rx_producer, irq_rx_consumer) = spsc::channel(IRQ_RX_CAPACITY);
805 let (rx_output_producer, rx_output_consumer) = spsc::channel(SUBSCRIPTION_RX_CAPACITY);
806 let (log_subscription_producer, log_subscription_consumer) =
807 spsc::channel(LOG_SUBSCRIPTION_CAPACITY);
808 let shared = Arc::new(RuntimeShared {
809 index,
810 info,
811 owner_cpu: primary_cpu,
812 polling,
813 port: SpinLock::new(port),
814 register_gate: register_gate.clone(),
815 ingress: TxIngress::new(),
816 log_mailbox,
817 rx_subscription: SpinLock::new(Some(rx_output_consumer)),
818 log_subscription: SpinLock::new(Some(log_subscription_consumer)),
819 log_subscription_gate: SpinLock::new(()),
820 log_subscription_active: AtomicBool::new(false),
821 log_subscription_dropped_records: AtomicUsize::new(0),
822 log_subscription_dropped_bytes: AtomicUsize::new(0),
823 control: ControlQueue::new(),
824 bridge: bridge.clone(),
825 stats: stats.clone(),
826 rx_source: Arc::new(PollSet::new()),
827 tx_source: Arc::new(PollSet::new()),
828 rx_progress: WaitQueue::new(),
829 console_progress: WaitQueue::new(),
830 tx_progress: WaitQueue::new(),
831 tty_output_lock: Mutex::new(()),
832 log_barriers: AtomicUsize::new(0),
833 lifecycle: RuntimeLifecycle::new(),
834 irq_handle: OnceLock::new(),
835 });
836
837 let worker = SerialWorker::new(
838 shared.clone(),
839 irq_rx_consumer,
840 rx_output_producer,
841 log_subscription_producer,
842 );
843 let task = TaskInner::new(
844 move || worker.run(),
845 alloc::format!("serial{index}-maint"),
846 ax_task::default_task_stack_size(),
847 );
848 task.set_cpumask(AxCpuMask::one_shot(primary_cpu));
849
850 if let Some(binding) = shared.info.irq.clone() {
851 let irq_id = crate::irq::resolve_binding_irq(binding).map_err(|error| {
852 warn!(
853 "failed to resolve serial IRQ for {}: {error:?}",
854 shared.info.name
855 );
856 RuntimeError::from(error)
857 })?;
858 let callback_bridge = bridge;
859 let callback_stats = stats;
860 let mut callback_rx = RuntimeIrqPublisher {
861 producer: irq_rx_producer,
862 bridge: callback_bridge.clone(),
863 stats: callback_stats.clone(),
864 };
865 let callback_gate = register_gate;
866 let request = serial_irq_request(
867 ax_hal::irq::IrqRequest::new(move |_| {
868 let Some(_register_access) = callback_gate.try_enter() else {
869 return ax_hal::irq::IrqReturn::Unhandled;
870 };
871 let Some(report) = irq.handle() else {
872 callback_stats.spurious_irq();
873 return ax_hal::irq::IrqReturn::Unhandled;
874 };
875 let event = callback_rx.publish(report);
876 mask_deferred_irq_rx(&mut *irq, event);
877 callback_stats.handled_irq(event);
878 callback_bridge.latch.publish(event);
879 callback_bridge.notify.notify_irq();
880 ax_hal::irq::IrqReturn::Handled
881 }),
882 primary_cpu,
883 );
884 let handle = ax_hal::irq::request_irq(irq_id, request).map_err(|error| {
885 warn!(
886 "failed to register serial IRQ for {}: {error:?}",
887 shared.info.name
888 );
889 RuntimeError::from(error)
890 })?;
891 shared.irq_handle.call_once(|| handle);
892 }
893
894 ax_task::spawn_task(task);
895 info!(
896 "serial runtime {} ready: cpu={}, irq={:?}, polling={}",
897 shared.info.name, shared.owner_cpu, shared.info.irq, shared.polling
898 );
899 Ok(SerialRuntimeHandle { shared })
900}
901
902fn serial_irq_request(
903 request: ax_hal::irq::IrqRequest,
904 primary_cpu: usize,
905) -> ax_hal::irq::IrqRequest {
906 request
907 .share_mode(ax_hal::irq::ShareMode::Shared)
908 .affinity(ax_hal::irq::IrqAffinity::Fixed(ax_hal::irq::CpuId(
909 primary_cpu,
910 )))
911 .auto_enable(ax_hal::irq::AutoEnable::No)
912}
913
914struct RuntimeIrqPublisher {
915 producer: SpscProducer<rdif_serial::RxSample>,
916 bridge: Arc<RuntimeIrqBridge>,
917 stats: Arc<SerialStatsAtomic>,
918}
919
920impl RuntimeIrqPublisher {
921 fn publish(&mut self, mut report: rdif_serial::SerialIrqReport) -> rdif_serial::SerialIrqEvent {
922 for &sample in report.rx.as_slice() {
926 if self.producer.push(sample).is_err() {
927 self.stats.add_rx_dropped(1);
928 self.bridge.rx_overflow.store(true, Ordering::Release);
929 report.event.rx_errors |= rdif_serial::RxErrorFlags::OVERRUN;
930 report.event.rearm |= rdif_serial::SerialEventSet::RX;
931 }
932 }
933 report.event
934 }
935}
936
937fn mask_deferred_irq_rx(irq: &mut dyn rdif_serial::UartIrq, event: rdif_serial::SerialIrqEvent) {
938 if event.rearm.intersects(rdif_serial::SerialEventSet::RX) {
939 irq.mask(rdif_serial::SerialEventSet::RX);
940 }
941}
942
943pub(crate) fn try_publish_record(
945 meta: ax_log::RecordMeta,
946 args: fmt::Arguments<'_>,
947) -> Option<ax_log::PublishStatus> {
948 let index = ACTIVE_CONSOLE.load(Ordering::Acquire);
949 let runtime = runtimes().get(index)?;
950 let guard = ax_task::sync::PreemptIrqSaveGuard::new();
951 let (outcome, log_wake_ready) = unsafe {
954 ax_hal::percpu::with_cpu_pin(|pin| {
955 let cpu_id = ax_hal::percpu::this_cpu_id_pinned(pin);
956 let current = ax_task::current_may_uninit();
957 let task_id = current.as_ref().map(|task| task.id().as_u64());
958 let timestamp_nanos = ax_hal::time::monotonic_time().as_nanos() as u64;
959 let record_meta = match meta.kind() {
960 ax_log::RecordKind::Print => LogRecordMeta::print(timestamp_nanos, task_id),
961 ax_log::RecordKind::Log => LogRecordMeta::log(timestamp_nanos, task_id),
962 };
963 (
964 runtime
965 .shared
966 .log_mailbox
967 .try_publish(cpu_id, record_meta, args),
968 runtime.shared.log_mailbox.wake_ready(cpu_id),
969 )
970 })
971 }
972 .unwrap_or_else(|_| (log_mailbox::PublishOutcome::dropped(0), false));
973 drop(guard);
974 runtime
975 .shared
976 .stats
977 .add_log_dropped(outcome.dropped_source_bytes());
978 runtime
979 .shared
980 .stats
981 .add_log_dropped_records(outcome.dropped_records());
982 match record_wake_context(
983 outcome.published(),
984 ax_hal::irq::in_irq_context(),
985 log_wake_ready,
986 ) {
987 RecordWakeContext::Interrupt => {
988 runtime.shared.bridge.notify.notify_irq();
989 }
990 RecordWakeContext::Task => {
991 runtime.shared.bridge.notify.notify();
992 }
993 RecordWakeContext::None => {}
994 }
995 Some(if !outcome.published() {
996 ax_log::PublishStatus::Dropped
997 } else if outcome.truncated() {
998 ax_log::PublishStatus::Truncated
999 } else {
1000 ax_log::PublishStatus::Published
1001 })
1002}
1003
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005enum RecordWakeContext {
1006 None,
1007 Interrupt,
1008 Task,
1009}
1010
1011const fn record_wake_context(
1012 published: bool,
1013 in_irq_context: bool,
1014 log_wake_ready: bool,
1015) -> RecordWakeContext {
1016 if !published || !log_wake_ready {
1017 RecordWakeContext::None
1018 } else if in_irq_context {
1019 RecordWakeContext::Interrupt
1020 } else {
1021 RecordWakeContext::Task
1022 }
1023}
1024
1025pub(crate) fn emergency_write(args: fmt::Arguments<'_>) -> Option<usize> {
1027 let index = ACTIVE_CONSOLE.load(Ordering::Acquire);
1028 let runtime = runtimes().get(index)?;
1029 let Some(_formatting) = EmergencyFormatting::try_enter() else {
1030 runtime.shared.stats.add_log_dropped_records(1);
1031 return Some(0);
1032 };
1033 let Some(register_access) = claim_emergency_registers(&runtime.shared.register_gate) else {
1034 runtime.shared.stats.add_log_dropped_records(1);
1035 return Some(0);
1036 };
1037 let mut writer = EmergencyWriter::new(register_access);
1038 if writer.write_fmt(args).is_err() {
1039 runtime.shared.stats.add_log_dropped_records(1);
1040 }
1041 Some(writer.source_written)
1042}
1043
1044const EMERGENCY_CLAIM_ATTEMPTS: usize = 4096;
1045static EMERGENCY_FORMATTING: AtomicBool = AtomicBool::new(false);
1046
1047fn claim_emergency_registers(
1048 gate: &rdif_serial::UartRegisterGate<dyn rdif_serial::UartEmergencyTx>,
1049) -> Option<rdif_serial::UartEmergencyAccess<'_, dyn rdif_serial::UartEmergencyTx>> {
1050 for _ in 0..EMERGENCY_CLAIM_ATTEMPTS {
1051 if let Some(access) = gate.try_begin_emergency() {
1052 return Some(access);
1053 }
1054 core::hint::spin_loop();
1055 }
1056 None
1057}
1058
1059struct EmergencyFormatting;
1060
1061impl EmergencyFormatting {
1062 fn try_enter() -> Option<Self> {
1063 EMERGENCY_FORMATTING
1064 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
1065 .ok()
1066 .map(|_| Self)
1067 }
1068}
1069
1070impl Drop for EmergencyFormatting {
1071 fn drop(&mut self) {
1072 EMERGENCY_FORMATTING.store(false, Ordering::Release);
1073 }
1074}
1075
1076struct EmergencyWriter<'a, E: rdif_serial::UartEmergencyTx + ?Sized> {
1077 access: rdif_serial::UartEmergencyAccess<'a, E>,
1078 source_written: usize,
1079}
1080
1081impl<'a, E: rdif_serial::UartEmergencyTx + ?Sized> EmergencyWriter<'a, E> {
1082 const fn new(access: rdif_serial::UartEmergencyAccess<'a, E>) -> Self {
1083 Self {
1084 access,
1085 source_written: 0,
1086 }
1087 }
1088
1089 fn write_all_blocking(&self, mut bytes: &[u8]) {
1090 while !bytes.is_empty() {
1091 let written = self.access.try_write(bytes).min(bytes.len());
1092 if written == 0 {
1093 core::hint::spin_loop();
1094 } else {
1095 bytes = &bytes[written..];
1096 }
1097 }
1098 }
1099}
1100
1101impl<E: rdif_serial::UartEmergencyTx + ?Sized> Write for EmergencyWriter<'_, E> {
1102 fn write_str(&mut self, text: &str) -> fmt::Result {
1103 let mut remaining = text.as_bytes();
1104 while let Some(newline) = remaining.iter().position(|&byte| byte == b'\n') {
1105 self.write_all_blocking(&remaining[..newline]);
1106 self.write_all_blocking(b"\r\n");
1107 remaining = &remaining[newline + 1..];
1108 }
1109 self.write_all_blocking(remaining);
1110 self.source_written = self.source_written.saturating_add(text.len());
1111 Ok(())
1112 }
1113}
1114
1115fn deactivate_console(shared: &RuntimeShared) {
1116 if ACTIVE_CONSOLE
1117 .compare_exchange(
1118 shared.index,
1119 NO_ACTIVE_CONSOLE,
1120 Ordering::AcqRel,
1121 Ordering::Acquire,
1122 )
1123 .is_ok()
1124 {
1125 shared.log_mailbox.release(shared.index);
1126 shared.bridge.notify.notify();
1127 }
1128}
1129
1130struct ActiveConsoleWriter {
1131 sender: SerialTxSender,
1132}
1133
1134impl Write for ActiveConsoleWriter {
1135 fn write_str(&mut self, text: &str) -> fmt::Result {
1136 self.sender
1137 .write_text_all(text.as_bytes())
1138 .map(|_| ())
1139 .map_err(|_| fmt::Error)
1140 }
1141}
1142
1143#[cfg(test)]
1144mod tests {
1145 use super::*;
1146
1147 struct ChunkedEmergencyTx(&'static AtomicUsize);
1148
1149 impl rdif_serial::UartEmergencyTx for ChunkedEmergencyTx {
1150 unsafe fn mask_interrupts_unlocked(&self) {}
1151
1152 unsafe fn try_write_unlocked(&self, bytes: &[u8]) -> usize {
1153 let written = bytes.len().min(7);
1154 self.0.fetch_add(written, Ordering::Relaxed);
1155 written
1156 }
1157 }
1158
1159 struct RecordingIrq {
1160 masked: rdif_serial::SerialEventSet,
1161 }
1162
1163 #[test]
1164 fn failed_closed_runtime_cannot_return_to_dormant_or_started() {
1165 let lifecycle = RuntimeLifecycle::new();
1166
1167 assert_eq!(
1168 lifecycle.ensure_started(),
1169 Err(RuntimeError::SerialNotStarted)
1170 );
1171 lifecycle.set_started(true);
1172 assert!(lifecycle.ensure_started().is_ok());
1173
1174 lifecycle.fail_closed();
1175 assert_eq!(
1176 lifecycle.ensure_available(),
1177 Err(RuntimeError::ConsoleFailedClosed)
1178 );
1179 assert_eq!(
1180 lifecycle.ensure_started(),
1181 Err(RuntimeError::ConsoleFailedClosed)
1182 );
1183
1184 lifecycle.set_started(false);
1185 lifecycle.set_started(true);
1186 assert_eq!(
1187 lifecycle.ensure_started(),
1188 Err(RuntimeError::ConsoleFailedClosed)
1189 );
1190 }
1191
1192 impl rdif_serial::UartIrq for RecordingIrq {
1193 fn mask(&mut self, sources: rdif_serial::SerialEventSet) {
1194 self.masked |= sources;
1195 }
1196
1197 fn handle(&mut self) -> Option<rdif_serial::SerialIrqReport> {
1198 None
1199 }
1200 }
1201
1202 #[test]
1203 fn emergency_writer_streams_a_record_larger_than_the_former_buffer() {
1204 static HARDWARE_BYTES: AtomicUsize = AtomicUsize::new(0);
1205
1206 HARDWARE_BYTES.store(0, Ordering::Relaxed);
1207 let gate = rdif_serial::UartRegisterGate::new(ChunkedEmergencyTx(&HARDWARE_BYTES));
1208 let access = gate.try_begin_emergency().expect("emergency takeover");
1209 let mut writer = EmergencyWriter::new(access);
1210 let payload = "x".repeat(2_048);
1211
1212 writer.write_str(&payload).unwrap();
1213 writer.write_str("\nBACKTRACE_END").unwrap();
1214
1215 assert_eq!(writer.source_written, payload.len() + 14);
1216 assert_eq!(HARDWARE_BYTES.load(Ordering::Relaxed), payload.len() + 15);
1217 assert!(gate.try_enter().is_none());
1218 }
1219
1220 #[test]
1221 fn irq_report_drops_only_after_the_preallocated_ring_is_full() {
1222 let bridge = Arc::new(RuntimeIrqBridge::new());
1223 let stats = Arc::new(SerialStatsAtomic::new());
1224 let (producer, mut consumer) = spsc::channel(2);
1225 let mut publisher = RuntimeIrqPublisher {
1226 producer,
1227 bridge: bridge.clone(),
1228 stats: stats.clone(),
1229 };
1230 let samples = [
1231 rdif_serial::RxSample {
1232 byte: Some(1),
1233 ..rdif_serial::RxSample::default()
1234 },
1235 rdif_serial::RxSample {
1236 byte: Some(2),
1237 ..rdif_serial::RxSample::default()
1238 },
1239 rdif_serial::RxSample {
1240 byte: Some(3),
1241 ..rdif_serial::RxSample::default()
1242 },
1243 ];
1244 let mut batch = rdif_serial::IrqRxBatch::new();
1245 for sample in samples {
1246 batch.try_push(sample).unwrap();
1247 }
1248 let event = publisher.publish(rdif_serial::SerialIrqReport::new(
1249 rdif_serial::SerialIrqEvent::default(),
1250 batch,
1251 ));
1252
1253 assert_eq!(consumer.pop().and_then(|sample| sample.byte), Some(1));
1254 assert_eq!(consumer.pop().and_then(|sample| sample.byte), Some(2));
1255 assert!(consumer.pop().is_none());
1256 assert_eq!(stats.snapshot().rx_dropped, 1);
1257 assert!(bridge.rx_overflow.load(Ordering::Acquire));
1258 assert!(event.rx_errors.contains(rdif_serial::RxErrorFlags::OVERRUN));
1259 assert!(event.rearm.contains(rdif_serial::SerialEventSet::RX));
1260 }
1261
1262 #[test]
1263 fn fully_drained_rx_irq_keeps_hardware_source_armed() {
1264 let bridge = Arc::new(RuntimeIrqBridge::new());
1265 let stats = Arc::new(SerialStatsAtomic::new());
1266 let (producer, mut consumer) = spsc::channel(2);
1267 let mut publisher = RuntimeIrqPublisher {
1268 producer,
1269 bridge,
1270 stats,
1271 };
1272 let mut batch = rdif_serial::IrqRxBatch::new();
1273 batch
1274 .try_push(rdif_serial::RxSample {
1275 byte: Some(b'x'),
1276 ..rdif_serial::RxSample::default()
1277 })
1278 .unwrap();
1279
1280 let event = publisher.publish(rdif_serial::SerialIrqReport::new(
1281 rdif_serial::SerialIrqEvent {
1282 events: rdif_serial::SerialEventSet::RX_DATA,
1283 ..rdif_serial::SerialIrqEvent::default()
1284 },
1285 batch,
1286 ));
1287
1288 assert_eq!(consumer.pop().and_then(|sample| sample.byte), Some(b'x'));
1289 assert!(
1290 !event.rearm.contains(rdif_serial::SerialEventSet::RX),
1291 "a drained IRQ must not leave a small UART FIFO masked until the owner task runs"
1292 );
1293 }
1294
1295 #[test]
1296 fn deferred_rx_masks_only_the_uart_source() {
1297 let mut irq = RecordingIrq {
1298 masked: rdif_serial::SerialEventSet::empty(),
1299 };
1300 mask_deferred_irq_rx(
1301 &mut irq,
1302 rdif_serial::SerialIrqEvent {
1303 rearm: rdif_serial::SerialEventSet::RX | rdif_serial::SerialEventSet::TX_SPACE,
1304 ..rdif_serial::SerialIrqEvent::default()
1305 },
1306 );
1307
1308 assert_eq!(irq.masked, rdif_serial::SerialEventSet::RX);
1309 }
1310
1311 #[test]
1312 fn subscription_drain_notifies_a_worker_waiting_for_output_space() {
1313 let (mut producer, consumer) = spsc::channel(1);
1314 producer.push(RxItem::Overrun).unwrap();
1315 let mut consumer = consumer;
1316 let mut item = [RxItem::default()];
1317 let mut notify_count = 0;
1318
1319 let count = consumer.drain(&mut item);
1320 notify_drained_space(count, || notify_count += 1);
1321 assert_eq!(count, 1);
1322 assert_eq!(item, [RxItem::Overrun]);
1323 assert_eq!(notify_count, 1);
1324 }
1325
1326 #[test]
1327 fn serial_irq_stays_disabled_until_the_worker_starts_the_port() {
1328 let request = serial_irq_request(
1329 ax_hal::irq::IrqRequest::new(|_| ax_hal::irq::IrqReturn::Handled),
1330 0,
1331 );
1332
1333 assert_eq!(
1334 request.auto_enable_mode(),
1335 ax_hal::irq::AutoEnable::No,
1336 "the IRQ action must not run before the worker has configured the UART"
1337 );
1338 }
1339
1340 #[test]
1341 fn absent_runtime_console_preserves_early_publication_fallback() {
1342 ACTIVE_CONSOLE.store(NO_ACTIVE_CONSOLE, Ordering::Release);
1343 assert_eq!(
1344 try_publish_record(ax_log::RecordMeta::print(), format_args!("fallback")),
1345 None
1346 );
1347 }
1348
1349 #[test]
1350 fn early_secondary_log_does_not_wake_before_log_wake_ready() {
1351 assert_eq!(
1352 record_wake_context(true, false, false),
1353 RecordWakeContext::None
1354 );
1355 assert_eq!(
1356 record_wake_context(true, false, true),
1357 RecordWakeContext::Task
1358 );
1359 assert_eq!(
1360 record_wake_context(true, true, false),
1361 RecordWakeContext::None
1362 );
1363 assert_eq!(
1364 record_wake_context(true, true, true),
1365 RecordWakeContext::Interrupt
1366 );
1367 }
1368
1369 #[test]
1370 fn wake_ready_transition_preserves_early_secondary_records() {
1371 const OWNER: usize = 7;
1372 let mailbox = Arc::new(LogMailbox::new(2));
1373 assert!(mailbox.claim(OWNER));
1374
1375 let early = mailbox.try_publish(
1376 1,
1377 LogRecordMeta::log(1, None),
1378 format_args!("secondary started\n"),
1379 );
1380 assert!(early.published());
1381 assert_eq!(
1382 record_wake_context(early.published(), false, mailbox.wake_ready(1)),
1383 RecordWakeContext::None
1384 );
1385
1386 mailbox.mark_wake_ready(1);
1387 let ready = mailbox.try_publish(
1388 1,
1389 LogRecordMeta::log(2, Some(8)),
1390 format_args!("secondary init OK\n"),
1391 );
1392 assert!(ready.published());
1393 assert_eq!(
1394 record_wake_context(ready.published(), false, mailbox.wake_ready(1)),
1395 RecordWakeContext::Task
1396 );
1397
1398 let mut reader = mailbox.reader();
1399 assert!(
1400 reader
1401 .take(OWNER)
1402 .is_some_and(|record| record.record.bytes().ends_with(b"secondary started\r\n"))
1403 );
1404 assert!(
1405 reader
1406 .take(OWNER)
1407 .is_some_and(|record| record.record.bytes().ends_with(b"secondary init OK\r\n"))
1408 );
1409 assert!(reader.take(OWNER).is_none());
1410 }
1411}