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, FileOpsCore, LockDepMutex, LockEqualOrBefore, Locked, Unlocked};
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(
31    locked: &mut Locked<Unlocked>,
32    kernel: &Kernel,
33    cpu_boost_duration: Option<zx::MonotonicDuration>,
34) {
35    let (booted_sender, booted_receiver) = ThreadLockupDetector::tracked_channel::<bool>();
36    let node = fuchsia_inspect::component::inspector().root().create_child("boot");
37    let device = BootedDevice::new(kernel, booted_sender, node, cpu_boost_duration)
38        .expect("must be able to initialize booted device");
39    device.clone().register(locked, kernel);
40    device.start_relay(kernel, booted_receiver);
41}
42
43#[derive(Clone)]
44struct BootedDevice {
45    inner: Arc<Inner>,
46}
47
48const INSPECT_KEY: &str = "boot_timestamp";
49
50impl BootedDevice {
51    pub fn new(
52        kernel: &Kernel,
53        booted_sender: Sender<bool>,
54        inspect_node: fuchsia_inspect::Node,
55        cpu_boost_duration: Option<zx::MonotonicDuration>,
56    ) -> Result<Self, anyhow::Error> {
57        let boot_timestamp = inspect_node.create_uint(INSPECT_KEY, 0);
58
59        if let Some(duration) = cpu_boost_duration {
60            match kernel.connect_to_protocol_at_container_svc::<BoostMarker>() {
61                Ok(client_end) => {
62                    log_info!("Enabling boot-time CPU boost");
63                    let booster = client_end.into_proxy();
64                    kernel.kthreads.spawn_future(
65                        move || async move {
66                            let token = match booster.boost().await {
67                                Ok(Ok(token)) => token,
68                                e => {
69                                    log_warn!(e:?; "Failed to enable boot-time CPU boost");
70                                    return;
71                                }
72                            };
73                            fuchsia_async::Timer::new(zx::MonotonicInstant::after(duration)).await;
74                            log_info!("Disabling boot-time CPU boost");
75                            drop(token);
76                        },
77                        "boot_cpu_boost",
78                    );
79                }
80                Err(e) => {
81                    log_warn!(e:?; "Failed to connect to cpu boost protocol");
82                }
83            }
84        }
85
86        Ok(Self {
87            inner: Arc::new(Inner {
88                file: File::new(booted_sender),
89                _inspect_node: inspect_node,
90                boot_timestamp,
91            }),
92        })
93    }
94
95    pub fn register<L>(self, locked: &mut Locked<L>, kernel: &Kernel)
96    where
97        L: LockEqualOrBefore<FileOpsCore>,
98    {
99        let registry = &kernel.device_registry;
100        registry
101            .register_dyn_device(
102                locked,
103                kernel,
104                "booted".into(),
105                registry.objects.starnix_class(),
106                self,
107            )
108            .expect("can register booted device");
109    }
110
111    pub fn start_relay(&self, kernel: &Kernel, booted_receiver: LockupDetectorReceiver<bool>) {
112        let this = self.inner.clone();
113        let closure = move |_lock_context: &mut Locked<Unlocked>, _current_task: &CurrentTask| {
114            let mut prev_booted = false;
115            while let Ok(booted) = booted_receiver.recv() {
116                if booted && !prev_booted {
117                    match this.notify_boot_completed() {
118                        Ok(()) => log_info!("Notified system boot completed"),
119                        Err(e) => log_error!(e:?; "Failed to notify system boot completed"),
120                    }
121                }
122                prev_booted = booted;
123            }
124            log_error!("booted relay was terminated unexpectedly.");
125        };
126        let req = SpawnRequestBuilder::new()
127            .with_debug_name("boot-notifier-relay")
128            .with_sync_closure(closure)
129            .build();
130
131        kernel.kthreads.spawner().spawn_from_request(req);
132    }
133}
134
135struct Inner {
136    file: Arc<File>,
137    _inspect_node: fuchsia_inspect::Node,
138    boot_timestamp: fuchsia_inspect::UintProperty,
139}
140
141impl Inner {
142    fn notify_boot_completed(&self) -> Result<(), anyhow::Error> {
143        log_info!("Boot has been marked completed");
144        let client =
145            fuchsia_component::client::connect_to_protocol_sync::<fsys::BootControllerMarker>()
146                .context("connecting to BootController")?;
147        client.notify(zx::MonotonicInstant::INFINITE).context("calling BootController/Notify")?;
148        let ts = zx::BootInstant::get().into_nanos() as u64;
149        let _ = self.boot_timestamp.set(ts);
150
151        Ok(())
152    }
153}
154
155struct File {
156    booted: LockDepMutex<bool, BootedLock>,
157    sender: Sender<bool>,
158}
159
160impl File {
161    fn new(sender: Sender<bool>) -> Arc<Self> {
162        Arc::new(Self { booted: false.into(), sender })
163    }
164}
165
166/// `TouchPowerPolicyFile` doesn't implement the `close` method.
167impl CloseFreeSafe for File {}
168impl FileOps for File {
169    fileops_impl_nonseekable!();
170    fileops_impl_noop_sync!();
171
172    fn read(
173        &self,
174        _locked: &mut Locked<FileOpsCore>,
175        _file: &FileObject,
176        _current_task: &CurrentTask,
177        offset: usize,
178        data: &mut dyn OutputBuffer,
179    ) -> Result<usize, Errno> {
180        debug_assert!(offset == 0);
181        let booted = self.booted.lock().to_owned();
182        data.write_all(booted.as_bytes())
183    }
184
185    fn write(
186        &self,
187        _locked: &mut Locked<FileOpsCore>,
188        _file: &FileObject,
189        _current_task: &CurrentTask,
190        _offset: usize,
191        data: &mut dyn InputBuffer,
192    ) -> Result<usize, Errno> {
193        let content = data.read_all()?;
194        let booted = match &*content {
195            b"0" | b"0\n" => false,
196            b"1" | b"1\n" => true,
197            _ => {
198                log_error!("Invalid booted value - must be 0 or 1");
199                return error!(EINVAL);
200            }
201        };
202        *self.booted.lock() = booted;
203        if let Err(e) = self.sender.send(booted) {
204            log_error!("unable to send recent booted state to device relay: {:?}", e);
205        }
206        Ok(content.len())
207    }
208}
209
210impl DeviceOps for BootedDevice {
211    fn open(
212        &self,
213        _locked: &mut Locked<FileOpsCore>,
214        _current_task: &CurrentTask,
215        _devt: DeviceId,
216        _node: &NamespaceNode,
217        _flags: OpenFlags,
218    ) -> Result<Box<dyn FileOps>, Errno> {
219        let file = self.inner.file.clone();
220        Ok(Box::new(file))
221    }
222}