xous-core/services/xous-ticktimer/src/main.rs
Sean Cross 511f77f897 ticktimer: only allow RecalculateSleep from ourselves
Rewrite the RecalculateSleep opcode handler so that it will only be
processed if sent by our own process.

As part of this rewrite, use the new `let else` pattern.

Signed-off-by: Sean Cross <sean@xobs.io>
2023-01-30 22:15:00 +08:00

914 lines
37 KiB
Rust

#![cfg_attr(not(target_os = "none"), allow(dead_code))]
#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(target_os = "none", no_main)]
use xous_api_ticktimer::*;
#[cfg(feature = "timestamp")]
mod version;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use log::{error, info};
type TimeoutExpiry = i64;
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum RequestKind {
Sleep = 0,
Timeout = 1,
}
#[derive(Eq)]
pub struct TimerRequest {
msec: TimeoutExpiry,
sender: xous::MessageSender,
kind: RequestKind,
data: usize,
}
impl core::fmt::Display for TimerRequest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TimerRequest {{ msec: {}, {} }}", self.msec, self.sender)
}
}
impl core::fmt::Debug for TimerRequest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TimerRequest {{ msec: {}, {} }}", self.msec, self.sender)
}
}
impl core::cmp::Ord for TimerRequest {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
if self.msec < other.msec {
core::cmp::Ordering::Less
} else if self.msec > other.msec {
core::cmp::Ordering::Greater
} else {
self.sender.cmp(&other.sender)
}
}
}
impl core::cmp::PartialOrd for TimerRequest {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl core::cmp::PartialEq for TimerRequest {
fn eq(&self, other: &Self) -> bool {
self.msec == other.msec && self.sender == other.sender
}
}
#[cfg(any(feature = "precursor", feature = "renode"))]
mod implementation {
const TICKS_PER_MS: u64 = 1;
use super::TimerRequest;
use susres::{RegManager, RegOrField, SuspendResume};
use utralib::generated::*;
pub struct XousTickTimer {
csr: utralib::CSR<u32>,
current_response: Option<TimerRequest>,
connection: xous::CID,
ticktimer_sr_manager: RegManager<{ utra::ticktimer::TICKTIMER_NUMREGS }>,
wdt_sr_manager: RegManager<{ utra::wdt::WDT_NUMREGS }>,
wdt: utralib::CSR<u32>,
}
fn handle_irq(_irq_no: usize, arg: *mut usize) {
let xtt = unsafe { &mut *(arg as *mut XousTickTimer) };
// println!("In IRQ, connection: {}", xtt.connection);
// Safe because we're in an interrupt, and this interrupt is only
// enabled when this value is not None.
let response = xtt.current_response.take().unwrap();
xous::return_scalar(response.sender, response.kind as usize).ok();
// Disable the timer
xtt.csr.wfo(utra::ticktimer::EV_ENABLE_ALARM, 0);
xtt.csr.wfo(utra::ticktimer::EV_PENDING_ALARM, 1);
// This is dangerous and may return an error if the queue is full.
// Which is fine, because the queue is always recalculated any time a message arrives.
use num_traits::ToPrimitive;
xous::try_send_message(
xtt.connection,
xous::Message::Scalar(xous::ScalarMessage {
id: crate::api::Opcode::RecalculateSleep.to_usize().unwrap(),
arg1: response.sender.to_usize(),
arg2: response.kind as usize,
arg3: response.data,
arg4: 0,
}),
)
.ok();
}
impl XousTickTimer {
pub fn new(connection: xous::CID) -> XousTickTimer {
// println!("Connection: {}", connection);
let csr = xous::syscall::map_memory(
xous::MemoryAddress::new(utra::ticktimer::HW_TICKTIMER_BASE),
None,
4096,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map Tick Timer CSR range");
let wdt = xous::syscall::map_memory(
xous::MemoryAddress::new(utra::wdt::HW_WDT_BASE),
None,
4096,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map Watchdog timer CSR range");
let ticktimer_sr_manager = RegManager::new(csr.as_mut_ptr() as *mut u32);
let wdt_sr_manager = RegManager::new(wdt.as_mut_ptr() as *mut u32);
let mut xtt = XousTickTimer {
csr: CSR::new(csr.as_mut_ptr() as *mut u32),
current_response: None,
connection,
ticktimer_sr_manager,
wdt_sr_manager,
wdt: CSR::new(wdt.as_mut_ptr() as *mut u32),
};
#[cfg(feature = "watchdog")]
{
xtt.wdt.wfo(utra::wdt::PERIOD_PERIOD, 0x7FFF_FFFF); // about 30 seconds +/- 50%
xtt.wdt.wfo(utra::wdt::WATCHDOG_ENABLE, 1);
// this is a write-once field that is lost later on, so it must be explicitly managed
// xtt.wdt_sr_manager.push(RegOrField::Field(utra::wdt::WATCHDOG_ENABLE), None);
}
xous::claim_interrupt(
utra::ticktimer::TICKTIMER_IRQ,
handle_irq,
(&mut xtt) as *mut XousTickTimer as *mut usize,
)
.expect("couldn't claim irq");
xtt.ticktimer_sr_manager
.push(RegOrField::Reg(utra::ticktimer::MSLEEP_TARGET0), None);
xtt.ticktimer_sr_manager
.push(RegOrField::Reg(utra::ticktimer::MSLEEP_TARGET1), None);
xtt.ticktimer_sr_manager
.push_fixed_value(RegOrField::Reg(utra::ticktimer::EV_PENDING), 0xFFFF_FFFF);
xtt.ticktimer_sr_manager
.push(RegOrField::Reg(utra::ticktimer::EV_ENABLE), None);
xtt
}
pub fn reset(&mut self) {
self.csr.wfo(utra::ticktimer::CONTROL_RESET, 0b1);
self.csr.wo(utra::ticktimer::CONTROL, 0); // not paused, not reset -> free-run
}
pub fn raw_ticktime(&self) -> u64 {
let mut time: u64 = self.csr.r(utra::ticktimer::TIME0) as u64;
time |= (self.csr.r(utra::ticktimer::TIME1) as u64) << 32;
time
}
pub fn elapsed_ms(&self) -> u64 {
self.raw_ticktime() / TICKS_PER_MS
}
pub fn stop_interrupt(&mut self) -> Option<TimerRequest> {
// Disable the timer
self.csr.wfo(utra::ticktimer::EV_ENABLE_ALARM, 0);
// Now that the interrupt is disabled, we can see if the interrupt handler has a current response.
// If it exists, then that means that an interrupt did NOT fire, and an existing interrupt
// is in place.
if let Some(sr) = self.current_response.take() {
#[cfg(feature = "debug-print")]
{
log::info!(
"Stopping currently-running timer sr.msec: {} elapsed_ms: {}",
sr.msec,
self.elapsed_ms()
);
}
Some(sr)
} else {
None
}
}
pub fn schedule_response(&mut self, request: TimerRequest) {
let irq_target = request.msec;
log::trace!(
"setting a response at {} ms (current time: {} ms)",
irq_target,
self.elapsed_ms()
);
// Disable the timer interrupt
assert!(self.csr.rf(utra::ticktimer::EV_ENABLE_ALARM) == 0);
self.csr.wfo(utra::ticktimer::EV_ENABLE_ALARM, 0);
// Save a copy of the current sleep request
self.current_response = Some(request);
// Set the new target time
self.csr
.wo(utra::ticktimer::MSLEEP_TARGET1, (irq_target >> 32) as _);
self.csr
.wo(utra::ticktimer::MSLEEP_TARGET0, irq_target as _);
// Clear previous interrupt (if any)
self.csr.wfo(utra::ticktimer::EV_PENDING_ALARM, 1);
// Enable the interrupt
self.csr.wfo(utra::ticktimer::EV_ENABLE_ALARM, 1);
}
#[allow(dead_code)]
pub fn reset_wdt(&mut self) {
self.wdt.wfo(utra::wdt::WATCHDOG_RESET_WDT, 1);
}
#[allow(dead_code)]
pub fn check_wdt(&mut self) {
let state = self.wdt.r(utra::wdt::STATE);
if state & self.wdt.ms(utra::wdt::STATE_DISARMED, 1) == 0 {
log::info!(
"{} WDT is not disarmed, state: 0x{:x}",
self.elapsed_ms(),
state
);
}
}
// the ticktimer suspend/resume routines are a bit trickier than normal, so this isn't a great
// example of a generic suspend/resume template
pub fn suspend(&mut self) {
log::trace!("suspending");
self.ticktimer_sr_manager.suspend();
self.wdt_sr_manager.suspend();
// by writing this after suspend(), resume will get the prior value
self.csr.wfo(utra::ticktimer::EV_ENABLE_ALARM, 0);
}
pub fn resume(&mut self) {
// this is a write-once bit that's later erased, so it can't be managed automatically
// thus we have to restore in manually on a resume
#[cfg(feature = "watchdog")]
{
self.wdt.wfo(utra::wdt::WATCHDOG_ENABLE, 1);
}
// manually clear any pending ticktimer events. This is mainly releveant for a "touch-and-go" simulated suspend.
self.csr.wfo(utra::ticktimer::EV_PENDING_ALARM, 1);
self.wdt_sr_manager.resume();
self.ticktimer_sr_manager.resume();
log::trace!(
"ticktimer enable: {}",
self.csr.r(utra::ticktimer::EV_ENABLE)
);
log::trace!(
"ticktimer time/target: {}/{}",
self.csr.r(utra::ticktimer::TIME0),
self.csr.r(utra::ticktimer::MSLEEP_TARGET0)
);
}
}
}
#[cfg(any(
not(target_os = "xous"),
not(any(feature = "precursor", feature = "renode", not(target_os = "xous")))
))]
mod implementation {
use crate::RequestKind;
use super::TimerRequest;
use num_traits::ToPrimitive;
use std::convert::TryInto;
#[derive(Debug)]
enum SleepComms {
InterruptSleep,
StartSleep(
xous::MessageSender,
i64, /* ms */
u64, /* elapsed */
),
}
pub struct XousTickTimer {
start: std::time::Instant,
sleep_comms: std::sync::mpsc::Sender<SleepComms>,
time_remaining_receiver: std::sync::mpsc::Receiver<Option<TimerRequest>>,
}
impl XousTickTimer {
pub fn new(cid: xous::CID) -> XousTickTimer {
let (sleep_sender, sleep_receiver) = std::sync::mpsc::channel();
let (time_remaining_sender, time_remaining_receiver) = std::sync::mpsc::channel();
xous::create_thread(move || {
let mut timeout = None;
let mut current_response: Option<TimerRequest> = None;
loop {
let result = match timeout {
None => sleep_receiver
.recv()
.map_err(|_| std::sync::mpsc::RecvTimeoutError::Disconnected),
Some(s) => sleep_receiver.recv_timeout(s),
};
match result {
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
let response = current_response.take().unwrap();
#[cfg(feature = "debug-print")]
log::info!("Returning scalar to {}", response.sender);
xous::return_scalar(response.sender, response.kind as usize)
.expect("couldn't send response");
// This is dangerous and may panic if the queue is full.
xous::try_send_message(
cid,
xous::Message::Scalar(xous::ScalarMessage {
id: crate::api::Opcode::RecalculateSleep.to_usize().unwrap(),
arg1: response.sender.to_usize(),
arg2: response.kind as usize,
arg3: response.data,
arg4: 0,
}),
)
.unwrap();
timeout = None;
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
return;
}
Ok(SleepComms::InterruptSleep) => {
timeout = None;
time_remaining_sender.send(current_response.take()).unwrap()
}
Ok(SleepComms::StartSleep(new_sender, expiry, elapsed)) => {
let mut duration = expiry - (elapsed as i64);
if duration > 0 {
#[cfg(feature = "debug-print")]
log::info!(
"Starting sleep for {} ms, returning to {}",
duration,
new_sender
);
} else {
#[cfg(feature = "debug-print")]
log::info!(
"Clamping duration to 0 (was: {})m returning to {}",
duration,
new_sender
);
duration = 0;
}
timeout = Some(std::time::Duration::from_millis(
duration.try_into().unwrap(),
));
current_response = Some(TimerRequest {
sender: new_sender,
msec: expiry,
kind: RequestKind::Sleep,
data: 0,
});
}
}
}
})
.unwrap();
XousTickTimer {
start: std::time::Instant::now(),
time_remaining_receiver,
sleep_comms: sleep_sender,
}
}
pub fn reset(&mut self) {
self.start = std::time::Instant::now();
}
pub fn elapsed_ms(&self) -> u64 {
self.start.elapsed().as_millis().try_into().unwrap()
}
pub fn stop_interrupt(&mut self) -> Option<TimerRequest> {
self.sleep_comms.send(SleepComms::InterruptSleep).unwrap();
self.time_remaining_receiver.recv().unwrap()
}
pub fn schedule_response(&mut self, request: TimerRequest) {
#[cfg(feature = "debug-print")]
log::info!(
"request.msec: {} self.elapsed_ms: {} returning to: {}",
request.msec,
self.elapsed_ms(),
request.sender
);
self.sleep_comms
.send(SleepComms::StartSleep(
request.sender,
request.msec as i64,
self.elapsed_ms(),
))
.unwrap();
}
#[allow(dead_code)]
pub fn reset_wdt(&self) {
// dummy function, does nothing
}
pub fn register_suspend_listener(
&self,
_opcode: u32,
_cid: xous::CID,
) -> Result<(), xous::Error> {
Ok(())
}
pub fn suspend(&self) {}
pub fn resume(&self) {}
}
}
use implementation::*;
use susres::SuspendOrder;
/// Disable the sleep interrupt and remove the currently-pending sleep item.
/// If the sleep item has fired, then there will be no existing sleep item
/// remaining.
fn stop_sleep(
ticktimer: &mut XousTickTimer,
sleep_heap: &mut BTreeMap<TimeoutExpiry, TimerRequest>, // min-heap with Reverse
) {
// If there's a sleep request ongoing now, grab it.
if let Some(current) = ticktimer.stop_interrupt() {
#[cfg(feature = "debug-print")]
info!("Existing request was {:?}", current);
sleep_heap.insert(current.msec, current);
} else {
#[cfg(feature = "debug-print")]
info!("There was no existing sleep() request");
}
}
fn start_sleep(
ticktimer: &mut XousTickTimer,
sleep_heap: &mut BTreeMap<TimeoutExpiry, TimerRequest>, // min-heap with Reverse
) {
// If there are items in the sleep heap, take the next item that will expire.
if let Some((_msec, next_response)) = sleep_heap.pop_first() {
#[cfg(feature = "debug-print")]
info!(
"scheduling a response at {} to {} (heap: {:?})",
next_response.msec, next_response.sender, sleep_heap
);
ticktimer.schedule_response(next_response);
} else {
#[cfg(feature = "debug-print")]
info!(
"not scheduling a response since the sleep heap is empty ({:?})",
sleep_heap
);
}
}
/// Recalculate the sleep timer, optionally adding a new Request to the list of available
/// sleep events. This involves stopping the timer, recalculating the newest item, then
/// restarting the timer.
///
/// Note that interrupts are always enabled, which is why we must stop the timer prior to
/// reordering the list.
fn recalculate_sleep(
ticktimer: &mut XousTickTimer,
sleep_heap: &mut BTreeMap<TimeoutExpiry, TimerRequest>, // min-heap with Reverse
new: Option<TimerRequest>,
) {
stop_sleep(ticktimer, sleep_heap);
// If we have a new sleep request, add it to the heap.
if let Some(mut request) = new {
#[cfg(feature = "debug-print")]
info!("New sleep request was: {:?}", request);
// Ensure that each timeout only exists once inside the tree
request.msec += ticktimer.elapsed_ms() as i64;
while sleep_heap.contains_key(&request.msec) {
request.msec += 1;
}
#[cfg(feature = "debug-print")]
info!("Modified, the request was: {:?}", request);
sleep_heap.insert(request.msec, request);
} else {
#[cfg(feature = "debug-print")]
info!("No new sleep request");
}
start_sleep(ticktimer, sleep_heap);
}
fn main() -> ! {
log_server::init_wait().unwrap();
log::set_max_level(log::LevelFilter::Info);
info!("my PID is {}", xous::process::id());
#[cfg(feature = "timestamp")]
{
log::info!("****************************************************************");
log::info!("Welcome to Xous {}", version::SEMVER);
log::info!("Built on {}", version::TIMESTAMP);
log::info!("****************************************************************");
}
#[cfg(not(feature = "timestamp"))]
{
log::info!("****************************************************************");
log::info!("Welcome to Xous");
log::info!("Reproducible build without timestamps");
log::info!("****************************************************************");
}
let ticktimer_server = xous::create_server_with_address(b"ticktimer-server")
.expect("Couldn't create Ticktimer server");
info!("Server started with SID {:?}", ticktimer_server);
// Connect to our own server so we can send the "Recalculate" message
let ticktimer_client = xous::connect(xous::SID::from_bytes(b"ticktimer-server").unwrap())
.expect("couldn't connect to self");
// Create a new ticktimer object
let mut ticktimer = XousTickTimer::new(ticktimer_client);
ticktimer.reset(); // make sure the time starts from zero
// register a suspend/resume listener
let xns = xous_names::XousNames::new().unwrap();
let sr_cid =
xous::connect(ticktimer_server).expect("couldn't create suspend callback connection");
let mut susres = susres::Susres::new(
Some(SuspendOrder::Last),
&xns,
api::Opcode::SuspendResume as u32,
sr_cid,
)
.expect("couldn't create suspend/resume object");
// A list of all sleep requests in the system, sorted by the time at which it
// expires. That is, if a request comes in to sleep for 1000 ms, and the ticktimer
// is currently at 900, the Request will be `1900`.
let mut sleep_heap: BTreeMap<TimeoutExpiry, TimerRequest> = BTreeMap::new();
// A list of message IDs that are waiting to receive a Notification. This queue is drained
// by threads sending `NotifyCondition` to us, or by a condvar timing out.
let mut notify_hash: HashMap<Option<xous::PID>, HashMap<usize, VecDeque<xous::MessageSender>>> =
HashMap::new();
// There is a small chance that a client sends a `notify_one()` or `notify_all()` before
// the other threads have a chance to recover. This is due to a non-threadsafe use of
// Mutex<T> within the standard library. Keep track of any excess `notify_one()` or
// `notify_all()` messages for when the Mutex<T> is successfully locked.
let mut immedaite_notifications: HashMap<Option<xous::PID>, HashMap<usize, usize>> =
HashMap::new();
// // A list of timeouts for a given condvar. This list serves two purposes:
// // 1. When a Notification is sent to a condvar, it is removed from this hash (based on the Message ID)
// // 2. When the timer event hits, a response is sent to this condvar
// // Therefore, this must be indexable by two keys: Message ID and
// let mut timeout_heap: std::collections::VecDeque<TimerRequest> = VecDeque::new();
// A list of mutexes that should be allowed to run immediately, because the
// thread they were waiting on has already unlocked the mutex. This occurs
// when a thread attempts to Lock a Mutex and fails, then gets preempted
// before it has a chance to send the `LockMutex` message to us.
let mut mutex_ready_hash: HashMap<Option<xous::PID>, HashSet<usize>> = HashMap::new();
// A list of message IDs that are waiting to lock a Mutex. These are processes
// that have attempted to lock a Mutex and failed, and have sent us the `LockMutex`
// message. This queue is drained by threads sending `UnlockMutex` to us.
let mut mutex_hash: HashMap<Option<xous::PID>, HashMap<usize, VecDeque<xous::MessageSender>>> =
HashMap::new();
let mut msg_opt = None;
let mut return_type = 0;
loop {
#[cfg(feature = "watchdog")]
ticktimer.reset_wdt();
//#[cfg(feature = "watchdog")] // for debugging the watchdog
//ticktimer.check_wdt();
xous::reply_and_receive_next_legacy(ticktimer_server, &mut msg_opt, &mut return_type)
.unwrap();
let msg = msg_opt.as_mut().unwrap();
log::trace!("msg: {:x?}", msg);
match num_traits::FromPrimitive::from_usize(msg.body.id())
.unwrap_or(api::Opcode::InvalidCall)
{
api::Opcode::ElapsedMs => {
if let Some(scalar) = msg.body.scalar_message_mut() {
let time = ticktimer.elapsed_ms() as i64;
scalar.arg1 = (time & 0xFFFF_FFFFi64) as usize;
scalar.arg2 = ((time >> 32) & 0xFFF_FFFFi64) as usize;
scalar.id = 0;
// API calls expect a `Scalar2` value in response
return_type = 2;
}
}
api::Opcode::SleepMs => {
if let Some(scalar) = msg.body.scalar_message_mut() {
let ms = scalar.arg1 as i64;
let sender = msg.sender;
// Forget the contents of the message, replacing it with `None`. This
// will prevent it from getting dropped, which (under normal circumstances)
// would cause it to be responded to automatically.
//
// The ideal approach here would be to have the struct -- in this case the
// TimerRequest -- keep track of the element itself. That way, it could
// return the value as part of its response routine. However, due to the
// legacy split of `BlockingScalars`, these are allowed to have an explicit
// return path, so we need to forget the original message here to prevent
// it from getting returned early.
//
// (n.b. this auto-response hasn't been enabled yet for Scalar messages,
// but this definitely would need to be done for e.g. Memory messages).
core::mem::forget(msg_opt.take());
// let timeout_queue = timeout_heap.entry(msg.sender.pid()).or_default();
recalculate_sleep(
&mut ticktimer,
&mut sleep_heap,
Some(TimerRequest {
msec: ms,
sender: sender,
kind: RequestKind::Sleep,
data: 0,
}),
);
}
}
api::Opcode::RecalculateSleep => {
if msg.sender.pid().map(|p| p.get()).unwrap_or_default() as u32
!= xous::process::id()
{
recalculate_sleep(&mut ticktimer, &mut sleep_heap, None);
continue;
}
let Some(args) = msg.body.scalar_message() else {
continue
};
// If this is a Timeout message that fired, remove it from the Notification list
let sender = args.arg1;
let request_kind = args.arg2;
let condvar = args.arg3;
let sender_pid = xous::MessageSender::from_usize(sender).pid();
// If we're being asked to recalculate due to a timeout expiring, drop the sent
// message from the `entries` list.
if (request_kind == RequestKind::Timeout as usize) && (sender > 0) {
let mut expired_values = 0;
let awaiting = notify_hash
.entry(sender_pid)
.or_default()
.entry(condvar)
.or_default();
awaiting.retain(|e| {
if e.to_usize() == sender {
expired_values += 1;
false
} else {
true
}
});
// Remove each entry in the timeout set
for (_msecs, timer_request) in sleep_heap.iter() {
if timer_request.sender.to_usize() == sender {
log::error!("we were just notified of PID {:?}/condvar {:08x} expiring, yet it's still in the sleep heap with sender {:08x}", sender_pid, condvar, timer_request.sender.to_usize());
}
}
// log::info!(
// "removed {} entries for PID {:?}/condvar {:08x}, {} to start, {} remain",
// expired_values,
// sender_pid,
// condvar,
// len_before,
// awaiting.len(),
// );
// log::trace!("new entries for PID {:?}/condvar {:08x}: {:?}", sender_pid, condvar, notify_hash.get(&sender_pid).unwrap().get(&condvar));
}
recalculate_sleep(&mut ticktimer, &mut sleep_heap, None);
}
api::Opcode::SuspendResume => xous::msg_scalar_unpack!(msg, token, _, _, _, {
ticktimer.suspend();
susres
.suspend_until_resume(token)
.expect("couldn't execute suspend/resume");
ticktimer.resume();
}),
api::Opcode::PingWdt => {
ticktimer.reset_wdt();
}
api::Opcode::GetVersion => {
let mut buf = unsafe {
xous_ipc::Buffer::from_memory_message_mut(
msg.body.memory_message_mut().unwrap(),
)
};
#[cfg(feature = "timestamp")]
buf.replace(version::get_version()).unwrap();
#[cfg(not(feature = "timestamp"))]
{
let v = crate::api::VersionString {
version: xous_ipc::String::from_str("--no-timestamp requested for build"),
};
buf.replace(v).unwrap();
}
}
api::Opcode::LockMutex => {
let pid = msg.sender.pid();
if let Some(scalar) = msg.body.scalar_message_mut() {
let ready = mutex_ready_hash.entry(pid).or_default();
// If this item is in the Ready list, return right away without blocking
if ready.remove(&scalar.arg1) {
scalar.id = 0;
continue;
}
// This item is not in the Ready list, so add our sender to the list of processes
// to get called when UnlockMutex is invoked
let awaiting = mutex_hash.entry(pid).or_default();
// Add this to the end of the list of entries to call so that when `UnlockMutex` is sent
// the message will get a response.
let mutex_entry = awaiting.entry(scalar.arg1).or_default();
mutex_entry.push_back(msg.sender);
// We've saved the `msg.sender` above and will return the lock as part of the mutex
// work thread. Forget the contents of `msg_opt` without running its destructor.
core::mem::forget(msg_opt.take());
} else {
info!("sender made LockMutex request that was not blocking");
}
}
api::Opcode::UnlockMutex => {
let pid = msg.sender.pid();
if msg.body.is_blocking() {
info!("sender made UnlockMutex request that was blocking");
continue;
}
if let Some(scalar) = msg.body.scalar_message() {
// Get a list of awaiting mutexes for this process
let awaiting = mutex_hash.entry(pid).or_default();
// Get the vector of awaiting mutex entries.
let mutex_entry = awaiting.entry(scalar.arg1).or_default();
// If there's something waiting in the queue, respond to that message
if let Some(sender) = mutex_entry.pop_front() {
xous::return_scalar(sender, 0).unwrap();
} else {
// Otherwise, mark this scalar as being ready to run
mutex_ready_hash.entry(pid).or_default().insert(scalar.arg1);
}
}
}
api::Opcode::WaitForCondition => {
let pid = msg.sender.pid();
if let Some(scalar) = msg.body.scalar_message_mut() {
let condvar = scalar.arg1;
let timeout = scalar.arg2;
log::trace!(
"sender in pid {:?} is waiting on a condition {:08x} with a timeout of {}",
pid,
condvar,
timeout
);
// If there's a condition waiting already, decrement the total list
// and return immediately.
if let Some(excess) = immedaite_notifications
.entry(pid)
.or_default()
.get_mut(&condvar)
{
if *excess > 0 {
*excess -= 1;
scalar.id = 0;
// Rust libstd expects a `Scalar1` return type for this call
return_type = 1;
continue;
}
}
// If there's a `timeout` argument, schedule a response.
if timeout != 0 {
recalculate_sleep(
&mut ticktimer,
&mut sleep_heap,
Some(TimerRequest {
msec: timeout as i64,
sender: msg.sender,
kind: RequestKind::Timeout,
data: condvar,
}),
)
}
// Add this to the list of entries waiting for a response.
notify_hash
.entry(pid)
.or_default()
.entry(condvar)
.or_default()
.push_back(msg.sender);
// log::trace!("New waiting senders: {:?}", notify_hash.get(&pid).unwrap().get(&condvar));
// The message will be responded to as part of the notification hash
// when the condvar is unlocked. Forget the contents of the message
// in order to prevent it from being responded to early.
core::mem::forget(msg_opt.take());
} else {
info!(
"sender made WaitForCondition request that wasn't a BlockingScalar Message"
);
}
}
api::Opcode::NotifyCondition => {
let pid = msg.sender.pid();
if msg.body.is_blocking() {
info!("sender made NotifyCondition request that was blocking");
continue;
}
if let Some(scalar) = msg.body.scalar_message() {
let condvar = scalar.arg1;
log::trace!(
"sender in pid {:?} is notifying {} entries for condition {:08x}",
pid,
scalar.arg2,
condvar,
);
let awaiting = notify_hash
.entry(pid)
.or_default()
.entry(condvar)
.or_default();
// Wake threads, ensuring we don't run off the end.
let requested_count = scalar.arg2;
let available_count = core::cmp::min(requested_count, awaiting.len());
stop_sleep(&mut ticktimer, &mut sleep_heap);
for entry in awaiting.drain(..available_count) {
// Remove each entry in the timeout set
sleep_heap.retain(|_, v| if v.sender == entry { false } else { true });
xous::return_scalar(entry, 0).expect("couldn't send response");
}
// If there are leftover requested, add them to the list of
// notofications that will be responded to immediately.
if available_count - requested_count > 0 {
#[cfg(feature = "debug-print")]
log::trace!(
"Adding {} spare sleep requests to immediate_notifications list",
available_count - requested_count
);
*immedaite_notifications
.entry(pid)
.or_default()
.entry(condvar)
.or_default() += available_count - requested_count;
}
// Resume sleeping, which re-enables interrupts and queues the
// next timer event to fire.
start_sleep(&mut ticktimer, &mut sleep_heap);
} else {
info!(
"sender made WaitForCondition request that wasn't a BlockingScalar Message"
);
}
}
api::Opcode::InvalidCall => {
error!("couldn't convert opcode");
}
}
}
}