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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(not(target_os = "none"), allow(dead_code))]
#![cfg_attr(not(target_os = "none"), allow(unused_imports))]
#![cfg_attr(not(target_os = "none"), allow(unused_variables))]

pub mod api;
pub use api::*;
use num_traits::{FromPrimitive, ToPrimitive};
use xous::{msg_scalar_unpack, send_message, Message, CID};
use xous_ipc::Buffer;

#[derive(Debug)]
pub struct Susres {
    conn: CID,
    suspend_cb_sid: Option<xous::SID>,
}
impl Susres {
    #[cfg(any(feature = "precursor", feature = "renode"))]
    /// When created, the `susres` object can be configured with a `SuspendOrder` to enforce
    /// sequencing rules in shutdown. It also requires arguments to define a callback which is
    /// pinged when a suspend event arrives. The callback takes the form of a `CID, discriminant`
    /// pair, where the CID is the local connection ID to the caller (in other words, a self-connection
    /// to the caller), and the discriminant is the number placed into the Xous message's `body.id()`
    /// field. This is typically just the descriminant of the main loop's opcode enum for the
    /// suspend-resume opcode.
    pub fn new(
        order: Option<SuspendOrder>,
        xns: &xous_names::XousNames,
        cb_discriminant: u32,
        cid: CID,
    ) -> Result<Self, xous::Error> {
        REFCOUNT.fetch_add(1, Ordering::Relaxed);
        let conn = xns.request_connection_blocking(api::SERVER_NAME_SUSRES).expect("Can't connect to SUSRES");

        let sid = xous::create_server().unwrap();
        let sid_tuple = sid.to_u32();
        xous::create_thread_4(
            suspend_cb_server,
            sid_tuple.0 as usize,
            sid_tuple.1 as usize,
            sid_tuple.2 as usize,
            sid_tuple.3 as usize,
        )
        .unwrap();
        let hookdata = ScalarHook {
            sid: sid_tuple,
            id: cb_discriminant,
            cid,
            order: order.unwrap_or(SuspendOrder::Normal),
        };
        log::debug!("hooking {:?}", hookdata);
        let buf = Buffer::into_buf(hookdata).or(Err(xous::Error::InternalError))?;
        buf.lend(conn, Opcode::SuspendEventSubscribe.to_u32().unwrap())?;

        Ok(Susres { conn, suspend_cb_sid: Some(sid) })
    }

    // suspend/resume is not implemented in hosted mode, and will break if you try to do it.
    // the main reason this was done is actually it seems hosted mode can't handle the level
    // of concurrency introduced by suspend/resume, as its underlying IPC mechanisms are quite
    // different and have a lot of overhead; it seems like the system goes into a form of deadlock
    // during boot when all the hosted mode servers try to connect. This isn't an issue on real hardware.
    #[cfg(not(target_os = "xous"))]
    /// When created, the `susres` object can be configured with a `SuspendOrder` to enforce
    /// sequencing rules in shutdown. It also requires arguments to define a callback which is
    /// pinged when a suspend event arrives. The callback takes the form of a `CID, discriminant`
    /// pair, where the CID is the local connection ID to the caller (in other words, a self-connection
    /// to the caller), and the discriminant is the number placed into the Xous message's `body.id()`
    /// field. This is typically just the descriminant of the main loop's opcode enum for the
    /// suspend-resume opcode.
    pub fn new(
        _ordering: Option<SuspendOrder>,
        xns: &xous_names::XousNames,
        cb_discriminant: u32,
        cid: CID,
    ) -> Result<Self, xous::Error> {
        REFCOUNT.fetch_add(1, Ordering::Relaxed);
        Ok(Susres { conn: 0, suspend_cb_sid: None })
    }

    /// Creates a connection to the `susres` server, but without a callback. This is useful
    /// for services that are suspend-insensitive, but need to manipulate the state of
    /// the machine (such as initiating a suspend).
    pub fn new_without_hook(xns: &xous_names::XousNames) -> Result<Self, xous::Error> {
        REFCOUNT.fetch_add(1, Ordering::Relaxed);
        let conn = xns.request_connection_blocking(api::SERVER_NAME_SUSRES)?;
        Ok(Susres { conn, suspend_cb_sid: None })
    }

    /// This call initiates a suspend. It will sequence through the suspend events; and
    /// if any services are unable to suspend within a defined time-out window, the call
    /// will fail with a `xous::Error::Timeout`.
    ///
    /// NB: services such as the SHA engine and
    /// the Curve25519 engine can deny a suspend because they have large internal state
    /// that can't be saved to battery-backed RAM.
    pub fn initiate_suspend(&self) -> Result<(), xous::Error> {
        log::trace!("suspend initiated");
        match send_message(
            self.conn,
            Message::new_blocking_scalar(Opcode::SuspendRequest.to_usize().unwrap(), 0, 0, 0, 0),
        ) {
            Ok(xous::Result::Scalar1(result)) => {
                if result == 1 {
                    Ok(())
                } else {
                    // indicate that we couldn't initiate the suspend
                    Err(xous::Error::Timeout)
                }
            }
            _ => Err(xous::Error::InternalError),
        }
    }

