1pub mod kernels;
6pub mod pager;
7pub mod proxy;
8pub mod suspend;
9
10use crate::pager::{Pager, run_pager};
11use anyhow::{Error, anyhow};
12use fidl::Peered;
13use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, ServerEnd};
14use fidl_fuchsia_component as fcomponent;
15use fidl_fuchsia_component_decl as fdecl;
16use fidl_fuchsia_component_runner as frunner;
17use fidl_fuchsia_io as fio;
18use fidl_fuchsia_starnix_container as fstarnix;
19use fidl_fuchsia_starnix_runner as fstarnixrunner;
20use fuchsia_async as fasync;
21use fuchsia_component::client as fclient;
22use fuchsia_inspect::{HistogramProperty, NumericProperty};
23use futures::TryStreamExt;
24use kernels::Kernels;
25use log::warn;
26use proxy::ChannelProxy;
27use rand::RngExt as _;
28use starnix_sync::{LockDepMutex, StarnixKernelWakeLeaseLock, WakeSourcesLock};
29use std::future::Future;
30use std::sync::Arc;
31use suspend::{
32 ASLEEP_SIGNAL, AWAKE_SIGNAL, SuspendContext, WakeSource, WakeSources, suspend_container,
33};
34
35const KERNEL_COLLECTION: &str = "kernels";
37
38const CONTAINER_RUNNER_PROTOCOL: &str = "fuchsia.starnix.container.Runner";
45
46#[allow(dead_code)]
47pub struct StarnixKernel {
48 name: String,
50
51 exposed_dir: fio::DirectoryProxy,
55
56 component_instance: zx::Event,
58
59 job: Arc<zx::Job>,
61
62 wake_lease: LockDepMutex<Option<zx::EventPair>, StarnixKernelWakeLeaseLock>,
64}
65
66impl StarnixKernel {
67 pub async fn create(
73 realm: fcomponent::RealmProxy,
74 kernel_url: &str,
75 start_info: frunner::ComponentStartInfo,
76 controller: ServerEnd<frunner::ComponentControllerMarker>,
77 ) -> Result<(Self, impl Future<Output = ()>), Error> {
78 let kernel_name = generate_kernel_name(&start_info)?;
79 let component_instance = start_info
80 .component_instance
81 .as_ref()
82 .ok_or_else(|| {
83 anyhow::anyhow!("expected to find component_instance in ComponentStartInfo")
84 })?
85 .duplicate_handle(zx::Rights::SAME_RIGHTS)?;
86
87 let (controller_proxy, controller_server_end) = fidl::endpoints::create_proxy();
89 realm
90 .create_child(
91 &fdecl::CollectionRef { name: KERNEL_COLLECTION.into() },
92 &fdecl::Child {
93 name: Some(kernel_name.clone()),
94 url: Some(kernel_url.to_string()),
95 startup: Some(fdecl::StartupMode::Lazy),
96 ..Default::default()
97 },
98 fcomponent::CreateChildArgs {
99 controller: Some(controller_server_end),
100 ..Default::default()
101 },
102 )
103 .await?
104 .map_err(|e| anyhow::anyhow!("failed to create kernel: {:?}", e))?;
105
106 let exposed_dir = open_exposed_directory(&realm, &kernel_name, KERNEL_COLLECTION).await?;
107 let container_runner = fclient::connect_to_named_protocol_at_dir_root::<
108 frunner::ComponentRunnerMarker,
109 >(&exposed_dir, CONTAINER_RUNNER_PROTOCOL)?;
110
111 container_runner.start(start_info, controller)?;
113
114 let container_controller =
116 fclient::connect_to_protocol_at_dir_root::<fstarnix::ControllerMarker>(&exposed_dir)?;
117 let fstarnix::ControllerGetJobHandleResponse { job, .. } =
118 container_controller.get_job_handle().await?;
119 let Some(job) = job else {
120 anyhow::bail!("expected to find job in ControllerGetJobHandleResponse");
121 };
122
123 let kernel = Self {
124 name: kernel_name,
125 exposed_dir,
126 component_instance,
127 job: Arc::new(job),
128 wake_lease: Default::default(),
129 };
130 let on_stop = async move {
131 _ = controller_proxy.into_channel().unwrap().on_closed().await;
132 };
133 Ok((kernel, on_stop))
134 }
135
136 pub fn component_instance(&self) -> &zx::Event {
138 &self.component_instance
139 }
140
141 pub fn job(&self) -> &Arc<zx::Job> {
143 &self.job
144 }
145
146 pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
148 fclient::connect_to_protocol_at_dir_root::<P>(&self.exposed_dir)
149 }
150}
151
152fn generate_kernel_name(_start_info: &frunner::ComponentStartInfo) -> Result<String, Error> {
158 let random_id: String =
159 rand::rng().sample_iter(&rand::distr::Alphanumeric).take(7).map(char::from).collect();
160 Ok(random_id)
161}
162
163async fn open_exposed_directory(
164 realm: &fcomponent::RealmProxy,
165 child_name: &str,
166 collection_name: &str,
167) -> Result<fio::DirectoryProxy, Error> {
168 let (directory_proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
169 realm
170 .open_exposed_dir(
171 &fdecl::ChildRef { name: child_name.into(), collection: Some(collection_name.into()) },
172 server_end,
173 )
174 .await?
175 .map_err(|e| {
176 anyhow!(
177 "failed to bind to child {} in collection {:?}: {:?}",
178 child_name,
179 collection_name,
180 e
181 )
182 })?;
183 Ok(directory_proxy)
184}
185
186pub struct SuspendMessage {
187 pub payload: fstarnixrunner::ManagerSuspendContainerRequest,
188 pub responder: fstarnixrunner::ManagerSuspendContainerResponder,
189}
190
191pub async fn run_suspend_worker(
192 receiver: async_channel::Receiver<SuspendMessage>,
193 suspend_context: Arc<SuspendContext>,
194 kernels: &Kernels,
195) {
196 while let Ok(msg) = receiver.recv().await {
197 suspend_context.suspend_attempts_count.add(1);
198 match suspend_container(msg.payload, &suspend_context, &kernels).await {
199 Ok(response) => {
200 if let Err(e) = match response {
201 Ok(o) => {
202 suspend_context.suspend_successes_count.add(1);
203 if let Some(suspend_time) = o.suspend_time {
204 suspend_context.suspend_duration_histogram.insert(suspend_time as u64);
205 }
206 msg.responder.send(Ok(&o))
207 }
208 Err(e) => {
209 suspend_context.suspend_failures_count.add(1);
210 msg.responder.send(Err(e))
211 }
212 } {
213 warn!("error replying to suspend request: {e}");
214 }
215 }
216 Err(e) => {
217 suspend_context.suspend_failures_count.add(1);
218 warn!("error executing suspend: {e}");
219 let _ = msg.responder.send(Err(fstarnixrunner::SuspendError::SuspendFailure));
220 }
221 }
222 }
223}
224
225pub async fn serve_starnix_manager(
226 mut stream: fstarnixrunner::ManagerRequestStream,
227 suspend_context: Arc<SuspendContext>,
228 sender: &async_channel::Sender<(ChannelProxy, Arc<LockDepMutex<WakeSources, WakeSourcesLock>>)>,
229 pager: Arc<Pager>,
230 suspend_sender: &async_channel::Sender<SuspendMessage>,
231) -> Result<(), Error> {
232 while let Some(event) = stream.try_next().await? {
233 match event {
234 fstarnixrunner::ManagerRequest::SuspendContainer { payload, responder, .. } => {
235 if let Err(e) = suspend_sender.try_send(SuspendMessage { payload, responder }) {
236 warn!("failed to send suspend request to worker: {e}");
237 }
238 }
239 fstarnixrunner::ManagerRequest::ProxyWakeChannel { payload, .. } => {
240 let fstarnixrunner::ManagerProxyWakeChannelRequest {
241 container_job: Some(_container_job),
243 remote_channel: Some(remote_channel),
244 container_channel: Some(container_channel),
245 name: Some(name),
246 counter: Some(message_counter),
247 ..
248 } = payload
249 else {
250 continue;
251 };
252
253 suspend_context.wake_sources.lock().insert(
254 message_counter.koid().unwrap(),
255 WakeSource::from_counter(
256 message_counter.duplicate_handle(zx::Rights::SAME_RIGHTS)?,
257 name.clone(),
258 ),
259 );
260
261 let proxy =
262 ChannelProxy { container_channel, remote_channel, message_counter, name };
263
264 sender.try_send((proxy, suspend_context.wake_sources.clone())).unwrap();
265 }
266 fstarnixrunner::ManagerRequest::RegisterWakeWatcher { payload, responder } => {
267 if let Some(watcher) = payload.watcher {
268 let (clear_mask, set_mask) = (ASLEEP_SIGNAL, AWAKE_SIGNAL);
269 watcher.signal_peer(clear_mask, set_mask)?;
270
271 suspend_context.wake_watchers.lock().push(watcher);
272 }
273 if let Err(e) = responder.send() {
274 warn!("error registering power watcher: {e}");
275 }
276 }
277 fstarnixrunner::ManagerRequest::AddWakeSource { payload, .. } => {
278 let fstarnixrunner::ManagerAddWakeSourceRequest {
279 container_job: Some(_container_job),
281 name: Some(name),
282 handle: Some(handle),
283 signals: Some(signals),
284 ..
285 } = payload
286 else {
287 continue;
288 };
289 suspend_context.wake_sources.lock().insert(
290 handle.koid().unwrap(),
291 WakeSource::from_handle(handle, name, zx::Signals::from_bits_truncate(signals)),
292 );
293 }
294 fstarnixrunner::ManagerRequest::RemoveWakeSource { payload, .. } => {
295 let fstarnixrunner::ManagerRemoveWakeSourceRequest {
296 container_job: Some(_container_job),
298 handle: Some(handle),
299 ..
300 } = payload
301 else {
302 continue;
303 };
304
305 suspend_context.wake_sources.lock().remove(&handle.koid().unwrap());
306 }
307 fstarnixrunner::ManagerRequest::CreatePager { payload, .. } => {
308 fasync::Task::spawn(run_pager(payload, pager.clone())).detach();
309 }
310 _ => {}
311 }
312 }
313 Ok(())
314}