diff options
| -rw-r--r-- | embassy-executor/src/raw/mod.rs | 20 | ||||
| -rw-r--r-- | embassy-executor/src/raw/trace.rs | 183 | ||||
| -rw-r--r-- | embassy-executor/src/spawner.rs | 53 |
3 files changed, 245 insertions, 11 deletions
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 88d839e07..e7a27035a 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs | |||
| @@ -18,7 +18,7 @@ mod state; | |||
| 18 | 18 | ||
| 19 | pub mod timer_queue; | 19 | pub mod timer_queue; |
| 20 | #[cfg(feature = "trace")] | 20 | #[cfg(feature = "trace")] |
| 21 | mod trace; | 21 | pub mod trace; |
| 22 | pub(crate) mod util; | 22 | pub(crate) mod util; |
| 23 | #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] | 23 | #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] |
| 24 | mod waker; | 24 | mod waker; |
| @@ -89,6 +89,12 @@ pub(crate) struct TaskHeader { | |||
| 89 | 89 | ||
| 90 | /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. | 90 | /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. |
| 91 | pub(crate) timer_queue_item: timer_queue::TimerQueueItem, | 91 | pub(crate) timer_queue_item: timer_queue::TimerQueueItem, |
| 92 | #[cfg(feature = "trace")] | ||
| 93 | pub(crate) name: Option<&'static str>, | ||
| 94 | #[cfg(feature = "trace")] | ||
| 95 | pub(crate) id: u32, | ||
| 96 | #[cfg(feature = "trace")] | ||
| 97 | all_tasks_next: AtomicPtr<TaskHeader>, | ||
| 92 | } | 98 | } |
| 93 | 99 | ||
| 94 | /// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased. | 100 | /// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased. |
| @@ -143,12 +149,6 @@ impl TaskRef { | |||
| 143 | pub(crate) fn as_ptr(self) -> *const TaskHeader { | 149 | pub(crate) fn as_ptr(self) -> *const TaskHeader { |
| 144 | self.ptr.as_ptr() | 150 | self.ptr.as_ptr() |
| 145 | } | 151 | } |
| 146 | |||
| 147 | /// Get the ID for a task | ||
| 148 | #[cfg(feature = "trace")] | ||
| 149 | pub fn as_id(self) -> u32 { | ||
| 150 | self.ptr.as_ptr() as u32 | ||
| 151 | } | ||
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | /// Raw storage in which a task can be spawned. | 154 | /// Raw storage in which a task can be spawned. |
| @@ -190,6 +190,12 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 190 | poll_fn: SyncUnsafeCell::new(None), | 190 | poll_fn: SyncUnsafeCell::new(None), |
| 191 | 191 | ||
| 192 | timer_queue_item: timer_queue::TimerQueueItem::new(), | 192 | timer_queue_item: timer_queue::TimerQueueItem::new(), |
| 193 | #[cfg(feature = "trace")] | ||
| 194 | name: None, | ||
| 195 | #[cfg(feature = "trace")] | ||
| 196 | id: 0, | ||
| 197 | #[cfg(feature = "trace")] | ||
| 198 | all_tasks_next: AtomicPtr::new(core::ptr::null_mut()), | ||
| 193 | }, | 199 | }, |
| 194 | future: UninitCell::uninit(), | 200 | future: UninitCell::uninit(), |
| 195 | } | 201 | } |
diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs index aba519c8f..6c9cfda25 100644 --- a/embassy-executor/src/raw/trace.rs +++ b/embassy-executor/src/raw/trace.rs | |||
| @@ -81,7 +81,131 @@ | |||
| 81 | 81 | ||
| 82 | #![allow(unused)] | 82 | #![allow(unused)] |
| 83 | 83 | ||
| 84 | use crate::raw::{SyncExecutor, TaskRef}; | 84 | use core::cell::UnsafeCell; |
| 85 | use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; | ||
| 86 | |||
| 87 | use rtos_trace::TaskInfo; | ||
| 88 | |||
| 89 | use crate::raw::{SyncExecutor, TaskHeader, TaskRef}; | ||
| 90 | use crate::spawner::{SpawnError, SpawnToken, Spawner}; | ||
| 91 | |||
| 92 | /// Global task tracker instance | ||
| 93 | /// | ||
| 94 | /// This static provides access to the global task tracker which maintains | ||
| 95 | /// a list of all tasks in the system. It's automatically updated by the | ||
| 96 | /// task lifecycle hooks in the trace module. | ||
| 97 | pub static TASK_TRACKER: TaskTracker = TaskTracker::new(); | ||
| 98 | |||
| 99 | /// A thread-safe tracker for all tasks in the system | ||
| 100 | /// | ||
| 101 | /// This struct uses an intrusive linked list approach to track all tasks | ||
| 102 | /// without additional memory allocations. It maintains a global list of | ||
| 103 | /// tasks that can be traversed to find all currently existing tasks. | ||
| 104 | pub struct TaskTracker { | ||
| 105 | head: AtomicPtr<TaskHeader>, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl TaskTracker { | ||
| 109 | /// Creates a new empty task tracker | ||
| 110 | /// | ||
| 111 | /// Initializes a tracker with no tasks in its list. | ||
| 112 | pub const fn new() -> Self { | ||
| 113 | Self { | ||
| 114 | head: AtomicPtr::new(core::ptr::null_mut()), | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Adds a task to the tracker | ||
| 119 | /// | ||
| 120 | /// This method inserts a task at the head of the intrusive linked list. | ||
| 121 | /// The operation is thread-safe and lock-free, using atomic operations | ||
| 122 | /// to ensure consistency even when called from different contexts. | ||
| 123 | /// | ||
| 124 | /// # Arguments | ||
| 125 | /// * `task` - The task reference to add to the tracker | ||
| 126 | pub fn add(&self, task: TaskRef) { | ||
| 127 | let task_ptr = task.as_ptr() as *mut TaskHeader; | ||
| 128 | |||
| 129 | loop { | ||
| 130 | let current_head = self.head.load(Ordering::Acquire); | ||
| 131 | unsafe { | ||
| 132 | (*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed); | ||
| 133 | } | ||
| 134 | |||
| 135 | if self | ||
| 136 | .head | ||
| 137 | .compare_exchange(current_head, task_ptr, Ordering::Release, Ordering::Relaxed) | ||
| 138 | .is_ok() | ||
| 139 | { | ||
| 140 | break; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Performs an operation on each task in the tracker | ||
| 146 | /// | ||
| 147 | /// This method traverses the entire list of tasks and calls the provided | ||
| 148 | /// function for each task. This allows inspecting or processing all tasks | ||
| 149 | /// in the system without modifying the tracker's structure. | ||
| 150 | /// | ||
| 151 | /// # Arguments | ||
| 152 | /// * `f` - A function to call for each task in the tracker | ||
| 153 | pub fn for_each<F>(&self, mut f: F) | ||
| 154 | where | ||
| 155 | F: FnMut(TaskRef), | ||
| 156 | { | ||
| 157 | let mut current = self.head.load(Ordering::Acquire); | ||
| 158 | while !current.is_null() { | ||
| 159 | let task = unsafe { TaskRef::from_ptr(current) }; | ||
| 160 | f(task); | ||
| 161 | |||
| 162 | current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) }; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Extension trait for `TaskRef` that provides tracing functionality. | ||
| 168 | /// | ||
| 169 | /// This trait is only available when the `trace` feature is enabled. | ||
| 170 | /// It extends `TaskRef` with methods for accessing and modifying task identifiers | ||
| 171 | /// and names, which are useful for debugging, logging, and performance analysis. | ||
| 172 | pub trait TaskRefTrace { | ||
| 173 | /// Get the name for a task | ||
| 174 | fn name(&self) -> Option<&'static str>; | ||
| 175 | |||
| 176 | /// Set the name for a task | ||
| 177 | fn set_name(&self, name: Option<&'static str>); | ||
| 178 | |||
| 179 | /// Get the ID for a task | ||
| 180 | fn id(&self) -> u32; | ||
| 181 | |||
| 182 | /// Set the ID for a task | ||
| 183 | fn set_id(&self, id: u32); | ||
| 184 | } | ||
| 185 | |||
| 186 | impl TaskRefTrace for TaskRef { | ||
| 187 | fn name(&self) -> Option<&'static str> { | ||
| 188 | self.header().name | ||
| 189 | } | ||
| 190 | |||
| 191 | fn set_name(&self, name: Option<&'static str>) { | ||
| 192 | unsafe { | ||
| 193 | let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; | ||
| 194 | (*header_ptr).name = name; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | fn id(&self) -> u32 { | ||
| 199 | self.header().id | ||
| 200 | } | ||
| 201 | |||
| 202 | fn set_id(&self, id: u32) { | ||
| 203 | unsafe { | ||
| 204 | let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; | ||
| 205 | (*header_ptr).id = id; | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 85 | 209 | ||
| 86 | #[cfg(not(feature = "rtos-trace"))] | 210 | #[cfg(not(feature = "rtos-trace"))] |
| 87 | extern "Rust" { | 211 | extern "Rust" { |
| @@ -160,6 +284,9 @@ pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { | |||
| 160 | 284 | ||
| 161 | #[cfg(feature = "rtos-trace")] | 285 | #[cfg(feature = "rtos-trace")] |
| 162 | rtos_trace::trace::task_new(task.as_ptr() as u32); | 286 | rtos_trace::trace::task_new(task.as_ptr() as u32); |
| 287 | |||
| 288 | #[cfg(feature = "rtos-trace")] | ||
| 289 | TASK_TRACKER.add(*task); | ||
| 163 | } | 290 | } |
| 164 | 291 | ||
| 165 | #[inline] | 292 | #[inline] |
| @@ -210,10 +337,62 @@ pub(crate) fn executor_idle(executor: &SyncExecutor) { | |||
| 210 | rtos_trace::trace::system_idle(); | 337 | rtos_trace::trace::system_idle(); |
| 211 | } | 338 | } |
| 212 | 339 | ||
| 340 | /// Returns an iterator over all active tasks in the system | ||
| 341 | /// | ||
| 342 | /// This function provides a convenient way to iterate over all tasks | ||
| 343 | /// that are currently tracked in the system. The returned iterator | ||
| 344 | /// yields each task in the global task tracker. | ||
| 345 | /// | ||
| 346 | /// # Returns | ||
| 347 | /// An iterator that yields `TaskRef` items for each task | ||
| 348 | fn get_all_active_tasks() -> impl Iterator<Item = TaskRef> + 'static { | ||
| 349 | struct TaskIterator<'a> { | ||
| 350 | tracker: &'a TaskTracker, | ||
| 351 | current: *mut TaskHeader, | ||
| 352 | } | ||
| 353 | |||
| 354 | impl<'a> Iterator for TaskIterator<'a> { | ||
| 355 | type Item = TaskRef; | ||
| 356 | |||
| 357 | fn next(&mut self) -> Option<Self::Item> { | ||
| 358 | if self.current.is_null() { | ||
| 359 | return None; | ||
| 360 | } | ||
| 361 | |||
| 362 | let task = unsafe { TaskRef::from_ptr(self.current) }; | ||
| 363 | self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) }; | ||
| 364 | |||
| 365 | Some(task) | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | TaskIterator { | ||
| 370 | tracker: &TASK_TRACKER, | ||
| 371 | current: TASK_TRACKER.head.load(Ordering::Acquire), | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | /// Perform an action on each active task | ||
| 376 | fn with_all_active_tasks<F>(f: F) | ||
| 377 | where | ||
| 378 | F: FnMut(TaskRef), | ||
| 379 | { | ||
| 380 | TASK_TRACKER.for_each(f); | ||
| 381 | } | ||
| 382 | |||
| 213 | #[cfg(feature = "rtos-trace")] | 383 | #[cfg(feature = "rtos-trace")] |
| 214 | impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { | 384 | impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { |
| 215 | fn task_list() { | 385 | fn task_list() { |
| 216 | // We don't know what tasks exist, so we can't send them. | 386 | with_all_active_tasks(|task| { |
| 387 | let name = task.name().unwrap_or("unnamed task\0"); | ||
| 388 | let info = rtos_trace::TaskInfo { | ||
| 389 | name, | ||
| 390 | priority: 0, | ||
| 391 | stack_base: 0, | ||
| 392 | stack_size: 0, | ||
| 393 | }; | ||
| 394 | rtos_trace::trace::task_send_info(task.id(), info); | ||
| 395 | }); | ||
| 217 | } | 396 | } |
| 218 | fn time() -> u64 { | 397 | fn time() -> u64 { |
| 219 | const fn gcd(a: u64, b: u64) -> u64 { | 398 | const fn gcd(a: u64, b: u64) -> u64 { |
diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index ff243081c..522d97db3 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs | |||
| @@ -5,6 +5,8 @@ use core::sync::atomic::Ordering; | |||
| 5 | use core::task::Poll; | 5 | use core::task::Poll; |
| 6 | 6 | ||
| 7 | use super::raw; | 7 | use super::raw; |
| 8 | #[cfg(feature = "trace")] | ||
| 9 | use crate::raw::trace::TaskRefTrace; | ||
| 8 | 10 | ||
| 9 | /// Token to spawn a newly-created task in an executor. | 11 | /// Token to spawn a newly-created task in an executor. |
| 10 | /// | 12 | /// |
| @@ -22,7 +24,7 @@ use super::raw; | |||
| 22 | /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. | 24 | /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. |
| 23 | #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] | 25 | #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] |
| 24 | pub struct SpawnToken<S> { | 26 | pub struct SpawnToken<S> { |
| 25 | raw_task: Option<raw::TaskRef>, | 27 | pub(crate) raw_task: Option<raw::TaskRef>, |
| 26 | phantom: PhantomData<*mut S>, | 28 | phantom: PhantomData<*mut S>, |
| 27 | } | 29 | } |
| 28 | 30 | ||
| @@ -103,7 +105,7 @@ impl core::error::Error for SpawnError {} | |||
| 103 | /// If you want to spawn tasks from another thread, use [SendSpawner]. | 105 | /// If you want to spawn tasks from another thread, use [SendSpawner]. |
| 104 | #[derive(Copy, Clone)] | 106 | #[derive(Copy, Clone)] |
| 105 | pub struct Spawner { | 107 | pub struct Spawner { |
| 106 | executor: &'static raw::Executor, | 108 | pub(crate) executor: &'static raw::Executor, |
| 107 | not_send: PhantomData<*mut ()>, | 109 | not_send: PhantomData<*mut ()>, |
| 108 | } | 110 | } |
| 109 | 111 | ||
| @@ -180,6 +182,53 @@ impl Spawner { | |||
| 180 | } | 182 | } |
| 181 | } | 183 | } |
| 182 | 184 | ||
| 185 | /// Extension trait adding tracing capabilities to the Spawner | ||
| 186 | /// | ||
| 187 | /// This trait provides an additional method to spawn tasks with an associated name, | ||
| 188 | /// which can be useful for debugging and tracing purposes. | ||
| 189 | pub trait SpawnerTraceExt { | ||
| 190 | /// Spawns a new task with a specified name. | ||
| 191 | /// | ||
| 192 | /// # Arguments | ||
| 193 | /// * `name` - Static string name to associate with the task | ||
| 194 | /// * `token` - Token representing the task to spawn | ||
| 195 | /// | ||
| 196 | /// # Returns | ||
| 197 | /// Result indicating whether the spawn was successful | ||
| 198 | fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError>; | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Implementation of the SpawnerTraceExt trait for Spawner when trace is enabled | ||
| 202 | #[cfg(feature = "trace")] | ||
| 203 | impl SpawnerTraceExt for Spawner { | ||
| 204 | fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> { | ||
| 205 | let task = token.raw_task; | ||
| 206 | core::mem::forget(token); | ||
| 207 | |||
| 208 | match task { | ||
| 209 | Some(task) => { | ||
| 210 | // Set the name and ID when trace is enabled | ||
| 211 | task.set_name(Some(name)); | ||
| 212 | let task_id = task.as_ptr() as u32; | ||
| 213 | task.set_id(task_id); | ||
| 214 | |||
| 215 | unsafe { self.executor.spawn(task) }; | ||
| 216 | Ok(()) | ||
| 217 | } | ||
| 218 | None => Err(SpawnError::Busy), | ||
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Implementation of the SpawnerTraceExt trait for Spawner when trace is disabled | ||
| 224 | #[cfg(not(feature = "trace"))] | ||
| 225 | impl SpawnerTraceExt for Spawner { | ||
| 226 | fn spawn_named<S>(&self, _name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> { | ||
| 227 | // When trace is disabled, just forward to regular spawn and ignore the name | ||
| 228 | self.spawn(token) | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 183 | /// Handle to spawn tasks into an executor from any thread. | 232 | /// Handle to spawn tasks into an executor from any thread. |
| 184 | /// | 233 | /// |
| 185 | /// This Spawner can be used from any thread (it is Send), but it can | 234 | /// This Spawner can be used from any thread (it is Send), but it can |
