xous_api_susres/
lib.rs

1#![cfg_attr(target_os = "none", no_std)]
2#![cfg_attr(not(target_os = "none"), allow(dead_code))]
3#![cfg_attr(not(target_os = "none"), allow(unused_imports))]
4#![cfg_attr(not(target_os = "none"), allow(unused_variables))]
5
6pub mod api;
7pub use api::*;
8use num_traits::{FromPrimitive, ToPrimitive};
9use xous::{CID, Message, msg_scalar_unpack, send_message};
10use xous_ipc::Buffer;
11
12#[derive(Debug)]
13pub struct Susres {
14    conn: CID,
15    suspend_cb_sid: Option<xous::SID>,
16}
17impl Susres {
18    #[cfg(any(feature = "precursor", feature = "renode"))]
19    /// When created, the `susres` object can be configured with a `SuspendOrder` to enforce
20    /// sequencing rules in shutdown. It also requires arguments to define a callback which is
21    /// pinged when a suspend event arrives. The callback takes the form of a `CID, discriminant`
22    /// pair, where the CID is the local connection ID to the caller (in other words, a self-connection
23    /// to the caller), and the discriminant is the number placed into the Xous message's `body.id()`
24    /// field. This is typically just the descriminant of the main loop's opcode enum for the
25    /// suspend-resume opcode.
26    pub fn new(
27        order: Option<SuspendOrder>,
28        xns: &xous_names::XousNames,
29        cb_discriminant: u32,
30        cid: CID,
31    ) -> Result<Self, xous::Error> {
32        REFCOUNT.fetch_add(1, Ordering::Relaxed);
33        let conn = xns.request_connection_blocking(api::SERVER_NAME_SUSRES).expect("Can't connect to SUSRES");
34
35        let sid = xous::create_server().unwrap();
36        let sid_tuple = sid.to_u32();
37        xous::create_thread_4(
38            suspend_cb_server,
39            sid_tuple.0 as usize,
40            sid_tuple.1 as usize,
41            sid_tuple.2 as usize,
42            sid_tuple.3 as usize,
43        )
44        .unwrap();
45        let hookdata = ScalarHook {
46            sid: sid_tuple,
47            id: cb_discriminant,
48            cid,
49            order: order.unwrap_or(SuspendOrder::Normal),
50        };
51        log::debug!("hooking {:?}", hookdata);
52        let buf = Buffer::into_buf(hookdata).or(Err(xous::Error::InternalError))?;
53        buf.lend(conn, Opcode::SuspendEventSubscribe.to_u32().unwrap())?;
54
55        Ok(Susres { conn, suspend_cb_sid: Some(sid) })
56    }
57
58    // suspend/resume is not implemented in hosted mode, and will break if you try to do it.
59    // the main reason this was done is actually it seems hosted mode can't handle the level
60    // of concurrency introduced by suspend/resume, as its underlying IPC mechanisms are quite
61    // different and have a lot of overhead; it seems like the system goes into a form of deadlock
62    // during boot when all the hosted mode servers try to connect. This isn't an issue on real hardware.
63    #[cfg(not(target_os = "xous"))]
64    /// When created, the `susres` object can be configured with a `SuspendOrder` to enforce
65    /// sequencing rules in shutdown. It also requires arguments to define a callback which is
66    /// pinged when a suspend event arrives. The callback takes the form of a `CID, discriminant`
67    /// pair, where the CID is the local connection ID to the caller (in other words, a self-connection
68    /// to the caller), and the discriminant is the number placed into the Xous message's `body.id()`
69    /// field. This is typically just the descriminant of the main loop's opcode enum for the
70    /// suspend-resume opcode.
71    pub fn new(
72        _ordering: Option<SuspendOrder>,
73        xns: &xous_names::XousNames,
74        cb_discriminant: u32,
75        cid: CID,
76    ) -> Result<Self, xous::Error> {
77        REFCOUNT.fetch_add(1, Ordering::Relaxed);
78        Ok(Susres { conn: 0, suspend_cb_sid: None })
79    }
80
81    /// Creates a connection to the `susres` server, but without a callback. This is useful
82    /// for services that are suspend-insensitive, but need to manipulate the state of
83    /// the machine (such as initiating a suspend).
84    pub fn new_without_hook(xns: &xous_names::XousNames) -> Result<Self, xous::Error> {
85        REFCOUNT.fetch_add(1, Ordering::Relaxed);
86        let conn = xns.request_connection_blocking(api::SERVER_NAME_SUSRES)?;
87        Ok(Susres { conn, suspend_cb_sid: None })
88    }
89
90    /// This call initiates a suspend. It will sequence through the suspend events; and
91    /// if any services are unable to suspend within a defined time-out window, the call
92    /// will fail with a `xous::Error::Timeout`.
93    ///
94    /// NB: services such as the SHA engine and
95    /// the Curve25519 engine can deny a suspend because they have large internal state
96    /// that can't be saved to battery-backed RAM.
97    pub fn initiate_suspend(&self) -> Result<(), xous::Error> {
98        log::trace!("suspend initiated");
99        match send_message(
100            self.conn,
101            Message::new_blocking_scalar(Opcode::SuspendRequest.to_usize().unwrap(), 0, 0, 0, 0),
102        ) {
103            Ok(xous::Result::Scalar1(result)) => {
104                if result == 1 {
105                    Ok(())
106                } else {
107                    // indicate that we couldn't initiate the suspend
108                    Err(xous::Error::Timeout)
109                }
110            }
111            _ => Err(xous::Error::InternalError),
112        }
113    }
114
115    /// This call is used by services that are suspend-sensitive. They are used to
116    /// acknowledge the callback from the suspend sequencer; calling this function
117    /// basically tells the sequencer "I'm ready to suspend immediately". Likewise,
118    /// this call blocks until the system resumes.
119    ///
120    /// Note that from the perspective of the caller, this call magically appears
121    /// like it returns almost immediately, because, the time spent in suspend does
122    /// not increment the ticktimer or system time. The only evidence of a suspend
123    /// would be a difference in the current RTC timestamp.
124    pub fn suspend_until_resume(&mut self, token: usize) -> Result<bool, xous::Error> {
125        if self.suspend_cb_sid.is_none() {
126            // this happens if you created without a hook
127            return Err(xous::Error::UseBeforeInit);
128        }
129        log::debug!("token {} pid {} suspending", token, xous::process::id()); // <-- use this to debug s/r
130        xous::yield_slice();
131        // first tell the susres server that we're ready to suspend
132        send_message(
133            self.conn,
134            Message::new_scalar(Opcode::SuspendReady.to_usize().unwrap(), token, 0, 0, 0),
135        )
136        .map(|_| ())?;
137        log::trace!("blocking until suspend");
138
139        // sometime between here and when this next message unblocks, the power went out...
140
141        // now block until we've resumed
142        send_message(
143            self.conn,
144            Message::new_blocking_scalar(Opcode::SuspendingNow.to_usize().unwrap(), 0, 0, 0, 0),
145        )
146        .map(|_| ())?;
147
148        let response = send_message(
149            self.conn,
150            Message::new_blocking_scalar(Opcode::WasSuspendClean.to_usize().unwrap(), token, 0, 0, 0),
151        )
152        .expect("couldn't query if my suspend was successful");
153        if let xous::Result::Scalar1(result) = response {
154            if result != 0 {
155                log::debug!("resume pid {} clean", xous::process::id()); // <-- use this to debug s/r
156                Ok(true)
157            } else {
158                log::debug!("resume pid {} dirty", xous::process::id()); // <-- use this to debug s/r
159                Ok(false)
160            }
161        } else {
162            Err(xous::Error::InternalError)
163        }
164    }
165
166    /// This is a call that a service can make to inform the suspend sequencer that
167    /// it is currently suspendable (or not suspendable). This is typically used to
168    /// book-end calls to hardware that contains large amount of state that cannot
169    /// be efficiently saved to battery-backed RAM.
170    pub fn set_suspendable(&mut self, allow_suspend: bool) -> Result<(), xous::Error> {
171        if allow_suspend {
172            send_message(self.conn, Message::new_scalar(Opcode::SuspendAllow.to_usize().unwrap(), 0, 0, 0, 0))
173                .map(|_| ())
174        } else {
175            send_message(self.conn, Message::new_scalar(Opcode::SuspendDeny.to_usize().unwrap(), 0, 0, 0, 0))
176                .map(|_| ())
177        }
178    }
179
180    /// Passing `true` causes the whole SOC including peripherals to receive a reset signal
181    /// `false` causes only the CPU to reboot, while the peripherals retain state. Generally you want `true`.
182    pub fn reboot(&self, whole_soc: bool) -> Result<(), xous::Error> {
183        send_message(self.conn, Message::new_scalar(Opcode::RebootRequest.to_usize().unwrap(), 0, 0, 0, 0))
184            .map(|_| ())?;
185
186        if whole_soc {
187            send_message(
188                self.conn,
189                Message::new_scalar(Opcode::RebootSocConfirm.to_usize().unwrap(), 0, 0, 0, 0),
190            )
191            .map(|_| ())
192        } else {
193            send_message(
194                self.conn,
195                Message::new_scalar(Opcode::RebootCpuConfirm.to_usize().unwrap(), 0, 0, 0, 0),
196            )
197            .map(|_| ())
198        }
199    }
200
201    /// Pulls power from the SoC without attempting to save state
202    pub fn immediate_poweroff(&self) -> Result<(), xous::Error> {
203        send_message(self.conn, Message::new_scalar(Opcode::PowerOff.to_usize().unwrap(), 0, 0, 0, 0))
204            .map(|_| ())
205    }
206}
207fn drop_conn(sid: xous::SID) {
208    let cid = xous::connect(sid).unwrap();
209    xous::send_message(cid, Message::new_scalar(SuspendEventCallback::Drop.to_usize().unwrap(), 0, 0, 0, 0))
210        .unwrap();
211    unsafe {
212        xous::disconnect(cid).unwrap();
213    }
214}
215use core::sync::atomic::{AtomicU32, Ordering};
216static REFCOUNT: AtomicU32 = AtomicU32::new(0);
217impl Drop for Susres {
218    fn drop(&mut self) {
219        if let Some(sid) = self.suspend_cb_sid.take() {
220            drop_conn(sid);
221        }
222        if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
223            unsafe {
224                xous::disconnect(self.conn).unwrap();
225            }
226        }
227    }
228}
229
230fn suspend_cb_server(sid0: usize, sid1: usize, sid2: usize, sid3: usize) {
231    let sid = xous::SID::from_u32(sid0 as u32, sid1 as u32, sid2 as u32, sid3 as u32);
232    let mut print_once = false;
233    loop {
234        let msg = xous::receive_message(sid).unwrap();
235        match FromPrimitive::from_usize(msg.body.id()) {
236            Some(SuspendEventCallback::Event) => msg_scalar_unpack!(msg, cid, id, token, _, {
237                // directly pass the scalar message onto the CID with the ID memorized in the original hook
238                if !print_once {
239                    // dump this only once so we have a PID->token map in the debug logs, but don't dump it
240                    // every time as it slows down the suspend
241                    log::info!("PID {} has s/r token {}", xous::current_pid().unwrap().get(), token); // <-- use this to debug s/r
242                    print_once = true;
243                }
244                send_message(cid as u32, Message::new_scalar(id, token, 0, 0, 0)).unwrap();
245            }),
246            Some(SuspendEventCallback::Drop) => {
247                break; // this exits the loop and kills the thread
248            }
249            None => (),
250        }
251    }
252    xous::destroy_server(sid).unwrap();
253}