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}