Skip to main content

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