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}