blob: 102c2d4c0f9fb80e47f16df6bc4275801cb424bd [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
use usart_api::backend::{IrqMask, UsartBackend};
use usart_api::UsartResponseHeader;
use userspace::syscall::{self, Signals};
use userspace::time::Instant;
use crate::{dispatch_request, DispatchOutcome, MAX_REQUEST_SIZE, MAX_RESPONSE_SIZE};
/// Holds at most one in-flight `TryRead` request that is waiting for the RX
/// interrupt to fire before it can be completed.
pub struct PendingRead {
/// IPC channel handle of the waiting client, or `None` if idle.
channel: Option<u32>,
/// Max bytes the client asked for.
requested_size: usize,
}
impl PendingRead {
pub const fn new() -> Self {
Self {
channel: None,
requested_size: 0,
}
}
/// Park a `TryRead` request; the server will complete it on the next RX IRQ.
///
/// Returns `false` if a read is already parked — UART RX is a single serial
/// stream and cannot be multiplexed between concurrent consumers. The
/// caller should respond with `UsartError::Busy` in that case.
pub fn park(&mut self, channel: u32, requested_size: usize) -> bool {
if self.channel.is_some() {
return false;
}
self.channel = Some(channel);
self.requested_size = requested_size;
true
}
/// Take the pending request, leaving the slot empty.
pub fn take(&mut self) -> Option<(u32, usize)> {
self.channel.take().map(|ch| (ch, self.requested_size))
}
pub fn is_pending(&self) -> bool {
self.channel.is_some()
}
}
impl Default for PendingRead {
fn default() -> Self {
Self::new()
}
}
/// Run the USART server dispatch loop forever.
///
/// The caller is responsible for populating `wg` ahead of time. The
/// convention this runtime relies on:
///
/// - For each IPC channel the binary serves, register it with its own
/// handle as `user_data`:
/// `wait_group_add(wg, ch, Signals::READABLE, ch as usize)`.
/// - Register the IRQ with `irq_signals` and `irq` as its `user_data`:
/// `wait_group_add(wg, irq, irq_signals, irq as usize)`.
///
/// The loop then routes wake-ups using `wait_return.user_data` directly:
/// the IRQ branch matches on the IRQ handle, every other wake-up is
/// treated as a channel and `user_data` is the channel handle to
/// read/respond on. This keeps the runtime topology-agnostic — adding
/// another client task is one more `wait_group_add` call in the binary.
pub fn run<B: UsartBackend>(backend: &mut B, wg: u32, irq: u32, irq_signals: Signals) -> ! {
let mut request_buf = [0u8; MAX_REQUEST_SIZE];
let mut response_buf = [0u8; MAX_RESPONSE_SIZE];
let mut pending = PendingRead::new();
let wait_mask = Signals::READABLE | irq_signals;
loop {
let Ok(wait_return) = syscall::object_wait(wg, wait_mask, Instant::MAX) else {
continue;
};
if wait_return.user_data as u32 == irq && wait_return.pending_signals.contains(irq_signals)
{
let acked = wait_return.pending_signals & irq_signals;
let _ = syscall::interrupt_ack(irq, acked);
// If a TryRead is parked, complete it now that data may be ready.
if let Some((client_channel, req_size)) = pending.take() {
let payload_offset = UsartResponseHeader::SIZE;
let payload_capacity = response_buf.len().saturating_sub(payload_offset);
let read_buf_len = core::cmp::min(req_size, payload_capacity);
// Disable the RX interrupt; it will be re-armed if the next
// try_read also finds no data (unlikely at this point).
let _ = backend.disable_interrupts(IrqMask::RX_DATA_AVAILABLE);
let resp_len = match backend
.try_read(&mut response_buf[payload_offset..payload_offset + read_buf_len])
{
Ok(n) => {
let hdr = UsartResponseHeader::success(n as u16);
response_buf[..UsartResponseHeader::SIZE]
.copy_from_slice(zerocopy::IntoBytes::as_bytes(&hdr));
UsartResponseHeader::SIZE + n
}
Err(e) => {
// Still not ready (shouldn't happen after IRQ, but handle gracefully).
// Re-arm and re-park with the original size.
let _ = backend.enable_interrupts(IrqMask::RX_DATA_AVAILABLE);
pending.park(client_channel, req_size);
let _ = e; // suppress
continue;
}
};
let _ = syscall::channel_respond(client_channel, &response_buf[..resp_len]);
}
continue;
}
if !wait_return.pending_signals.contains(Signals::READABLE) {
continue;
}
let channel = wait_return.user_data as u32;
let Ok(req_len) = syscall::channel_read(channel, 0, &mut request_buf) else {
continue;
};
match dispatch_request(
backend,
&mut pending,
channel,
&request_buf[..req_len],
&mut response_buf,
) {
DispatchOutcome::Respond(resp_len) => {
let _ = syscall::channel_respond(channel, &response_buf[..resp_len]);
}
DispatchOutcome::Queued => {
// Response will be sent from the IRQ completion path above.
}
}
}
}