kernel_manager/
suspend.rs1use crate::Kernels;
6use anyhow::Error;
7use fidl::{HandleBased, Peered};
8use fidl_fuchsia_starnix_runner as fstarnixrunner;
9use fuchsia_sync::Mutex;
10use log::warn;
11use std::sync::Arc;
12use zx::Task;
13
14pub const AWAKE_SIGNAL: zx::Signals = zx::Signals::USER_0;
16
17pub const ASLEEP_SIGNAL: zx::Signals = zx::Signals::USER_1;
19
20pub struct WakeSource {
21 handle: zx::NullableHandle,
22 name: String,
23 signals: zx::Signals,
24}
25
26impl WakeSource {
27 pub fn from_counter(counter: zx::Counter, name: String) -> Self {
28 Self { handle: counter.into_handle(), name, signals: zx::Signals::COUNTER_POSITIVE }
29 }
30
31 pub fn from_handle(handle: zx::NullableHandle, name: String, signals: zx::Signals) -> Self {
32 Self { handle, name, signals }
33 }
34
35 fn as_wait_item(&self) -> zx::WaitItem<'_> {
36 zx::WaitItem {
37 handle: self.handle.as_handle_ref(),
38 waitfor: self.signals,
39 pending: zx::Signals::empty(),
40 }
41 }
42}
43
44pub type WakeSources = std::collections::HashMap<zx::Koid, WakeSource>;
45
46#[derive(Default)]
47pub struct SuspendContext {
48 pub wake_sources: Arc<Mutex<WakeSources>>,
49 pub wake_watchers: Arc<Mutex<Vec<zx::EventPair>>>,
50}
51
52pub async fn suspend_container(
54 payload: fstarnixrunner::ManagerSuspendContainerRequest,
55 suspend_context: &Arc<SuspendContext>,
56 kernels: &Kernels,
57) -> Result<
58 Result<fstarnixrunner::ManagerSuspendContainerResponse, fstarnixrunner::SuspendError>,
59 Error,
60> {
61 fuchsia_trace::duration!("power", "starnix-runner:suspending-container");
62 let Some(container_job) = payload.container_job else {
63 warn!(
64 "error suspending container: could not find container job {:?}",
65 payload.container_job
66 );
67 return Ok(Err(fstarnixrunner::SuspendError::SuspendFailure));
68 };
69
70 log::info!("Suspending all container processes.");
73 let _suspend_handles = match suspend_job(&container_job).await {
74 Ok(handles) => handles,
75 Err(e) => {
76 warn!("error suspending container {:?}", e);
77 fuchsia_trace::instant!(
78 "power",
79 "starnix-runner:suspend-failed-actual",
80 fuchsia_trace::Scope::Process
81 );
82 return Ok(Err(fstarnixrunner::SuspendError::SuspendFailure));
83 }
84 };
85 log::info!("Finished suspending all container processes.");
86
87 let suspend_start = zx::BootInstant::get();
88 let resume_reason = {
89 if let Some(wake_locks) = payload.wake_locks {
91 match wake_locks
92 .wait_one(zx::Signals::EVENT_SIGNALED, zx::MonotonicInstant::ZERO)
93 .to_result()
94 {
95 Ok(_) => {
96 warn!("error suspending container: Linux wake locks exist");
99 fuchsia_trace::instant!(
100 "power",
101 "starnix-runner:suspend-failed-with-wake-locks",
102 fuchsia_trace::Scope::Process
103 );
104 return Ok(Err(fstarnixrunner::SuspendError::WakeLocksExist));
105 }
106 Err(_) => {}
107 };
108 }
109
110 {
111 log::info!("Notifying wake watchers of container suspend.");
112 let mut watchers = suspend_context.wake_watchers.lock();
113 let (clear_mask, set_mask) = (AWAKE_SIGNAL, ASLEEP_SIGNAL);
114 watchers.retain(|event| match event.signal_peer(clear_mask, set_mask) {
115 Err(zx::Status::PEER_CLOSED) => false,
116 Ok(()) => true,
117 Err(e) => {
118 log::warn!("Failed to signal wake watcher of suspension: {e:?}");
119 true
120 }
121 });
122 }
123 kernels.drop_wake_lease(&container_job)?;
124
125 let wake_sources = suspend_context.wake_sources.lock();
126 let mut wait_items: Vec<zx::WaitItem<'_>> =
127 wake_sources.iter().map(|(_, w)| w.as_wait_item()).collect();
128
129 let mono_before = zx::MonotonicInstant::get();
139 let boot_before = zx::BootInstant::get();
140
141 {
142 fuchsia_trace::duration!("power", "starnix-runner:waiting-on-container-wake");
143 if wait_items.len() > 0 {
144 log::info!("Waiting on container to receive incoming message on wake proxies");
145 match zx::object_wait_many(
146 &mut wait_items,
147 zx::MonotonicInstant::after(zx::Duration::from_seconds(9)),
148 ) {
149 Ok(_) => (),
150 Err(e) => {
151 warn!("error waiting for wake event {:?}", e);
152 }
153 };
154 }
155 }
156
157 let mono_after = zx::MonotonicInstant::get();
158 let boot_after = zx::BootInstant::get();
159 log::info!(
160 "Finished waiting on container wake proxies. Monotonic delta: {:?} ns, Boot delta: {:?} ns",
161 (mono_after - mono_before).into_nanos(),
162 (boot_after - boot_before).into_nanos()
163 );
164
165 let mut resume_reasons: Vec<String> = Vec::new();
166 for wait_item in &wait_items {
167 if (wait_item.pending & wait_item.waitfor) != zx::Signals::NONE {
168 let koid = wait_item.handle.koid().unwrap();
169 if let Some(event) = wake_sources.get(&koid) {
170 log::info!("Woke container from sleep for: {}", event.name,);
171 resume_reasons.push(event.name.clone());
172 }
173 }
174 }
175
176 let resume_reason =
177 if resume_reasons.is_empty() { None } else { Some(resume_reasons.join(",")) };
178 resume_reason
179 };
180
181 kernels.acquire_wake_lease(&container_job).await?;
182
183 log::info!("Notifying wake watchers of container wakeup.");
184 let mut watchers = suspend_context.wake_watchers.lock();
185 let (clear_mask, set_mask) = (ASLEEP_SIGNAL, AWAKE_SIGNAL);
186 watchers.retain(|event| match event.signal_peer(clear_mask, set_mask) {
187 Err(zx::Status::PEER_CLOSED) => false,
188 Ok(()) => true,
189 Err(e) => {
190 log::warn!("Failed to signal wake watcher of wakeup: {e:?}");
191 true
192 }
193 });
194
195 log::info!("Returning successfully from suspend container");
196 Ok(Ok(fstarnixrunner::ManagerSuspendContainerResponse {
197 suspend_time: Some((zx::BootInstant::get() - suspend_start).into_nanos()),
198 resume_reason,
199 ..Default::default()
200 }))
201}
202
203async fn suspend_job(kernel_job: &zx::Job) -> Result<Vec<zx::NullableHandle>, Error> {
210 let mut handles = std::collections::HashMap::<zx::Koid, zx::NullableHandle>::new();
211 loop {
212 let process_koids = kernel_job.processes().expect("failed to get processes");
213 let mut found_new_process = false;
214 let mut processes = vec![];
215
216 for process_koid in process_koids {
217 if handles.get(&process_koid).is_some() {
218 continue;
219 }
220
221 found_new_process = true;
222
223 if let Ok(process_handle) = kernel_job.get_child(&process_koid, zx::Rights::SAME_RIGHTS)
224 {
225 let process = zx::Process::from(process_handle);
226 match process.suspend() {
227 Ok(suspend_handle) => {
228 handles.insert(process_koid, suspend_handle);
229 }
230 Err(zx::Status::BAD_STATE) => {
231 continue;
233 }
234 Err(e) => {
235 log::warn!("Failed process suspension: {:?}", e);
236 return Err(e.into());
237 }
238 };
239 processes.push(process);
240 }
241 }
242
243 for process in processes {
244 let threads = process.threads().expect("failed to get threads");
245 for thread_koid in &threads {
246 fuchsia_trace::duration!("power", "starnix-runner:suspend_kernel", "thread_koid" => *thread_koid);
247 if let Ok(thread) = process.get_child(&thread_koid, zx::Rights::SAME_RIGHTS) {
248 match thread
249 .wait_one(
250 zx::Signals::THREAD_SUSPENDED,
251 zx::MonotonicInstant::after(zx::MonotonicDuration::INFINITE),
252 )
253 .to_result()
254 {
255 Err(e) => {
256 log::warn!("Error waiting for task suspension: {:?}", e);
257 return Err(e.into());
258 }
259 _ => {}
260 }
261 }
262 }
263 }
264
265 if !found_new_process {
266 break;
267 }
268 }
269
270 Ok(handles.into_values().collect())
271}