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