use core::mem; use alloc::{vec::Vec, string::String, collections::btree_map::BTreeMap}; use sched::{Io, Mutex, Error as SchedError}; const ALIGNMENT: usize = 64; #[cfg(has_drtio)] pub mod remote_dma { use super::*; use board_artiq::drtio_routing::RoutingTable; use rtio_mgt::drtio; use board_misoc::clock; #[derive(Debug, PartialEq, Clone)] pub enum RemoteState { NotLoaded, Loaded, PlaybackEnded { error: u8, channel: u32, timestamp: u64 } } #[derive(Debug, Clone)] struct RemoteTrace { trace: Vec, pub state: RemoteState } impl From> for RemoteTrace { fn from(trace: Vec) -> Self { RemoteTrace { trace: trace, state: RemoteState::NotLoaded } } } impl RemoteTrace { pub fn get_trace(&self) -> &Vec { &self.trace } } #[derive(Fail, Debug)] pub enum Error { #[fail(display = "Timed out waiting for DMA results")] Timeout, #[fail(display = "DDMA trace is in incorrect state for the given operation")] IncorrectState, #[fail(display = "scheduler error: {}", _0)] SchedError(#[cause] SchedError), #[fail(display = "DRTIO error: {}", _0)] DrtioError(#[cause] drtio::Error), } impl From for Error { fn from(value: drtio::Error) -> Error { match value { drtio::Error::SchedError(x) => Error::SchedError(x), x => Error::DrtioError(x), } } } impl From for Error { fn from(value: SchedError) -> Error { Error::SchedError(value) } } // remote traces map. ID -> destination, trace pair static mut TRACES: BTreeMap> = BTreeMap::new(); pub fn add_traces(io: &Io, ddma_mutex: &Mutex, id: u32, traces: BTreeMap> ) -> Result<(), SchedError> { let _lock = ddma_mutex.lock(io)?; let mut trace_map: BTreeMap = BTreeMap::new(); for (destination, trace) in traces { trace_map.insert(destination, trace.into()); } unsafe { TRACES.insert(id, trace_map); } Ok(()) } pub fn await_done(io: &Io, ddma_mutex: &Mutex, id: u32, timeout: u64) -> Result { let max_time = clock::get_ms() + timeout as u64; io.until(|| { if clock::get_ms() > max_time { return true; } if ddma_mutex.test_lock() { // cannot lock again within io.until - scheduler guarantees // that it will not be interrupted - so only test the lock return false; } let traces = unsafe { TRACES.get(&id).unwrap() }; for (_dest, trace) in traces { match trace.state { RemoteState::PlaybackEnded {error: _, channel: _, timestamp: _} => (), _ => return false } } true })?; if clock::get_ms() > max_time { error!("Remote DMA await done timed out"); return Err(Error::Timeout); } // clear the internal state, and if there have been any errors, return one of them let mut playback_state: RemoteState = RemoteState::PlaybackEnded { error: 0, channel: 0, timestamp: 0 }; { let _lock = ddma_mutex.lock(io)?; let traces = unsafe { TRACES.get_mut(&id).unwrap() }; for (_dest, trace) in traces { match trace.state { RemoteState::PlaybackEnded {error: e, channel: _c, timestamp: _ts} => if e != 0 { playback_state = trace.state.clone(); }, _ => (), } trace.state = RemoteState::Loaded; } } Ok(playback_state) } pub fn erase(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable, id: u32) -> Result<(), Error> { let _lock = ddma_mutex.lock(io)?; let destinations = unsafe { TRACES.get(&id).unwrap() }; for destination in destinations.keys() { match drtio::ddma_send_erase(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, *destination) { Ok(_) => (), Err(e) => error!("Error erasing trace on DMA: {}", e) } } unsafe { TRACES.remove(&id); } Ok(()) } pub fn upload_traces(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable, id: u32) -> Result<(), Error> { let _lock = ddma_mutex.lock(io)?; let traces = unsafe { TRACES.get_mut(&id).unwrap() }; for (destination, mut trace) in traces { drtio::ddma_upload_trace(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, *destination, trace.get_trace())?; trace.state = RemoteState::Loaded; } Ok(()) } pub fn playback(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable, id: u32, timestamp: u64) -> Result<(), Error>{ // triggers playback on satellites let destinations = unsafe { let _lock = ddma_mutex.lock(io)?; TRACES.get(&id).unwrap() }; for (destination, trace) in destinations { { // need to drop the lock before sending the playback request to avoid a deadlock // if a PlaybackStatus is returned from another satellite in the meanwhile. let _lock = ddma_mutex.lock(io)?; if trace.state != RemoteState::Loaded { error!("Destination {} not ready for DMA, state: {:?}", *destination, trace.state); return Err(Error::IncorrectState); } } drtio::ddma_send_playback(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, *destination, timestamp)?; } Ok(()) } pub fn playback_done(io: &Io, ddma_mutex: &Mutex, id: u32, source: u8, error: u8, channel: u32, timestamp: u64) { // called upon receiving PlaybackDone aux packet let _lock = ddma_mutex.lock(io).unwrap(); let mut trace = unsafe { TRACES.get_mut(&id).unwrap().get_mut(&source).unwrap() }; trace.state = RemoteState::PlaybackEnded { error: error, channel: channel, timestamp: timestamp }; } pub fn destination_changed(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable, destination: u8, up: bool) { // update state of the destination, resend traces if it's up let _lock = ddma_mutex.lock(io).unwrap(); let traces_iter = unsafe { TRACES.iter_mut() }; for (id, dest_traces) in traces_iter { if let Some(trace) = dest_traces.get_mut(&destination) { if up { match drtio::ddma_upload_trace(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, *id, destination, trace.get_trace()) { Ok(_) => trace.state = RemoteState::Loaded, Err(e) => error!("Error adding DMA trace on destination {}: {}", destination, e) } } else { trace.state = RemoteState::NotLoaded; } } } } pub fn has_remote_traces(io: &Io, ddma_mutex: &Mutex, id: u32) -> Result { let _lock = ddma_mutex.lock(io)?; let trace_list = unsafe { TRACES.get(&id).unwrap() }; Ok(!trace_list.is_empty()) } } #[derive(Debug)] struct LocalEntry { trace: Vec, padding_len: usize, duration: u64 } #[derive(Debug)] pub struct Manager { entries: BTreeMap, name_map: BTreeMap, recording_name: String, recording_trace: Vec } impl Manager { pub fn new() -> Manager { Manager { entries: BTreeMap::new(), name_map: BTreeMap::new(), recording_trace: Vec::new(), recording_name: String::new() } } pub fn record_start(&mut self, name: &str) -> Option { self.recording_name = String::from(name); self.recording_trace = Vec::new(); if let Some(id) = self.name_map.get(&self.recording_name) { // replacing a trace let old_id = id.clone(); self.entries.remove(&id); self.name_map.remove(&self.recording_name); // return old ID return Some(old_id); } return None; } pub fn record_append(&mut self, data: &[u8]) { self.recording_trace.extend_from_slice(data) } pub fn record_stop(&mut self, duration: u64, _enable_ddma: bool, _io: &Io, _ddma_mutex: &Mutex) -> Result { let mut local_trace = Vec::new(); let mut _remote_traces: BTreeMap> = BTreeMap::new(); if _enable_ddma && cfg!(has_drtio) { let mut trace = Vec::new(); mem::swap(&mut self.recording_trace, &mut trace); trace.push(0); // analyze each entry and put in proper buckets, as the kernel core // sends whole chunks, to limit comms/kernel CPU communication, // and as only comms core has access to varios DMA buffers. let mut ptr = 0; while trace[ptr] != 0 { // ptr + 3 = tgt >> 24 (destination) let len = trace[ptr] as usize; let destination = trace[ptr+3]; if destination == 0 { local_trace.extend(&trace[ptr..ptr+len]); } else { if let Some(remote_trace) = _remote_traces.get_mut(&destination) { remote_trace.extend(&trace[ptr..ptr+len]); } else { _remote_traces.insert(destination, trace[ptr..ptr+len].to_vec()); } } // and jump to the next event ptr += len; } } else { // with disabled DDMA, move the whole trace to local mem::swap(&mut self.recording_trace, &mut local_trace); } local_trace.push(0); let data_len = local_trace.len(); // Realign the local entry. local_trace.reserve(ALIGNMENT - 1); let padding = ALIGNMENT - local_trace.as_ptr() as usize % ALIGNMENT; let padding = if padding == ALIGNMENT { 0 } else { padding }; for _ in 0..padding { // Vec guarantees that this will not reallocate local_trace.push(0) } for i in 1..data_len + 1 { local_trace[data_len + padding - i] = local_trace[data_len - i] } // trace ID is its pointer let id = local_trace[padding..].as_ptr() as u32; self.entries.insert(id, LocalEntry { trace: local_trace, padding_len: padding, duration: duration, }); let mut name = String::new(); mem::swap(&mut self.recording_name, &mut name); self.name_map.insert(name, id); #[cfg(has_drtio)] remote_dma::add_traces(_io, _ddma_mutex, id, _remote_traces)?; Ok(id) } pub fn erase(&mut self, name: &str) { if let Some(id) = self.name_map.get(name) { self.entries.remove(&id); } self.name_map.remove(name); } #[cfg(has_drtio)] pub fn get_id(&mut self, name: &str) -> Option<&u32> { self.name_map.get(name) } pub fn with_trace(&self, name: &str, f: F) -> R where F: FnOnce(Option<&[u8]>, u64) -> R { if let Some(ptr) = self.name_map.get(name) { match self.entries.get(ptr) { Some(entry) => f(Some(&entry.trace[entry.padding_len..]), entry.duration), None => f(None, 0) } } else { f(None, 0) } } }