blob: 46b634ff540e5d27e675bf91725779f4ba1ed2ab [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Safe async IPC transaction wrapper, layered on `IpcInitiator`.
//!
//! `AsyncTransaction` wraps the unsafe `async_transact_start`/`_complete`/
//! `_cancel` trio into a state machine (Idle / Pending) that enforces the
//! kernel's one-transaction-per-channel rule locally and keeps the
//! raw-pointer safety contract in one place.
//!
//! Buffers are `&'static` because the kernel holds raw pointers into them
//! for the duration of the transaction. If `AsyncTransaction` owned the buffers
//! inline and the struct moved after `start()`, those pointers would
//! dangle. Static borrows make soundness independent of moves and
//! `mem::forget`.
//!
//! Every exit from Pending returns both buffers to the caller so they can
//! be reused for the next transaction (the standard embedded-DMA ownership
//! pattern).
use super::IpcInitiator;
use pw_status::{Error, Result};
/// Buffers lent to the kernel for the duration of one async transaction.
#[derive(Debug)]
pub struct Buffers {
pub send: &'static mut [u8],
pub recv: &'static mut [u8],
}
/// One-at-a-time async IPC transaction on an `IpcInitiator`.
///
/// Transitions: Idle -> `start()` -> Pending -> `try_recv()`/`cancel()` -> Idle.
pub struct AsyncTransaction<H: IpcInitiator> {
handle: H,
inflight: Option<Buffers>,
}
impl<H: IpcInitiator> AsyncTransaction<H> {
/// Wrap an initiator handle with no outstanding async transaction.
pub fn new(handle: H) -> Self {
Self {
handle,
inflight: None,
}
}
/// The raw channel handle, e.g. to register with a WaitGroup or pass to
/// `object_wait`.
pub fn as_raw(&self) -> u32 {
self.handle.as_raw()
}
/// Whether a transaction is in flight.
pub fn is_pending(&self) -> bool {
self.inflight.is_some()
}
/// Start an async transaction.
///
/// `send` holds the request payload the server will read. `recv` is
/// the buffer the kernel writes the server's response into. Both must
/// be `'static` because the kernel holds raw pointers into them until
/// `try_recv` or `cancel` completes the transaction.
///
/// `send` is taken mutably even though the kernel only reads it: the
/// caller serializes the next request into the same buffer, and a
/// shared borrow handed back would leave it with nowhere to write.
/// Only `send[..send_len]` goes on the wire, so a caller that keeps one
/// buffer sized for its largest request does not transmit the slack
/// behind a short one. `send_len` past the end of `send` is
/// `OutOfRange`.
///
/// On success the buffers are held until `try_recv` or `cancel`
/// returns them. On failure (already pending, or a kernel error) both
/// buffers come back in the `Err` so nothing is lost.
pub fn start(
&mut self,
send: &'static mut [u8],
send_len: usize,
recv: &'static mut [u8],
) -> core::result::Result<(), StartError> {
if self.inflight.is_some() {
return Err(StartError {
error: Error::FailedPrecondition,
send,
recv,
});
}
if send_len > send.len() {
return Err(StartError {
error: Error::OutOfRange,
send,
recv,
});
}
// Safety: send/recv are 'static, so the kernel's raw pointers
// stay valid regardless of what happens to `self`, and they are
// not read, written, or dropped again until try_recv/cancel.
// nosemgrep
let result = unsafe { self.handle.async_transact_start(&send[..send_len], recv) };
match result {
Ok(()) => {
self.inflight = Some(Buffers { send, recv });
Ok(())
}
Err(error) => Err(StartError { error, send, recv }),
}
}
/// Try to complete a pending transaction.
///
/// Returns `Ok(Some(Completion { len, send, recv }))` when the server
/// has responded. `recv[..len]` holds the response payload; both
/// buffers come back so they can be reused.
///
/// Returns `Ok(None)` while the server has not responded yet. The
/// buffers stay held and another `try_recv` is expected after the
/// next READABLE signal.
///
/// Any error ends the transaction and carries the buffers out, so
/// there is no path that strands them. `FailedPrecondition` means no
/// transaction was pending, and that one carries nothing.
pub fn try_recv(&mut self) -> core::result::Result<Option<Completion>, RecvError> {
if self.inflight.is_none() {
return Err(RecvError {
error: Error::FailedPrecondition,
buffers: None,
});
}
match self.handle.async_transact_complete() {
Ok(len) => {
let Buffers { send, recv } = self.inflight.take().unwrap();
Ok(Some(Completion { len, send, recv }))
}
Err(Error::Unavailable) => Ok(None),
Err(error) => {
// The kernel refused for a reason that is not "not yet".
// Take the buffers back rather than leave the caller to
// remember a cancel().
let buffers = self.cancel().ok();
Err(RecvError { error, buffers })
}
}
}
/// Cancel a pending transaction and reclaim the buffers.
///
/// If the server has already responded, the response is silently
/// discarded.
///
/// Returns `Err(Error::FailedPrecondition)` if no transaction is
/// pending. The buffers come back even if the cancel syscall errors: the
/// kernel clears its transaction slot on every path, so it no longer
/// holds pointers into them.
pub fn cancel(&mut self) -> Result<Buffers> {
let Some(buffers) = self.inflight.take() else {
return Err(Error::FailedPrecondition);
};
// Only failure for a started transaction is Unavailable, meaning the
// transaction was already dropped. The buffers are free either way.
let _ = self.handle.async_cancel();
Ok(buffers)
}
}
impl<H: IpcInitiator> Drop for AsyncTransaction<H> {
fn drop(&mut self) {
if self.inflight.is_some() {
let _ = self.handle.async_cancel();
}
}
}
/// Successful completion of an async transaction.
#[derive(Debug)]
pub struct Completion {
/// Number of response bytes written into `recv`.
pub len: usize,
/// The send buffer, returned for reuse.
pub send: &'static mut [u8],
/// The receive buffer, returned for reuse. `recv[..len]` holds the
/// response payload.
pub recv: &'static mut [u8],
}
/// Error from `try_recv()`. `buffers` is `None` only when no transaction
/// was pending, so there were none to hand back.
#[derive(Debug)]
pub struct RecvError {
pub error: Error,
pub buffers: Option<Buffers>,
}
/// Error from `start()`, carrying the buffers back so they are not lost.
#[derive(Debug)]
pub struct StartError {
pub error: Error,
pub send: &'static mut [u8],
pub recv: &'static mut [u8],
}