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