blob: 02fcb82163d1e6b45e7dae73e69bf834155b55bc [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use foreign_box::{ForeignRc, ForeignRcState};
use pw_status::{Error, Result};
use pw_time_core::Instant;
use crate::object::{KernelObject, ObjectBase, Signals, SyscallBuffer, WaitReturn};
use crate::sync::mutex::Mutex;
use crate::sync::spinlock::SpinLock;
use crate::{Arch, Kernel};
type InitiatorRef<K> =
SpinLock<K, Option<ForeignRc<<K as Arch>::AtomicUsize, ChannelInitiatorObject<K>>>>;
struct Transaction<K: Kernel> {
send_buffer: SyscallBuffer,
recv_buffer: SyscallBuffer,
initiator: ForeignRc<K::AtomicUsize, ChannelInitiatorObject<K>>,
}
pub struct ChannelHandlerObject<K: Kernel> {
base: ObjectBase<K>,
// SpinLock is used rather than UnsafeCell because the type system cannot
// guarantee set_initiator() is only called before threads start. The lock
// is always uncontended at runtime; cost is a few atomic instructions.
//
// TODO: https://pwbug.dev/493955030 - Eliminate this SpinLock by wiring the
// back-reference at construction time so set_initiator() is not needed.
initiator: InitiatorRef<K>,
active_transaction: Mutex<K, Option<Transaction<K>>>,
}
impl<K: Kernel> ChannelHandlerObject<K> {
pub fn new(kernel: K) -> Self {
Self {
base: ObjectBase::new(Signals::no_active()),
initiator: SpinLock::new(None),
active_transaction: Mutex::new(kernel, None),
}
}
/// Binds the paired initiator back-reference.
///
/// Must be called before either the initiator or handler are added to any
/// object tables.
pub fn set_initiator(
&self,
kernel: K,
initiator: Option<ForeignRc<K::AtomicUsize, ChannelInitiatorObject<K>>>,
) {
*self.initiator.lock(kernel) = initiator;
}
}
impl<K: Kernel> KernelObject<K> for ChannelHandlerObject<K> {
fn base(&self) -> Option<&ObjectBase<K>> {
Some(&self.base)
}
fn object_wait(
&self,
kernel: K,
signal_mask: Signals,
deadline: Instant<<K>::Clock>,
) -> Result<WaitReturn> {
self.base.wait_until(kernel, signal_mask, deadline)
}
fn channel_read(
&self,
_kernel: K,
offset: usize,
mut read_buffer: SyscallBuffer,
) -> Result<usize> {
let active_transaction = self.active_transaction.lock();
let Some(ref transaction) = *active_transaction else {
return Err(Error::Unavailable);
};
transaction.send_buffer.copy_into(offset, &mut read_buffer)
}
fn channel_respond(&self, kernel: K, response_buffer: SyscallBuffer) -> Result<()> {
let mut active_transaction = self.active_transaction.lock();
let Some(ref mut transaction) = *active_transaction else {
return Err(Error::Unavailable);
};
if response_buffer.size() > transaction.recv_buffer.size() {
return Err(Error::OutOfRange);
}
response_buffer.copy_into(0, &mut transaction.recv_buffer)?;
transaction.recv_buffer.truncate(response_buffer.size());
self.base.signal(kernel, |signals| {
signals - (Signals::READABLE | Signals::WRITEABLE)
});
transaction
.initiator
.base
.signal(kernel, |signals| signals | Signals::READABLE);
Ok(())
}
fn object_set_peer_user_signal(&self, kernel: K, set: bool) -> Result<()> {
let Some(initiator) = self.initiator.lock(kernel).clone() else {
return Err(Error::FailedPrecondition);
};
initiator.base.signal(kernel, |signals| {
if set {
signals | Signals::USER
} else {
signals - Signals::USER
}
});
Ok(())
}
/// Reset the handler object. If there is a mid-flight transaction, cancel it.
fn reset(&self, kernel: K) -> Result<()> {
// Clear peer USER signal on initiator.
if let Some(initiator) = self.initiator.lock(kernel).clone() {
initiator
.base
.signal(kernel, |signals| signals - Signals::USER);
}
let mut active_transaction = self.active_transaction.lock();
if let Some(transaction) = active_transaction.take() {
drop(active_transaction);
transaction
.initiator
.base
.signal(kernel, |signals| signals | Signals::ERROR);
}
Ok(())
}
}
pub struct ChannelInitiatorObject<K: Kernel> {
base: ObjectBase<K>,
handler: ForeignRc<K::AtomicUsize, ChannelHandlerObject<K>>,
}
impl<K: Kernel> ChannelInitiatorObject<K> {
#[must_use]
pub fn new(handler: ForeignRc<K::AtomicUsize, ChannelHandlerObject<K>>) -> Self {
Self {
base: ObjectBase::new(Signals::WRITEABLE),
handler,
}
}
}
impl<K: Kernel> KernelObject<K> for ChannelInitiatorObject<K> {
fn base(&self) -> Option<&ObjectBase<K>> {
Some(&self.base)
}
/// Reset the initiator object. Clear any active transaction, and
/// restore the initial signals.
fn reset(&self, kernel: K) -> Result<()> {
// Clear peer USER signal on handler.
self.handler
.base
.signal(kernel, |signals| signals - Signals::USER);
// Cancel the active transaction.
if self.handler.active_transaction.lock().take().is_some() {
self.handler
.base
.signal(kernel, |signals| signals | Signals::ERROR);
}
// Restore objects initial signals.
if let Some(base) = self.base() {
base.signal(kernel, |signals| {
(signals | Signals::WRITEABLE) - (Signals::READABLE | Signals::ERROR)
});
}
Ok(())
}
fn object_wait(
&self,
kernel: K,
signal_mask: Signals,
deadline: Instant<<K>::Clock>,
) -> Result<WaitReturn> {
self.base.wait_until(kernel, signal_mask, deadline)
}
fn channel_transact(
&self,
kernel: K,
send_buffer: SyscallBuffer,
recv_buffer: SyscallBuffer,
deadline: Instant<K::Clock>,
) -> Result<usize> {
self.start_transaction(kernel, send_buffer, recv_buffer)?;
// Result processing is deferred until the object is in a coherent state.
let wait_result = self.object_wait(kernel, Signals::READABLE, deadline);
// Always clean up the transaction state regardless of wait_result.
let transaction_result = self.finish_transaction(kernel);
wait_result?;
transaction_result
}
fn channel_async_transact(
&self,
kernel: K,
send_buffer: SyscallBuffer,
recv_buffer: SyscallBuffer,
) -> Result<()> {
self.start_transaction(kernel, send_buffer, recv_buffer)
}
fn channel_async_transact_complete(&self, kernel: K) -> Result<usize> {
let active_signals = self.base.state.lock(kernel).active_signals;
if active_signals.contains(Signals::READABLE) {
// Transaction completed successfully.
self.finish_transaction(kernel)
} else {
// Transaction is still pending (or doesn't exist).
Err(Error::Unavailable)
}
}
fn channel_async_cancel(&self, kernel: K) -> Result<()> {
self.finish_transaction(kernel).map(|_| ())
}
fn object_set_peer_user_signal(&self, kernel: K, set: bool) -> Result<()> {
self.handler.base.signal(kernel, |signals| {
if set {
signals | Signals::USER
} else {
signals - Signals::USER
}
});
Ok(())
}
}
impl<K: Kernel> ChannelInitiatorObject<K> {
fn start_transaction(
&self,
kernel: K,
send_buffer: SyscallBuffer,
recv_buffer: SyscallBuffer,
) -> Result<()> {
// TODO: konkers - When the kernel has dynamic memory mapping APIs either:
// * these checks will have to be differed til the time of memcpy.
// * a region locking mechanism will need to be built
// * IPC will be disallowed too/from dynamically mappable memory.
let self_rc = unsafe { ForeignRcState::create_ref_from_inner(self) };
let mut active_transaction = self.handler.active_transaction.lock();
// Check to see if a transaction is already active on the channel.
if active_transaction.is_some() {
return Err(Error::Unavailable);
}
*active_transaction = Some(Transaction {
send_buffer,
recv_buffer,
initiator: self_rc,
});
drop(active_transaction);
// Clear Readable and Writable & Error signals on our side before
// signaling the handler.
self.base.signal(kernel, |signals| {
signals - (Signals::READABLE | Signals::WRITEABLE | Signals::ERROR)
});
self.handler.base.signal(kernel, |signals| {
(signals | Signals::READABLE) - Signals::WRITEABLE
});
Ok(())
}
fn finish_transaction(&self, kernel: K) -> Result<usize> {
// TODO: konkers - Rationalize signal behavior with syscall_defs.rs.
// Go back to the writable state now that the transaction is finished.
self.base.signal(kernel, |signals| {
(signals | Signals::WRITEABLE) - Signals::READABLE
});
// Also reset the handler signals.
self.handler.base.signal(kernel, |signals| {
signals - (Signals::READABLE | Signals::WRITEABLE)
});
let mut active_transaction = self.handler.active_transaction.lock();
// All success and error paths reset `active_transaction` to `None`.
let transaction = active_transaction.take();
match transaction {
// The handler has stored the number of response bytes by updating.
// the recv_buffer length.
Some(transaction) => Ok(transaction.recv_buffer.size()),
// Transaction was dropped.
None => Err(Error::Unavailable),
}
}
}