1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
//! Inter-process communication mechanism for Tock.
//!
//! This is a special syscall driver that allows userspace applications to
//! share memory.

use crate::callback::{AppId, Callback};
use crate::capabilities::MemoryAllocationCapability;
use crate::driver::Driver;
use crate::grant::Grant;
use crate::mem::{AppSlice, Shared};
use crate::process;
use crate::returncode::ReturnCode;
use crate::sched::Kernel;

/// Syscall number
pub const DRIVER_NUM: usize = 0x10000;

struct IPCData {
    shared_memory: [Option<AppSlice<Shared, u8>>; 8],
    client_callbacks: [Option<Callback>; 8],
    callback: Option<Callback>,
}

impl Default for IPCData {
    fn default() -> IPCData {
        IPCData {
            shared_memory: [None, None, None, None, None, None, None, None],
            client_callbacks: [None, None, None, None, None, None, None, None],
            callback: None,
        }
    }
}

pub struct IPC {
    data: Grant<IPCData>,
}

impl IPC {
    pub fn new(kernel: &'static Kernel, capability: &dyn MemoryAllocationCapability) -> IPC {
        IPC {
            data: kernel.create_grant(capability),
        }
    }

    pub unsafe fn schedule_callback(
        &self,
        appid: AppId,
        otherapp: AppId,
        cb_type: process::IPCType,
    ) {
        self.data
            .enter(appid, |mydata, _| {
                let callback = match cb_type {
                    process::IPCType::Service => mydata.callback,
                    process::IPCType::Client => {
                        *mydata.client_callbacks.get(otherapp.idx()).unwrap_or(&None)
                    }
                };
                callback.map_or((), |mut callback| {
                    self.data
                        .enter(otherapp, |otherdata, _| {
                            if appid.idx() >= otherdata.shared_memory.len() {
                                return;
                            }
                            match otherdata.shared_memory[appid.idx()] {
                                Some(ref slice) => {
                                    slice.expose_to(appid);
                                    callback.schedule(
                                        otherapp.idx() + 1,
                                        slice.len(),
                                        slice.ptr() as usize,
                                    );
                                }
                                None => {
                                    callback.schedule(otherapp.idx() + 1, 0, 0);
                                }
                            }
                        })
                        .unwrap_or(());
                });
            })
            .unwrap_or(());
    }
}

impl Driver for IPC {
    /// subscribe enables processes using IPC to register callbacks that fire
    /// when notify() is called.
    fn subscribe(
        &self,
        subscribe_num: usize,
        callback: Option<Callback>,
        app_id: AppId,
    ) -> ReturnCode {
        match subscribe_num {
            // subscribe(0)
            //
            // Subscribe with subscribe_num == 0 is how a process registers
            // itself as an IPC service. Each process can only register as a
            // single IPC service. The identifier for the IPC service is the
            // application name stored in the TBF header of the application.
            // The callback that is passed to subscribe is called when another
            // process notifies the server process.
            0 => self
                .data
                .enter(app_id, |data, _| {
                    data.callback = callback;
                    ReturnCode::SUCCESS
                })
                .unwrap_or(ReturnCode::EBUSY),

            // subscribe(>=1)
            //
            // Subscribe with subscribe_num >= 1 is how a client registers
            // a callback for a given service. The service number (passed
            // here as subscribe_num) is returned from the allow() call.
            // Once subscribed, the client will receive callbacks when the
            // service process calls notify_client().
            svc_id => {
                if svc_id - 1 >= 8 {
                    ReturnCode::EINVAL /* Maximum of 8 IPC's exceeded */
                } else {
                    self.data
                        .enter(app_id, |data, _| {
                            data.client_callbacks[svc_id - 1] = callback;
                            ReturnCode::SUCCESS
                        })
                        .unwrap_or(ReturnCode::EBUSY)
                }
            }
        }
    }

    /// command is how notify() is implemented.
    /// Notifying an IPC service is done by setting client_or_svc to 0,
    /// and notifying an IPC client is done by setting client_or_svc to 1.
    /// In either case, the target_id is the same number as provided in a notify
    /// callback or as returned by allow.
    ///
    /// Returns EINVAL if the other process doesn't exist.
    fn command(
        &self,
        target_id: usize,
        client_or_svc: usize,
        _: usize,
        appid: AppId,
    ) -> ReturnCode {
        let cb_type = if client_or_svc == 0 {
            process::IPCType::Service
        } else {
            process::IPCType::Client
        };

        self.data
            .kernel
            .process_map_or(ReturnCode::EINVAL, target_id - 1, |target| {
                let ret = target.enqueue_task(process::Task::IPC((appid, cb_type)));
                match ret {
                    true => ReturnCode::SUCCESS,
                    false => ReturnCode::FAIL,
                }
            })
    }

    /// allow enables processes to discover IPC services on the platform or
    /// share buffers with existing services.
    ///
    /// If allow is called with target_id == 0, it is an IPC service discover
    /// call. The contents of the slice should be the string name of the IPC
    /// service. If this mechanism can find that service, allow will return
    /// an ID that can be used to notify that service. Otherwise an error will
    /// be returned.
    ///
    /// If allow is called with target_id >= 1, it is a share command where the
    /// application is explicitly sharing a slice with an IPC service (as
    /// specified by the target_id). allow() simply allows both processes to
    /// access the buffer, it does not signal the service.
    fn allow(
        &self,
        appid: AppId,
        target_id: usize,
        slice: Option<AppSlice<Shared, u8>>,
    ) -> ReturnCode {
        if target_id == 0 {
            match slice {
                Some(slice_data) => {
                    let ret = self.data.kernel.process_until(|p| {
                        let s = p.get_process_name().as_bytes();
                        // are slices equal?
                        if s.len() == slice_data.len()
                            && s.iter().zip(slice_data.iter()).all(|(c1, c2)| c1 == c2)
                        {
                            ReturnCode::SuccessWithValue {
                                value: (p.appid().idx() as usize) + 1,
                            }
                        } else {
                            ReturnCode::FAIL
                        }
                    });
                    if ret != ReturnCode::FAIL {
                        return ret;
                    }
                }
                None => {}
            }

            return ReturnCode::EINVAL; /* AppSlice must have non-zero length */
        }
        self.data
            .enter(appid, |data, _| {
                data.shared_memory.get_mut(target_id - 1).map_or(
                    ReturnCode::EINVAL, /* Target process does not exist */
                    |smem| {
                        *smem = slice;
                        ReturnCode::SUCCESS
                    },
                )
            })
            .unwrap_or(ReturnCode::EBUSY)
    }
}