capsules_extra/process_info_driver.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5//! Allow userspace to inspect and control processes on the board.
6//!
7//! ## Warning!
8//!
9//! This capsule is designed for testing and experimental use cases only. It
10//! should not be used in production! It was originally written for use in an
11//! educational tutorial to make it easy to interact with processes stored on
12//! the board from userspace using a screen.
13//!
14//! This capsule does require a capability to also indicate that this interacts
15//! with processes in a way that common capsules should not.
16//!
17//! ## Commands
18//!
19//! - 0: Check driver exists.
20//! - 1: Get the count of processes running on the board.
21//! - 2: Fill the allow RW buffer with the process IDs for the running processes
22//! on the board. Returns the number of running processes.
23//! - 3: Fill the allow RW buffer with the short IDs for the running processes
24//! on the board. Returns the number of running processes.
25//! - 4: Put the name of the process specified by the process ID in `data1` in
26//! the allow RW buffer (as much as will fit). Returns the full length of the
27//! process name.
28//! - 5: Fill the allow RW buffer with the following information for the process
29//! specified by the process ID in `data1`.
30//! - The number of timeslice expirations.
31//! - The number of syscalls called.
32//! - The number of restarts.
33//! - The current process state (running=0, yielded=1, yieldedfor=2,
34//! stopped=3, faulted=4, terminated=5).
35//! - 6: Change the process state. `data1` is the process ID, and `data2` is the
36//! new state.(1=start, 2=stop, 3=fault, 4=terminate, 5=boot).
37
38use kernel::capabilities::{ProcessManagementCapability, ProcessStartCapability};
39use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
40use kernel::process;
41use kernel::processbuffer::WriteableProcessBuffer;
42use kernel::syscall::{CommandReturn, SyscallDriver};
43use kernel::Kernel;
44use kernel::{ErrorCode, ProcessId};
45
46/// Syscall driver number.
47use capsules_core::driver;
48pub const DRIVER_NUM: usize = driver::NUM::ProcessInfo as usize;
49
50mod rw_allow {
51 pub const INFO: usize = 0;
52 /// The number of allow buffers the kernel stores for this grant
53 pub const COUNT: u8 = 1;
54}
55
56pub struct ProcessInfo<C: ProcessManagementCapability + ProcessStartCapability> {
57 apps: Grant<(), UpcallCount<0>, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>,
58 /// Reference to the kernel object so we can access process state.
59 kernel: &'static Kernel,
60 /// Capability needed to interact with and control processes.
61 capability: C,
62}
63
64impl<C: ProcessManagementCapability + ProcessStartCapability> ProcessInfo<C> {
65 pub fn new(
66 kernel: &'static Kernel,
67 grant: Grant<(), UpcallCount<0>, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>,
68 capability: C,
69 ) -> Self {
70 Self {
71 kernel,
72 apps: grant,
73 capability,
74 }
75 }
76
77 fn iterate_u32<F>(&self, process_id: ProcessId, func: F) -> u32
78 where
79 F: Fn(&dyn kernel::process::Process) -> u32,
80 {
81 let mut count = 0;
82 let _ = self.apps.enter(process_id, |_app, kernel_data| {
83 let _ = kernel_data
84 .get_readwrite_processbuffer(rw_allow::INFO)
85 .and_then(|shared| {
86 shared.mut_enter(|s| {
87 let mut chunks = s.chunks(size_of::<u32>());
88
89 self.kernel
90 .process_each_capability(&self.capability, |process| {
91 // Get the next chunk to write the next
92 // PID into.
93 if let Some(chunk) = chunks.next() {
94 let _ =
95 chunk.copy_from_slice_or_err(&func(process).to_le_bytes());
96 }
97 count += 1;
98 });
99 })
100 });
101 });
102 count as u32
103 }
104}
105
106impl<C: ProcessManagementCapability + ProcessStartCapability> SyscallDriver for ProcessInfo<C> {
107 fn command(
108 &self,
109 command_num: usize,
110 data1: usize,
111 data2: usize,
112 process_id: ProcessId,
113 ) -> CommandReturn {
114 match command_num {
115 // Driver existence check
116 0 => CommandReturn::success(),
117
118 1 => {
119 let mut count = 0;
120 self.kernel
121 .process_each_capability(&self.capability, |_process| {
122 count += 1;
123 });
124 CommandReturn::success_u32(count)
125 }
126
127 2 => {
128 let count = self.iterate_u32(process_id, |process| process.processid().id() as u32);
129 CommandReturn::success_u32(count)
130 }
131
132 3 => {
133 let count = self.iterate_u32(process_id, |process| match process.short_app_id() {
134 kernel::process::ShortId::LocallyUnique => 0,
135 kernel::process::ShortId::Fixed(id) => id.into(),
136 });
137 CommandReturn::success_u32(count)
138 }
139
140 4 => self
141 .apps
142 .enter(process_id, |_app, kernel_data| {
143 kernel_data
144 .get_readwrite_processbuffer(rw_allow::INFO)
145 .and_then(|shared| {
146 shared.mut_enter(|s| {
147 // We need to differentiate between no matching
148 // apps (based on ProcessId) and an app with a
149 // 0 length name.
150 let mut matched_name_len: Option<usize> = None;
151
152 self.kernel
153 .process_each_capability(&self.capability, |process| {
154 if process.processid().id() == data1 {
155 let n = process.get_process_name().as_bytes();
156
157 let name_len = n.len();
158 let buffer_len = s.len();
159 let copy_len = core::cmp::min(name_len, buffer_len);
160
161 // Copy as much as we can into the
162 // allowed buffer.
163 s.get(0..copy_len).map(|dest| {
164 n.get(0..copy_len).map(|src| {
165 let _ = dest.copy_from_slice_or_err(src);
166 });
167 });
168
169 // Return that we did find a
170 // matching app with a name of a
171 // specific length.
172 matched_name_len = Some(name_len);
173 }
174 });
175 if let Some(nlen) = matched_name_len {
176 CommandReturn::success_u32(nlen as u32)
177 } else {
178 CommandReturn::failure(ErrorCode::INVAL)
179 }
180 })
181 })
182 .unwrap_or_else(|err| CommandReturn::failure(err.into()))
183 })
184 .unwrap_or_else(|err| CommandReturn::failure(err.into())),
185
186 5 => self
187 .apps
188 .enter(process_id, |_app, kernel_data| {
189 kernel_data
190 .get_readwrite_processbuffer(rw_allow::INFO)
191 .and_then(|shared| {
192 shared.mut_enter(|s| {
193 let mut chunks = s.chunks(size_of::<u32>());
194 self.kernel
195 .process_each_capability(&self.capability, |process| {
196 if process.processid().id() == data1 {
197 if let Some(chunk) = chunks.next() {
198 let _ = chunk.copy_from_slice_or_err(
199 &process
200 .debug_timeslice_expiration_count()
201 .to_le_bytes(),
202 );
203 }
204 if let Some(chunk) = chunks.next() {
205 let _ = chunk.copy_from_slice_or_err(
206 &process.debug_syscall_count().to_le_bytes(),
207 );
208 }
209 if let Some(chunk) = chunks.next() {
210 let _ = chunk.copy_from_slice_or_err(
211 &process.get_restart_count().to_le_bytes(),
212 );
213 }
214 if let Some(chunk) = chunks.next() {
215 let process_state_id: u32 =
216 match process.get_state() {
217 process::State::Running => 0,
218 process::State::Yielded => 1,
219 process::State::YieldedFor(_) => 2,
220 process::State::Stopped(_) => 3,
221 process::State::Faulted => 4,
222 process::State::Terminated => 5,
223 };
224
225 let _ = chunk.copy_from_slice_or_err(
226 &process_state_id.to_le_bytes(),
227 );
228 }
229 }
230 });
231 CommandReturn::success()
232 })
233 })
234 .unwrap_or_else(|err| CommandReturn::failure(err.into()))
235 })
236 .unwrap_or_else(|err| CommandReturn::failure(err.into())),
237
238 6 => {
239 let mut matched = false;
240 self.kernel
241 .process_each_capability(&self.capability, |process| {
242 if process.processid().id() == data1 {
243 matched = true;
244
245 match data2 {
246 1 => {
247 // START
248 process.resume();
249 }
250 2 => {
251 // STOP
252 process.stop();
253 }
254
255 3 => {
256 // FAULT
257 process.set_fault_state();
258 }
259
260 4 => {
261 // TERMINATE
262 process.terminate(None);
263 }
264
265 5 => {
266 // BOOT
267 if process.get_state() == process::State::Terminated {
268 process.start(&self.capability);
269 }
270 }
271
272 _ => {}
273 }
274 }
275 });
276 if matched {
277 CommandReturn::success()
278 } else {
279 CommandReturn::failure(ErrorCode::INVAL)
280 }
281 }
282
283 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
284 }
285 }
286
287 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
288 self.apps.enter(processid, |_, _| {})
289 }
290}