    /// This call is used by services that are suspend-sensitive. They are used to
    /// acknowledge the callback from the suspend sequencer; calling this function
    /// basically tells the sequencer "I'm ready to suspend immediately". Likewise,
    /// this call blocks until the system resumes.
    ///
    /// Note that from the perspective of the caller, this call magically appears
    /// like it returns almost immediately, because, the time spent in suspend does
    /// not increment the ticktimer or system time. The only evidence of a suspend
    /// would be a difference in the current RTC timestamp.
    pub fn suspend_until_resume(&mut self, token: usize) -> Result<bool, xous::Error> {
        if self.suspend_cb_sid.is_none() {
            // this happens if you created without a hook
            return Err(xous::Error::UseBeforeInit);
        }
        log::debug!("token {} pid {} suspending", token, xous::process::id()); // <-- use this to debug s/r
        xous::yield_slice();
        // first tell the susres server that we're ready to suspend
        send_message(
            self.conn,
            Message::new_scalar(Opcode::SuspendReady.to_usize().unwrap(), token, 0, 0, 0),
        )
        .map(|_| ())?;
        log::trace!("blocking until suspend");

        // sometime between here and when this next message unblocks, the power went out...

        // now block until we've resumed
        send_message(
            self.conn,
            Message::new_blocking_scalar(Opcode::SuspendingNow.to_usize().unwrap(), 0, 0, 0, 0),
        )
        .map(|_| ())?;

        let response = send_message(
            self.conn,
            Message::new_blocking_scalar(Opcode::WasSuspendClean.to_usize().unwrap(), token, 0, 0, 0),
        )
        .expect("couldn't query if my suspend was successful");
        if let xous::Result::Scalar1(result) = response {
            if result != 0 {
                log::debug!("resume pid {} clean", xous::process::id()); // <-- use this to debug s/r
                Ok(true)
            } else {
                log::debug!("resume pid {} dirty", xous::process::id()); // <-- use this to debug s/r
                Ok(false)
            }
        } else {
            Err(xous::Error::InternalError)
        }
    }

    /// This is a call that a service can make to inform the suspend sequencer that
    /// it is currently suspendable (or not suspendable). This is typically used to
    /// book-end calls to hardware that contains large amount of state that cannot
    /// be efficiently saved to battery-backed RAM.
    pub fn set_suspendable(&mut self, allow_suspend: bool) -> Result<(), xous::Error> {
        if allow_suspend {
            send_message(self.conn, Message::new_scalar(Opcode::SuspendAllow.to_usize().unwrap(), 0, 0, 0, 0))
                .map(|_| ())
        } else {
            send_message(self.conn, Message::new_scalar(Opcode::SuspendDeny.to_usize().unwrap(), 0, 0, 0, 0))
                .map(|_| ())
        }
    }

    /// Passing `true` causes the whole SOC including peripherals to receive a reset signal
    /// `false` causes only the CPU to reboot, while the peripherals retain state. Generally you want `true`.
    pub fn reboot(&self, whole_soc: bool) -> Result<(), xous::Error> {
        send_message(self.conn, Message::new_scalar(Opcode::RebootRequest.to_usize().unwrap(), 0, 0, 0, 0))
            .map(|_| ())?;

        if whole_soc {
            send_message(
                self.conn,
                Message::new_scalar(Opcode::RebootSocConfirm.to_usize().unwrap(), 0, 0, 0, 0),
            )
            .map(|_| ())
        } else {
            send_message(
                self.conn,
                Message::new_scalar(Opcode::RebootCpuConfirm.to_usize().unwrap(), 0, 0, 0, 0),
            )
            .map(|_| ())
        }
    }

    /// Pulls power from the SoC without attempting to save state
    pub fn immediate_poweroff(&self) -> Result<(), xous::Error> {
        send_message(self.conn, Message::new_scalar(Opcode::PowerOff.to_usize().unwrap(), 0, 0, 0, 0))
            .map(|_| ())
    }
}
fn drop_conn(sid: xous::SID) {
    let cid = xous::connect(sid).unwrap();
    xous::send_message(cid, Message::new_scalar(SuspendEventCallback::Drop.to_usize().unwrap(), 0, 0, 0, 0))
        .unwrap();
    unsafe {
        xous::disconnect(cid).unwrap();
    }
}
use core::sync::atomic::{AtomicU32, Ordering};
static REFCOUNT: AtomicU32 = AtomicU32::new(0);
impl Drop for Susres {
    fn drop(&mut self) {
        if let Some(sid) = self.suspend_cb_sid.take() {
            drop_conn(sid);
        }
        if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
            unsafe {
                xous::disconnect(self.conn).unwrap();
            }
        }
    }
}

fn suspend_cb_server(sid0: usize, sid1: usize, sid2: usize, sid3: usize) {
    let sid = xous::SID::from_u32(sid0 as u32, sid1 as u32, sid2 as u32, sid3 as u32);
    let mut print_once = false;
    loop {
        let msg = xous::receive_message(sid).unwrap();
        match FromPrimitive::from_usize(msg.body.id()) {
            Some(SuspendEventCallback::Event) => msg_scalar_unpack!(msg, cid, id, token, _, {
                // directly pass the scalar message onto the CID with the ID memorized in the original hook
                if !print_once {
                    // dump this only once so we have a PID->token map in the debug logs, but don't dump it
                    // every time as it slows down the suspend
                    log::info!("PID {} has s/r token {}", xous::current_pid().unwrap().get(), token); // <-- use this to debug s/r
                    print_once = true;
                }
                send_message(cid as u32, Message::new_scalar(id, token, 0, 0, 0)).unwrap();
            }),
            Some(SuspendEventCallback::Drop) => {
                break; // this exits the loop and kills the thread
            }
            None => (),
        }
    }
    xous::destroy_server(sid).unwrap();
}