kernel/
ipc.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 2022.
4
5//! Inter-process communication mechanism for Tock.
6//!
7//! This is a special syscall driver that allows userspace applications to
8//! share memory.
9
10use crate::capabilities::MemoryAllocationCapability;
11use crate::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
12use crate::kernel::Kernel;
13use crate::process;
14use crate::process::ProcessId;
15use crate::processbuffer::ReadableProcessBuffer;
16use crate::syscall_driver::{CommandReturn, SyscallDriver};
17use crate::ErrorCode;
18
19/// Syscall number
20pub const DRIVER_NUM: usize = 0x10000;
21
22/// Ids for read-only allow buffers
23mod ro_allow {
24    pub(super) const SEARCH: usize = 0;
25    /// The number of allow buffers the kernel stores for this grant.
26    pub(super) const COUNT: u8 = 1;
27}
28
29/// Enum to mark which type of upcall is scheduled for the IPC mechanism.
30#[derive(Copy, Clone, Debug)]
31pub enum IPCUpcallType {
32    /// Indicates that the upcall is for the service upcall handler this
33    /// process has setup.
34    Service,
35    /// Indicates that the upcall is from a different service app and will
36    /// call one of the client upcalls setup by this process.
37    Client,
38}
39
40/// State that is stored in each process's grant region to support IPC.
41#[derive(Default)]
42struct IPCData;
43
44/// The IPC mechanism struct.
45pub struct IPC<const NUM_PROCS: u8> {
46    /// The grant regions for each process that holds the per-process IPC data.
47    data: Grant<
48        IPCData,
49        UpcallCount<NUM_PROCS>,
50        AllowRoCount<{ ro_allow::COUNT }>,
51        AllowRwCount<NUM_PROCS>,
52    >,
53}
54
55impl<const NUM_PROCS: u8> IPC<NUM_PROCS> {
56    pub fn new(
57        kernel: &'static Kernel,
58        driver_num: usize,
59        capability: &dyn MemoryAllocationCapability,
60    ) -> Self {
61        Self {
62            data: kernel.create_grant(driver_num, capability),
63        }
64    }
65
66    /// Schedule an IPC upcall for a process. This is called by the main
67    /// scheduler loop if an IPC task was queued for the process.
68    pub(crate) unsafe fn schedule_upcall(
69        &self,
70        schedule_on: ProcessId,
71        called_from: ProcessId,
72        cb_type: IPCUpcallType,
73    ) -> Result<(), process::Error> {
74        let schedule_on_id = schedule_on.index().ok_or(process::Error::NoSuchApp)?;
75        let called_from_id = called_from.index().ok_or(process::Error::NoSuchApp)?;
76
77        // Verify that IPC is not trying to share with the same app. If so, this
78        // will cause a double grant enter if we don't return now.
79        if schedule_on_id == called_from_id {
80            return Err(process::Error::AlreadyInUse);
81        }
82
83        self.data.enter(schedule_on, |_, schedule_on_data| {
84            self.data.enter(called_from, |_, called_from_data| {
85                // If the other app shared a buffer with us, make
86                // sure we have access to that slice and then call
87                // the upcall. If no slice was shared then just
88                // call the upcall.
89                let (len, ptr) = match called_from_data.get_readwrite_processbuffer(schedule_on_id)
90                {
91                    Ok(slice) => {
92                        // Ensure receiving app has MPU access to sending app's buffer
93                        self.data
94                            .kernel
95                            .process_map_or(None, schedule_on, |process| {
96                                process.add_mpu_region(slice.ptr(), slice.len(), slice.len())
97                            });
98                        (slice.len(), slice.ptr() as usize)
99                    }
100                    Err(_) => (0, 0),
101                };
102                let to_schedule: usize = match cb_type {
103                    IPCUpcallType::Service => schedule_on_id,
104                    IPCUpcallType::Client => called_from_id,
105                };
106                let _ = schedule_on_data.schedule_upcall(to_schedule, (called_from_id, len, ptr));
107            })
108        })?
109    }
110}
111
112impl<const NUM_PROCS: u8> SyscallDriver for IPC<NUM_PROCS> {
113    /// command is how notify() is implemented.
114    /// Notifying an IPC service is done by setting client_or_svc to 0,
115    /// and notifying an IPC client is done by setting client_or_svc to 1.
116    /// In either case, the target_id is the same number as provided in a notify
117    /// upcall or as returned by allow.
118    ///
119    /// Returns INVAL if the other process doesn't exist.
120
121    /// Initiates a service discovery or notifies a client or service.
122    ///
123    /// ### `command_num`
124    ///
125    /// - `0`: Driver existence check, always returns Ok(())
126    /// - `1`: Perform discovery on the package name passed to `allow_readonly`.
127    ///   Returns the service descriptor if the service is found, otherwise
128    ///   returns an error.
129    /// - `2`: Notify a service previously discovered to have the service
130    ///   descriptor in `target_id`. Returns an error if `target_id` refers to
131    ///   an invalid service or the notify fails to enqueue.
132    /// - `3`: Notify a client with descriptor `target_id`, typically in
133    ///   response to a previous notify from the client. Returns an error if
134    ///   `target_id` refers to an invalid client or the notify fails to
135    ///   enqueue.
136    fn command(
137        &self,
138        command_number: usize,
139        target_id: usize,
140        _: usize,
141        processid: ProcessId,
142    ) -> CommandReturn {
143        match command_number {
144            0 => CommandReturn::success(),
145            1 =>
146            /* Discover */
147            {
148                self.data
149                    .enter(processid, |_, kernel_data| {
150                        kernel_data
151                            .get_readonly_processbuffer(ro_allow::SEARCH)
152                            .and_then(|search| {
153                                search.enter(|slice| {
154                                    self.data
155                                        .kernel
156                                        .process_until(|p| {
157                                            let s = p.get_process_name().as_bytes();
158                                            // are slices equal?
159                                            if s.len() == slice.len()
160                                                && s.iter()
161                                                    .zip(slice.iter())
162                                                    .all(|(c1, c2)| *c1 == c2.get())
163                                            {
164                                                // Return the index of the process which is used for
165                                                // subscribe number
166                                                p.processid()
167                                                    .index()
168                                                    .map(|i| CommandReturn::success_u32(i as u32))
169                                            } else {
170                                                None
171                                            }
172                                        })
173                                        .unwrap_or(CommandReturn::failure(ErrorCode::NODEVICE))
174                                })
175                            })
176                            .unwrap_or(CommandReturn::failure(ErrorCode::INVAL))
177                    })
178                    .unwrap_or(CommandReturn::failure(ErrorCode::NOMEM))
179            }
180            2 =>
181            /* Service notify */
182            {
183                let cb_type = IPCUpcallType::Service;
184
185                let other_process =
186                    self.data
187                        .kernel
188                        .process_until(|p| match p.processid().index() {
189                            Some(i) if i == target_id => Some(p.processid()),
190                            _ => None,
191                        });
192
193                other_process.map_or(CommandReturn::failure(ErrorCode::INVAL), |otherapp| {
194                    self.data.kernel.process_map_or(
195                        CommandReturn::failure(ErrorCode::INVAL),
196                        otherapp,
197                        |target| {
198                            let ret = target.enqueue_task(process::Task::IPC((processid, cb_type)));
199                            match ret {
200                                Ok(()) => CommandReturn::success(),
201                                Err(e) => {
202                                    // `enqueue_task` does not provide information on whether the
203                                    // recipient has set a non-null callback. It only reports
204                                    // general failures, such as insufficient memory in the pending
205                                    // tasks queue
206                                    CommandReturn::failure(e)
207                                }
208                            }
209                        },
210                    )
211                })
212            }
213            3 =>
214            /* Client notify */
215            {
216                let cb_type = IPCUpcallType::Client;
217
218                let other_process =
219                    self.data
220                        .kernel
221                        .process_until(|p| match p.processid().index() {
222                            Some(i) if i == target_id => Some(p.processid()),
223                            _ => None,
224                        });
225
226                other_process.map_or(CommandReturn::failure(ErrorCode::INVAL), |otherapp| {
227                    self.data.kernel.process_map_or(
228                        CommandReturn::failure(ErrorCode::INVAL),
229                        otherapp,
230                        |target| {
231                            let ret = target.enqueue_task(process::Task::IPC((processid, cb_type)));
232                            match ret {
233                                Ok(()) => CommandReturn::success(),
234                                Err(e) => {
235                                    // `enqueue_task` does not provide information on whether the
236                                    // recipient has set a non-null callback. It only reports
237                                    // general failures, such as insufficient memory in the pending
238                                    // tasks queue
239                                    CommandReturn::failure(e)
240                                }
241                            }
242                        },
243                    )
244                })
245            }
246            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
247        }
248    }
249
250    fn allocate_grant(&self, processid: ProcessId) -> Result<(), crate::process::Error> {
251        self.data.enter(processid, |_, _| {})
252    }
253}