Skip to main content

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::ErrorCode;
11use crate::capabilities::MemoryAllocationCapability;
12use crate::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
13use crate::kernel::Kernel;
14use crate::process;
15use crate::process::ProcessId;
16use crate::processbuffer::ReadableProcessBuffer;
17use crate::syscall_driver::{CommandReturn, SyscallDriver};
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    ///
69    /// # Safety
70    ///
71    /// Scheduling the upcall is what causes a shared buffer to be shared with
72    /// the target (`schedule_on`) process. This causes an MPU region to be
73    /// created for the target process, giving it access to the shared buffer.
74    ///
75    /// The caller must ensure that source (`called_from`) process is not
76    /// deallocated/reclaimed while the buffer is still shared (i.e., the MPU
77    /// region is mapped). Failing to ensure this could potentially give the
78    /// original target process access to memory allocated for a new process.
79    /// More problematically, that memory could be used for the kernel's grant
80    /// region for a new process, and allowing a process to access that memory
81    /// violates memory safety for the kernel.
82    pub(crate) unsafe fn schedule_upcall(
83        &self,
84        schedule_on: ProcessId,
85        called_from: ProcessId,
86        cb_type: IPCUpcallType,
87    ) -> Result<(), process::Error> {
88        let schedule_on_id = schedule_on.index().ok_or(process::Error::NoSuchApp)?;
89        let called_from_id = called_from.index().ok_or(process::Error::NoSuchApp)?;
90
91        // Verify that IPC is not trying to share with the same app. If so, this
92        // will cause a double grant enter if we don't return now.
93        if schedule_on_id == called_from_id {
94            return Err(process::Error::AlreadyInUse);
95        }
96
97        self.data.enter(schedule_on, |_, schedule_on_data| {
98            self.data.enter(called_from, |_, called_from_data| {
99                // If the other app shared a buffer with us, make
100                // sure we have access to that slice and then call
101                // the upcall. If no slice was shared then just
102                // call the upcall.
103                let (len, ptr) = match called_from_data.get_readwrite_processbuffer(schedule_on_id)
104                {
105                    Ok(slice) => {
106                        // Ensure receiving app has MPU access to sending app's buffer
107                        self.data
108                            .kernel
109                            .process_map_or(None, schedule_on, |process| {
110                                process.add_mpu_region(slice.ptr(), slice.len(), slice.len())
111                            });
112                        (slice.len(), slice.ptr() as usize)
113                    }
114                    Err(_) => (0, 0),
115                };
116                let to_schedule: usize = match cb_type {
117                    IPCUpcallType::Service => schedule_on_id,
118                    IPCUpcallType::Client => called_from_id,
119                };
120                let _ = schedule_on_data.schedule_upcall(to_schedule, (called_from_id, len, ptr));
121            })
122        })?
123    }
124}
125
126impl<const NUM_PROCS: u8> SyscallDriver for IPC<NUM_PROCS> {
127    /// command is how notify() is implemented.
128    /// Notifying an IPC service is done by setting client_or_svc to 0,
129    /// and notifying an IPC client is done by setting client_or_svc to 1.
130    /// In either case, the target_id is the same number as provided in a notify
131    /// upcall or as returned by allow.
132    ///
133    /// Returns INVAL if the other process doesn't exist.
134    ///
135    /// Initiates a service discovery or notifies a client or service.
136    ///
137    /// ### `command_num`
138    ///
139    /// - `0`: Driver existence check, always returns Ok(())
140    /// - `1`: Perform discovery on the package name passed to `allow_readonly`.
141    ///   Returns the service descriptor if the service is found, otherwise
142    ///   returns an error.
143    /// - `2`: Notify a service previously discovered to have the service
144    ///   descriptor in `target_id`. Returns an error if `target_id` refers to
145    ///   an invalid service or the notify fails to enqueue.
146    /// - `3`: Notify a client with descriptor `target_id`, typically in
147    ///   response to a previous notify from the client. Returns an error if
148    ///   `target_id` refers to an invalid client or the notify fails to
149    ///   enqueue.
150    fn command(
151        &self,
152        command_number: usize,
153        target_id: usize,
154        _: usize,
155        processid: ProcessId,
156    ) -> CommandReturn {
157        match command_number {
158            0 => CommandReturn::success(),
159            1 =>
160            /* Discover */
161            {
162                self.data
163                    .enter(processid, |_, kernel_data| {
164                        kernel_data
165                            .get_readonly_processbuffer(ro_allow::SEARCH)
166                            .and_then(|search| {
167                                search.enter(|slice| {
168                                    self.data
169                                        .kernel
170                                        .process_until(|p| {
171                                            let s = p.get_process_name().as_bytes();
172                                            // are slices equal?
173                                            if s.len() == slice.len()
174                                                && s.iter()
175                                                    .zip(slice.iter())
176                                                    .all(|(c1, c2)| *c1 == c2.get())
177                                            {
178                                                // Return the index of the process which is used for
179                                                // subscribe number
180                                                p.processid()
181                                                    .index()
182                                                    .map(|i| CommandReturn::success_u32(i as u32))
183                                            } else {
184                                                None
185                                            }
186                                        })
187                                        .unwrap_or(CommandReturn::failure(ErrorCode::NODEVICE))
188                                })
189                            })
190                            .unwrap_or(CommandReturn::failure(ErrorCode::INVAL))
191                    })
192                    .unwrap_or(CommandReturn::failure(ErrorCode::NOMEM))
193            }
194            2 =>
195            /* Service notify */
196            {
197                let cb_type = IPCUpcallType::Service;
198
199                let other_process =
200                    self.data
201                        .kernel
202                        .process_until(|p| match p.processid().index() {
203                            Some(i) if i == target_id => Some(p.processid()),
204                            _ => None,
205                        });
206
207                other_process.map_or(CommandReturn::failure(ErrorCode::INVAL), |otherapp| {
208                    self.data.kernel.process_map_or(
209                        CommandReturn::failure(ErrorCode::INVAL),
210                        otherapp,
211                        |target| {
212                            let ret = target.enqueue_task(process::Task::IPC((processid, cb_type)));
213                            match ret {
214                                Ok(()) => CommandReturn::success(),
215                                Err(e) => {
216                                    // `enqueue_task` does not provide information on whether the
217                                    // recipient has set a non-null callback. It only reports
218                                    // general failures, such as insufficient memory in the pending
219                                    // tasks queue
220                                    CommandReturn::failure(e)
221                                }
222                            }
223                        },
224                    )
225                })
226            }
227            3 =>
228            /* Client notify */
229            {
230                let cb_type = IPCUpcallType::Client;
231
232                let other_process =
233                    self.data
234                        .kernel
235                        .process_until(|p| match p.processid().index() {
236                            Some(i) if i == target_id => Some(p.processid()),
237                            _ => None,
238                        });
239
240                other_process.map_or(CommandReturn::failure(ErrorCode::INVAL), |otherapp| {
241                    self.data.kernel.process_map_or(
242                        CommandReturn::failure(ErrorCode::INVAL),
243                        otherapp,
244                        |target| {
245                            let ret = target.enqueue_task(process::Task::IPC((processid, cb_type)));
246                            match ret {
247                                Ok(()) => CommandReturn::success(),
248                                Err(e) => {
249                                    // `enqueue_task` does not provide information on whether the
250                                    // recipient has set a non-null callback. It only reports
251                                    // general failures, such as insufficient memory in the pending
252                                    // tasks queue
253                                    CommandReturn::failure(e)
254                                }
255                            }
256                        },
257                    )
258                })
259            }
260            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
261        }
262    }
263
264    fn allocate_grant(&self, processid: ProcessId) -> Result<(), crate::process::Error> {
265        self.data.enter(processid, |_, _| {})
266    }
267}