Skip to main content

starnix_modules_boot/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![recursion_limit = "512"]
6
7use anyhow::Context;
8use fidl_fuchsia_power_cpu_manager::BoostMarker;
9use fidl_fuchsia_sys2 as fsys;
10use fuchsia_inspect::Property;
11use starnix_core::device::DeviceOps;
12use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
13use starnix_core::task::{CurrentTask, Kernel, LockupDetectorReceiver, ThreadLockupDetector};
14use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
15use starnix_core::vfs::{
16    CloseFreeSafe, FileObject, FileOps, NamespaceNode, fileops_impl_nonseekable,
17    fileops_impl_noop_sync,
18};
19use starnix_logging::{log_error, log_info, log_warn};
20use starnix_sync::{BootedLock, LockDepMutex};
21use starnix_uapi::device_id::DeviceId;
22use starnix_uapi::error;
23use starnix_uapi::errors::Errno;
24use starnix_uapi::open_flags::OpenFlags;
25use std::sync::Arc;
26use std::sync::mpsc::Sender;
27use zerocopy::IntoBytes;
28
29/// Initializes the boot notifier device.
30pub fn booted_device_init(kernel: &Kernel, cpu_boost_duration: Option<zx::MonotonicDuration>) {
31    let (booted_sender, booted_receiver) = ThreadLockupDetector::tracked_channel::<bool>();
32    let node = fuchsia_inspect::component::inspector().root().create_child("boot");
33    let device = BootedDevice::new(kernel, booted_sender, node, cpu_boost_duration)
34        .expect("must be able to initialize booted device");
35    device.clone().register(kernel);
36    device.start_relay(kernel, booted_receiver);
37}
38
39#[derive(Clone)]
40struct BootedDevice {
41    inner: Arc<Inner>,
42}
43
44const INSPECT_KEY: &str = "boot_timestamp";
45
46impl BootedDevice {
47    pub fn new(
48        kernel: &Kernel,
49        booted_sender: Sender<bool>,
50        inspect_node: fuchsia_inspect::Node,
51        cpu_boost_duration: Option<zx::MonotonicDuration>,
52    ) -> Result<Self, anyhow::Error> {
53        let boot_timestamp = inspect_node.create_uint(INSPECT_KEY, 0);
54
55        if let Some(duration) = cpu_boost_duration {
56            match kernel.connect_to_protocol_at_container_svc::<BoostMarker>() {
57                Ok(client_end) => {
58                    log_info!("Enabling boot-time CPU boost");
59                    let booster = client_end.into_proxy();
60                    kernel.kthreads.spawn_future(
61                        move || async move {
62                            let token = match booster.boost().await {
63                                Ok(Ok(token)) => token,
64                                e => {
65                                    log_warn!(e:?; "Failed to enable boot-time CPU boost");
66                                    return;
67                                }
68                            };
69                            fuchsia_async::Timer::new(zx::MonotonicInstant::after(duration)).await;
70                            log_info!("Disabling boot-time CPU boost");
71                            drop(token);
72                        },
73                        "boot_cpu_boost",
74                    );
75                }
76                Err(e) => {
77                    log_warn!(e:?; "Failed to connect to cpu boost protocol");
78                }
79            }
80        }
81
82        Ok(Self {
83            inner: Arc::new(Inner {
84                file: File::new(booted_sender),
85                _inspect_node: inspect_node,
86                boot_timestamp,
87            }),
88        })
89    }
90
91    pub fn register(self, kernel: &Kernel) {
92        let registry = &kernel.device_registry;
93        registry
94            .register_dyn_device(kernel, "booted".into(), registry.objects.starnix_class(), self)
95            .expect("can register booted device");
96    }
97
98    pub fn start_relay(&self, kernel: &Kernel, booted_receiver: LockupDetectorReceiver<bool>) {
99        let this = self.inner.clone();
100        let closure = move |_current_task: &CurrentTask| {
101            let mut prev_booted = false;
102            while let Ok(booted) = booted_receiver.recv() {
103                if booted && !prev_booted {
104                    match this.notify_boot_completed() {
105                        Ok(()) => log_info!("Notified system boot completed"),
106                        Err(e) => log_error!(e:?; "Failed to notify system boot completed"),
107                    }
108                }
109                prev_booted = booted;
110            }
111            log_error!("booted relay was terminated unexpectedly.");
112        };
113        let req = SpawnRequestBuilder::new()
114            .with_debug_name("boot-notifier-relay")
115            .with_sync_closure(closure)
116            .build();
117
118        kernel.kthreads.spawner().spawn_from_request(req);
119    }
120}
121
122struct Inner {
123    file: Arc<File>,
124    _inspect_node: fuchsia_inspect::Node,
125    boot_timestamp: fuchsia_inspect::UintProperty,
126}
127
128impl Inner {
129    fn notify_boot_completed(&self) -> Result<(), anyhow::Error> {
130        log_info!("Boot has been marked completed");
131        let client =
132            fuchsia_component::client::connect_to_protocol_sync::<fsys::BootControllerMarker>()
133                .context("connecting to BootController")?;
134        client.notify(zx::MonotonicInstant::INFINITE).context("calling BootController/Notify")?;
135        let ts = zx::BootInstant::get().into_nanos() as u64;
136        let _ = self.boot_timestamp.set(ts);
137
138        Ok(())
139    }
140}
141
142struct File {
143    booted: LockDepMutex<bool, BootedLock>,
144    sender: Sender<bool>,
145}
146
147impl File {
148    fn new(sender: Sender<bool>) -> Arc<Self> {
149        Arc::new(Self { booted: false.into(), sender })
150    }
151}
152
153/// `TouchPowerPolicyFile` doesn't implement the `close` method.
154impl CloseFreeSafe for File {}
155impl FileOps for File {
156    fileops_impl_nonseekable!();
157    fileops_impl_noop_sync!();
158
159    fn read(
160        &self,
161        _file: &FileObject,
162        _current_task: &CurrentTask,
163        offset: usize,
164        data: &mut dyn OutputBuffer,
165    ) -> Result<usize, Errno> {
166        debug_assert!(offset == 0);
167        let booted = self.booted.lock().to_owned();
168        data.write_all(booted.as_bytes())
169    }
170
171    fn write(
172        &self,
173        _file: &FileObject,
174        _current_task: &CurrentTask,
175        _offset: usize,
176        data: &mut dyn InputBuffer,
177    ) -> Result<usize, Errno> {
178        let content = data.read_all()?;
179        let booted = match &*content {
180            b"0" | b"0\n" => false,
181            b"1" | b"1\n" => true,
182            _ => {
183                log_error!("Invalid booted value - must be 0 or 1");
184                return error!(EINVAL);
185            }
186        };
187        *self.booted.lock() = booted;
188        if let Err(e) = self.sender.send(booted) {
189            log_error!("unable to send recent booted state to device relay: {:?}", e);
190        }
191        Ok(content.len())
192    }
193}
194
195impl DeviceOps for BootedDevice {
196    fn open(
197        &self,
198        _current_task: &CurrentTask,
199        _devt: DeviceId,
200        _node: &NamespaceNode,
201        _flags: OpenFlags,
202    ) -> Result<Box<dyn FileOps>, Errno> {
203        let file = self.inner.file.clone();
204        Ok(Box::new(file))
205    }
206}