Skip to main content

starnix_modules_wakeup_test/
device.rs

1// Copyright 2026 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
5use crate::input::{create_media_buttons_proxy, schedule_wakeup_power_button};
6use crate::ioctl::{CommandCode, WakeupMethod, WakeupTestType, WakeupTimerInfo};
7use crate::tracing;
8use anyhow::{Result, anyhow};
9use starnix_core::device::DeviceOps;
10use starnix_core::mm::MemoryAccessorExt;
11use starnix_core::perf::TraceEventQueue;
12use starnix_core::task::{CurrentTask, Kernel};
13use starnix_core::vfs::{CloseFreeSafe, FileObject, FileOps, NamespaceNode, default_ioctl};
14use starnix_core::{fileops_impl_dataless, fileops_impl_noop_sync, fileops_impl_seekless};
15use starnix_logging::{log_error, log_info, log_warn};
16use starnix_sync::{FileOpsCore, Locked, Unlocked};
17use starnix_syscalls::{SUCCESS, SyscallResult};
18use starnix_uapi::errors::Errno;
19use starnix_uapi::open_flags::OpenFlags;
20use starnix_uapi::user_address::UserRef;
21use starnix_uapi::{device_type, error};
22use std::sync::{Arc, Weak};
23use zx;
24
25#[derive(Clone)]
26pub struct WakeupTestDevice {
27    commands: Commands,
28}
29
30impl CloseFreeSafe for WakeupTestDevice {}
31
32impl WakeupTestDevice {
33    pub fn new(current_task: &CurrentTask) -> Self {
34        Self {
35            commands: Commands {
36                tid: current_task.get_tid(),
37                kernel: Arc::downgrade(current_task.kernel()),
38            },
39        }
40    }
41}
42
43impl DeviceOps for WakeupTestDevice {
44    fn open(
45        &self,
46        _locked: &mut Locked<FileOpsCore>,
47        current_task: &CurrentTask,
48        _id: device_type::DeviceType,
49        _node: &NamespaceNode,
50        _flags: OpenFlags,
51    ) -> Result<Box<dyn FileOps>, Errno> {
52        Ok(Box::new(WakeupTestDevice::new(current_task)))
53    }
54}
55
56#[derive(Clone)]
57struct Commands {
58    kernel: Weak<Kernel>,
59    tid: i32,
60}
61
62impl Commands {
63    fn schedule_wakeup(&self, time_ns: i64) -> Result<()> {
64        log_info!("WakeupTestDevice::schedule_wakeup creating async task for time_ns: {time_ns}");
65        let kernel = self.kernel.upgrade().expect("kernel should exist");
66
67        kernel.kthreads.spawn_future(
68            move || async move {
69                let media_button_proxy = match create_media_buttons_proxy().await {
70                    Ok(proxy) => proxy,
71                    Err(e) => {
72                        log_error!("Failed to create media buttons proxy: {:?}", e);
73                        return;
74                    }
75                };
76                schedule_wakeup_power_button(
77                    &media_button_proxy,
78                    zx::Duration::from_nanos(time_ns),
79                )
80                .await;
81                // Keep the device around until the timer goes off.
82                // Since this is called from via ioctl, it is not feasible to return an EventPair or
83                // some other handle that could be used to make the lifetime more event driven.
84                // This may be something to revisit if the test is flaky because of the media_button_proxy
85                // being dropped before the timer is goes off and the input sent.
86
87                // Sleep for 5 seconds plus the timer duration to ensure the event is sent before the proxy is dropped.
88                let deadline = fuchsia_async::MonotonicInstant::after(
89                    zx::Duration::from_seconds(5) + zx::Duration::from_nanos(time_ns),
90                );
91                fuchsia_async::Timer::new(deadline).await;
92                log_info!("media_button proxy dropped.")
93            },
94            "wakeup_test",
95        );
96        Ok(())
97    }
98
99    fn run_wakeup_set_timers(&self, timer_info: WakeupTimerInfo) -> Result<()> {
100        let method = WakeupMethod::from(timer_info.method);
101
102        // TODO(https://fxbug.dev/458389823): Use other input events to wakeup the system.
103        match method {
104            WakeupMethod::PowerButton => (),
105            _ => {
106                return Err(anyhow!(
107                    "Only PowerButton wakeup method is currently supported: b/458389823"
108                ));
109            }
110        };
111
112        let test_type = WakeupTestType::from(timer_info.test_type);
113        log_info!(
114            "WakeupTestDevice::WakeupSetTimers test_type: {test_type:?} Setting {} timers for {:?}, interval {} ns, starting {} ns",
115            timer_info.num_events,
116            method,
117            timer_info.interval,
118            timer_info.offset
119        );
120        tracing::trace_wakeup_test_type(self.get_trace_event_queue(), self.tid, test_type);
121        for index in 0..timer_info.num_events {
122            let time = (timer_info.interval * (index as i64)) + timer_info.offset;
123            log_info!("WakeupTestDevice::set_timer i: {index} for {time}");
124            self.schedule_wakeup(time)?;
125        }
126        Ok(())
127    }
128
129    /// Gets the trace event queue if available to emit trace events.
130    fn get_trace_event_queue(&self) -> Option<Arc<TraceEventQueue>> {
131        if let Some(k) = self.kernel.upgrade() {
132            let event_queue = TraceEventQueue::from(&k);
133            if event_queue.is_enabled() {
134                return Some(event_queue);
135            } else {
136                log_warn!("Trace event queue is not enabled");
137            }
138        }
139        None
140    }
141}
142
143impl FileOps for WakeupTestDevice {
144    fileops_impl_seekless!();
145    fileops_impl_dataless!();
146    fileops_impl_noop_sync!();
147
148    fn ioctl(
149        &self,
150        locked: &mut Locked<Unlocked>,
151        file: &FileObject,
152        current_task: &CurrentTask,
153        request: u32,
154        arg: starnix_syscalls::SyscallArg,
155    ) -> Result<SyscallResult, Errno> {
156        let cmd_num = CommandCode::from(request);
157        log_info!("WakeupTestDevice::ioctl cmd_num {cmd_num:?}, arg: {arg:?}");
158
159        match cmd_num {
160            CommandCode::WakeupSetTimers => {
161                let timer_ref = UserRef::<WakeupTimerInfo>::new(arg.into());
162                let timer_info = current_task.read_object(timer_ref)?;
163                log_info!("WakeupTestDevice::WakeupSetTimers {timer_info:?}");
164                log_info!("WakeupTestDevice::WakeupSetTimers version 0x{:x}", timer_info.version);
165
166                match self.commands.run_wakeup_set_timers(timer_info) {
167                    Ok(_) => Ok(SUCCESS),
168                    Err(e) => {
169                        log_error!("WakeupTestDevice::WakeupSetTimers failed: {:?}", e);
170                        return error!(EINVAL);
171                    }
172                }
173            }
174            CommandCode::WakeupTest => error!(ENOSYS),
175            CommandCode::WakeupHowManyTimers => error!(ENOSYS),
176            CommandCode::WakeupCancelTimers => error!(ENOSYS),
177            _ => default_ioctl(file, locked, current_task, request, arg),
178        }
179    }
180}