starnix_core/fs/fuchsia/
sync_file.rs

1// Copyright 2023 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::fs::fuchsia::RemoteCounter;
6use crate::mm::MemoryAccessorExt;
7use crate::task::{
8    CurrentTask, EventHandler, ManyZxHandleSignalHandler, SignalHandler, SignalHandlerInner,
9    WaitCanceler, Waiter,
10};
11use crate::vfs::buffers::{InputBuffer, OutputBuffer};
12use crate::vfs::{
13    Anon, FdFlags, FdNumber, FileHandle, FileObject, FileOps, fileops_impl_nonseekable,
14    fileops_impl_noop_sync,
15};
16
17use starnix_lifecycle::AtomicUsizeCounter;
18use starnix_logging::{CATEGORY_STARNIX, impossible_error, log_warn, trace_duration};
19use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
20use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
21use starnix_uapi::errors::Errno;
22use starnix_uapi::open_flags::OpenFlags;
23use starnix_uapi::user_address::{UserAddress, UserRef};
24use starnix_uapi::vfs::FdEvents;
25use starnix_uapi::{
26    SYNC_IOC_MAGIC, c_char, errno, error, sync_fence_info, sync_file_info, sync_merge_data,
27};
28use std::collections::HashSet;
29use std::sync::Arc;
30use zx::HandleBased;
31
32// Implementation of the sync framework described at:
33// https://source.android.com/docs/core/graphics/sync
34//
35// A sync point "is a single value or point on a sync_timeline. A point has three states: active,
36// signaled, and error. Points start in the active state and transition to the signaled or error
37// states."  A timestamp of the state transition is returned by the ioctl SYNC_IOC_FILE_INFO,
38// so we use VMOs to implement the sync point.  The timestamp is stored in the first 8 bytes of
39// the VMO and should be stored before the object signal state change.  This timestamp is always
40// early; while in most cases only by a slight amount, the difference could be substantial if the
41// signaling thread is de-scheduled in the middle of the two syscalls.
42// TODO(b/305781995) - use events instead of VMOs.
43//
44
45const SYNC_IOC_MERGE: u8 = 3;
46const SYNC_IOC_FILE_INFO: u8 = 4;
47
48#[derive(Clone, Debug)]
49pub enum Timeline {
50    Magma,
51    Hwc,
52}
53
54#[derive(PartialEq, Copy, Clone)]
55// Error status (-1) is not currently used.
56pub enum Status {
57    Active = 0,
58    Signaled = 1,
59}
60
61#[derive(Clone)]
62pub struct SyncPoint {
63    pub timeline: Timeline,
64    pub counter: Arc<zx::Counter>,
65}
66
67impl SyncPoint {
68    pub fn new(timeline: Timeline, counter: zx::Counter) -> SyncPoint {
69        SyncPoint { timeline, counter: Arc::new(counter) }
70    }
71}
72
73pub struct SyncFence {
74    pub sync_points: Vec<SyncPoint>,
75}
76
77pub struct SyncFile {
78    pub name: [u8; 32],
79    pub fence: SyncFence,
80}
81
82struct FenceState {
83    status: Status,
84    timestamp_ns: u64,
85}
86
87impl SyncFile {
88    const SIGNALS: zx::Signals = zx::Signals::COUNTER_SIGNALED;
89
90    /// Returns a `FileHandle` with the specified `name` and `fence`.
91    pub fn new_file<L>(
92        locked: &mut Locked<L>,
93        current_task: &CurrentTask,
94        name: [u8; 32],
95        fence: SyncFence,
96    ) -> Result<FileHandle, Errno>
97    where
98        L: LockEqualOrBefore<FileOpsCore>,
99    {
100        // The Linux `create_sync_file()` helper is described as using `anon_inode_getfile()` to
101        // wrap the file-ops into a file, which means using the singleton private anonymous inode.
102        Ok(Anon::new_private_file(
103            locked,
104            current_task,
105            Box::new(SyncFile::new(name, fence)),
106            OpenFlags::RDWR,
107            "sync_file",
108        ))
109    }
110
111    pub fn new(name: [u8; 32], fence: SyncFence) -> SyncFile {
112        SyncFile { name, fence }
113    }
114
115    fn get_fence_state(&self) -> Vec<FenceState> {
116        let mut state: Vec<FenceState> = vec![];
117
118        for sync_point in &self.fence.sync_points {
119            if sync_point.counter.wait_one(Self::SIGNALS, zx::MonotonicInstant::ZERO).to_result()
120                == Err(zx::Status::TIMED_OUT)
121            {
122                state.push(FenceState { status: Status::Active, timestamp_ns: 0 });
123            } else {
124                state.push(FenceState {
125                    status: Status::Signaled,
126                    timestamp_ns: sync_point.counter.read().unwrap() as u64,
127                });
128            }
129        }
130        state
131    }
132}
133
134impl FileOps for SyncFile {
135    fileops_impl_nonseekable!();
136    fileops_impl_noop_sync!();
137
138    fn to_handle(
139        &self,
140        _file: &FileObject,
141        _current_task: &CurrentTask,
142    ) -> Result<Option<zx::NullableHandle>, Errno> {
143        error!(ENOTSUP)
144    }
145
146    fn get_handles(
147        &self,
148        _file: &FileObject,
149        _current_task: &CurrentTask,
150    ) -> Result<Vec<zx::NullableHandle>, Errno> {
151        let mut handles = Vec::with_capacity(self.fence.sync_points.len());
152        for sync_point in &self.fence.sync_points {
153            let handle = sync_point
154                .counter
155                .duplicate_handle(zx::Rights::SAME_RIGHTS)
156                .map_err(impossible_error)?
157                .into();
158            handles.push(handle);
159        }
160        Ok(handles)
161    }
162
163    fn ioctl(
164        &self,
165        locked: &mut Locked<Unlocked>,
166        _file: &FileObject,
167        current_task: &CurrentTask,
168        request: u32,
169        arg: SyscallArg,
170    ) -> Result<SyscallResult, Errno> {
171        let user_addr = UserAddress::from(arg);
172        let ioctl_type = (request >> 8) as u8;
173        let ioctl_number = request as u8;
174
175        if ioctl_type != SYNC_IOC_MAGIC {
176            log_warn!("Unexpected type {:?}", ioctl_type);
177            return error!(EINVAL);
178        }
179
180        match ioctl_number {
181            SYNC_IOC_MERGE => {
182                trace_duration!(CATEGORY_STARNIX, "SyncFileMerge");
183                let user_ref = UserRef::new(user_addr);
184                let mut merge_data: sync_merge_data = current_task.read_object(user_ref)?;
185                let file2 = current_task.files.get(FdNumber::from_raw(merge_data.fd2))?;
186
187                let mut fence = SyncFence { sync_points: vec![] };
188                let mut set = HashSet::<zx::Koid>::new();
189
190                for sync_point in &self.fence.sync_points {
191                    let koid = sync_point.counter.koid().unwrap();
192                    if set.insert(koid) {
193                        fence.sync_points.push(sync_point.clone());
194                    }
195                }
196
197                if let Some(file2) = file2.downcast_file::<SyncFile>() {
198                    for sync_point in &file2.fence.sync_points {
199                        let koid = sync_point.counter.koid().unwrap();
200                        if set.insert(koid) {
201                            fence.sync_points.push(sync_point.clone());
202                        }
203                    }
204                } else if let Some(file2) = file2.downcast_file::<RemoteCounter>() {
205                    let counter = file2.duplicate_handle()?;
206                    let koid = counter.koid().map_err(impossible_error)?;
207                    if set.insert(koid) {
208                        fence.sync_points.push(SyncPoint::new(Timeline::Hwc, counter.into()));
209                    }
210                } else {
211                    return error!(EINVAL);
212                }
213
214                // Remove sync points that are already signaled.
215                let mut i = 0 as usize;
216                let mut last_signaled_timestamp_ns = 0;
217                let mut last_signaled_sync_point: Option<SyncPoint> = None;
218                while i < fence.sync_points.len() {
219                    if fence.sync_points[i]
220                        .counter
221                        .wait_one(Self::SIGNALS, zx::MonotonicInstant::ZERO)
222                        .to_result()
223                        != Err(zx::Status::TIMED_OUT)
224                    {
225                        let timestamp_ns =
226                            fence.sync_points[i].counter.read().map_err(|_| errno!(EIO))?;
227                        let removed = fence.sync_points.remove(i);
228                        if i == 0 && timestamp_ns >= last_signaled_timestamp_ns {
229                            last_signaled_timestamp_ns = timestamp_ns;
230                            last_signaled_sync_point = Some(removed);
231                        }
232                        continue;
233                    }
234                    i += 1;
235                }
236                if fence.sync_points.is_empty() {
237                    fence.sync_points.push(last_signaled_sync_point.expect("No sync points left."));
238                }
239
240                let name = merge_data.name.map(|x| x as u8);
241                let file = SyncFile::new_file(locked, current_task, name, fence)?;
242
243                let fd = current_task.add_file(locked, file, FdFlags::empty())?;
244                merge_data.fence = fd.raw();
245
246                current_task.write_object(user_ref, &merge_data)?;
247                Ok(SUCCESS)
248            }
249            SYNC_IOC_FILE_INFO => {
250                trace_duration!(CATEGORY_STARNIX, "SyncFileInfo");
251                let user_ref = UserRef::new(user_addr);
252                let mut info: sync_file_info = current_task.read_object(user_ref)?;
253
254                for i in 0..self.name.len() {
255                    info.name[i] = self.name[i] as c_char;
256                }
257                info.status = 0;
258
259                if info.num_fences == 0 {
260                    info.num_fences = self.fence.sync_points.len() as u32;
261                } else if info.num_fences > self.fence.sync_points.len() as u32 {
262                    return error!(EINVAL);
263                } else {
264                    let fence_state = self.get_fence_state();
265                    let mut user_addr = info.sync_fence_info;
266
267                    let mut sync_file_status = 1;
268                    for (i, state) in fence_state.iter().enumerate() {
269                        if state.status == Status::Active {
270                            sync_file_status = 0;
271                        }
272                        if i < info.num_fences as usize {
273                            // Note: obj_name not supported.
274                            let mut fence_info = sync_fence_info {
275                                status: state.status as i32,
276                                timestamp_ns: state.timestamp_ns,
277                                ..sync_fence_info::default()
278                            };
279                            let driver_name = match self.fence.sync_points[i].timeline {
280                                Timeline::Magma => b"Magma\0",
281                                Timeline::Hwc => b"Hwc\0\0\0",
282                            };
283                            assert!(driver_name.len() <= fence_info.driver_name.len());
284                            for i in 0..driver_name.len() {
285                                fence_info.driver_name[i] = driver_name[i] as c_char;
286                            }
287
288                            let fence_user_ref = UserRef::new(UserAddress::from(user_addr));
289                            user_addr += std::mem::size_of::<sync_fence_info>() as u64;
290
291                            current_task.write_object(fence_user_ref, &fence_info)?;
292                        }
293                    }
294
295                    info.status = sync_file_status;
296                }
297
298                current_task.write_object(user_ref, &info)?;
299                Ok(SUCCESS)
300            }
301            _ => {
302                error!(EINVAL)
303            }
304        }
305    }
306
307    fn wait_async(
308        &self,
309        _locked: &mut Locked<FileOpsCore>,
310        _file: &FileObject,
311        _current_task: &CurrentTask,
312        waiter: &Waiter,
313        events: FdEvents,
314        event_handler: EventHandler,
315    ) -> Option<WaitCanceler> {
316        if !events.contains(FdEvents::POLLIN) {
317            return None;
318        }
319
320        let count = Arc::<AtomicUsizeCounter>::new(0.into());
321
322        let mut canceler = WaitCanceler::new_noop();
323
324        for sync_point in &self.fence.sync_points {
325            let signal_handler = SignalHandler {
326                inner: SignalHandlerInner::ManyZxHandle(ManyZxHandleSignalHandler {
327                    count: self.fence.sync_points.len(),
328                    counter: count.clone(),
329                    expected_signals: Self::SIGNALS,
330                    events: FdEvents::POLLIN,
331                }),
332                event_handler: event_handler.clone(),
333                err_code: None,
334            };
335
336            let canceler_result = waiter.wake_on_zircon_signals(
337                sync_point.counter.as_ref(),
338                Self::SIGNALS,
339                signal_handler,
340            );
341            let canceler_result = match canceler_result {
342                Ok(o) => o,
343                Err(e) => {
344                    log_warn!("Error returned from wake_on_zircon_signals: {:?}", e);
345                    return None;
346                }
347            };
348
349            // The wakeup is edge triggered, so handles that were already signaled will never get
350            // a callback. Normally the "already signaled" case is handled by a call to
351            // query_events() after this query_async() returns; however that works only if all
352            // handles are signaled.  Here we perform the counting, and cancel waits, for any
353            // handles currently signaled.
354            if sync_point.counter.wait_one(Self::SIGNALS, zx::MonotonicInstant::ZERO).to_result()
355                == Err(zx::Status::TIMED_OUT)
356            {
357                canceler = WaitCanceler::merge_unbounded(
358                    canceler,
359                    WaitCanceler::new_port(canceler_result),
360                );
361            } else {
362                canceler_result.cancel();
363                count.next();
364            }
365        }
366
367        Some(canceler)
368    }
369
370    fn query_events(
371        &self,
372        _locked: &mut Locked<FileOpsCore>,
373        _file: &FileObject,
374        _current_task: &CurrentTask,
375    ) -> Result<FdEvents, Errno> {
376        let fence_state = self.get_fence_state();
377
378        for state in fence_state.iter() {
379            if state.status == Status::Active {
380                return Ok(FdEvents::empty());
381            }
382        }
383
384        Ok(FdEvents::POLLIN)
385    }
386
387    fn read(
388        &self,
389        _locked: &mut Locked<FileOpsCore>,
390        _file: &FileObject,
391        _current_task: &CurrentTask,
392        _offset: usize,
393        _data: &mut dyn OutputBuffer,
394    ) -> Result<usize, Errno> {
395        error!(ENODEV)
396    }
397
398    fn write(
399        &self,
400        _locked: &mut Locked<FileOpsCore>,
401        _file: &FileObject,
402        _current_task: &CurrentTask,
403        _offset: usize,
404        _data: &mut dyn InputBuffer,
405    ) -> Result<usize, Errno> {
406        error!(ENODEV)
407    }
408}