Skip to main content

starnix_modules_nanohub/
sysfs.rs

1// Copyright 2025 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::sysfs_errno;
6use std::marker::PhantomData;
7
8use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker};
9use fuchsia_component::client::connect_to_protocol_sync;
10use starnix_core::task::CurrentTask;
11use starnix_core::vfs::{
12    AppendLockGuard, FileObject, FileOps, FsNode, FsNodeOps, InputBuffer, OutputBuffer,
13    fileops_impl_noop_sync,
14};
15use starnix_core::{fileops_impl_seekable, fs_node_impl_not_dir};
16use starnix_logging::log_error;
17use starnix_sync::{FileOpsCore, Locked, Mutex};
18use starnix_uapi::errors::Errno;
19use starnix_uapi::open_flags::OpenFlags;
20use starnix_uapi::{errno, error};
21use zx;
22
23/// Convert an `Option<T>` (usually from a FIDL table) to a `Result<T, SysfsError>`.
24pub fn try_get<T>(o: Option<T>) -> Result<T, SysfsError> {
25    o.ok_or_else(|| {
26        log_error!("Missing expected value from Nanohub service method response.");
27        sysfs_errno!(EINVAL)
28    })
29}
30
31/// A wrapper around `starnix_uapi::errors::Errno`.
32#[derive(Debug, PartialEq)]
33pub struct SysfsError(pub Errno);
34
35/// An equivalent to `starnix_uapi::errno!`, but for `SysfsError`.
36#[macro_export]
37macro_rules! sysfs_errno {
38    ($error_code:ident) => {
39        $crate::sysfs::SysfsError(errno!($error_code))
40    };
41}
42
43/// An equivalent to `starnix_uapi::error!`, but for `SysfsError`.
44#[macro_export]
45macro_rules! sysfs_error {
46    ($error_code:ident) => {
47        Err($crate::sysfs::SysfsError(errno!($error_code)))
48    };
49}
50
51impl From<fidl::Error> for SysfsError {
52    /// Generate the sysfs error response for a FIDL transport error.
53    ///
54    /// This allows you to handle the error case simply by using the ? operator.
55    fn from(value: fidl::Error) -> Self {
56        log_error!("FIDL error while calling service method: {value:?}");
57        sysfs_errno!(EINVAL)
58    }
59}
60
61impl From<i32> for SysfsError {
62    /// Generate the sysfs error response for a zx.Status error result from a FIDL method.
63    ///
64    /// This allows you to handle the error case simply by using the ? operator.
65    fn from(value: i32) -> Self {
66        let status = zx::Status::from_raw(value);
67        log_error!("Service method responded with an error: {status:?}");
68        sysfs_errno!(EINVAL)
69    }
70}
71
72/// Operations supported by sysfs files.
73///
74/// These are analogous to (but not identical to) the operations exposed by Linux sysfs attributes.
75pub trait SysfsOps<S: fidl::endpoints::SynchronousProxy>: Default + Send + Sync + 'static {
76    /// Get the value of the attribute.
77    fn show(&self, _service: &S) -> Result<String, SysfsError> {
78        sysfs_error!(EINVAL)
79    }
80
81    /// Store a new value for the attribute.
82    fn store(&self, _service: &S, _value: String) -> Result<(), SysfsError> {
83        sysfs_error!(EINVAL)
84    }
85}
86
87enum SysfsContentsState {
88    Unarmed,
89    Armed(String),
90    Err(Errno),
91}
92
93impl AsRef<SysfsContentsState> for SysfsContentsState {
94    fn as_ref(&self) -> &Self {
95        &self
96    }
97}
98
99/// A file that exposes data via sysfs semantics.
100pub struct SysfsFile<P: ProtocolMarker, O: SysfsOps<P::SynchronousProxy>> {
101    /// Implementations for sysfs operations.
102    sysfs_ops: Box<O>,
103
104    /// An active connection to the FIDL service.
105    service: Mutex<P::SynchronousProxy>,
106
107    /// The buffered contents of the return of the underlying FIDL method.
108    ///
109    /// To match SysFS behavior on Linux, this buffer is only populated/refreshed when the file is
110    /// read from offset position 0, so at the moment the file is opened (i.e., at the moment this
111    /// struct is instantiated), the contents are `Unarmed`. Once the the buffer is populated,
112    /// the result contains either the data returned by the `show` method or a Linux error code.
113    contents: Mutex<SysfsContentsState>,
114
115    _phantom: PhantomData<P>,
116}
117
118impl<P: DiscoverableProtocolMarker, O: SysfsOps<P::SynchronousProxy>> SysfsFile<P, O> {
119    pub fn new(sysfs_ops: Box<O>) -> Result<Box<Self>, Errno> {
120        let service = connect_to_protocol_sync::<P>().map_err(|e| {
121            log_error!("Error connecting to service: {:?}", e);
122            errno!(EIO)
123        })?;
124
125        Ok(Box::new(SysfsFile {
126            sysfs_ops,
127            service: Mutex::new(service),
128            contents: Mutex::new(SysfsContentsState::Unarmed),
129            _phantom: PhantomData,
130        }))
131    }
132
133    /// Refresh the data in the file buffer by calling `show`.
134    ///
135    /// This is analogous to sysfs rearming in Linux.
136    fn rearm(&self) {
137        let op_result = self.sysfs_ops.show(&self.service.lock());
138        let mut contents_guard = self.contents.lock();
139        *contents_guard = match op_result {
140            Ok(value) => SysfsContentsState::Armed(value),
141            Err(error) => SysfsContentsState::Err(error.0),
142        }
143    }
144}
145
146impl<P: DiscoverableProtocolMarker, O: SysfsOps<P::SynchronousProxy>> FileOps for SysfsFile<P, O> {
147    fileops_impl_seekable!();
148    fileops_impl_noop_sync!();
149
150    fn read(
151        &self,
152        _locked: &mut Locked<FileOpsCore>,
153        _file: &FileObject,
154        _current_task: &CurrentTask,
155        offset: usize,
156        data: &mut dyn OutputBuffer,
157    ) -> Result<usize, Errno> {
158        // Reading from offset 0 refreshes the contents buffer via the underlying `show` method.
159        if offset == 0 {
160            self.rearm();
161        };
162
163        let contents_guard = self.contents.lock();
164
165        match contents_guard.as_ref() {
166            SysfsContentsState::Err(error) => Err(error.clone()),
167            SysfsContentsState::Unarmed => {
168                log_error!("Failed to read SysFS file with no contents");
169                error!(EINVAL)
170            }
171            SysfsContentsState::Armed(value) => {
172                // Write the slice of data requested by the caller from the contents buffer,
173                // returning the number of bytes written. If the offset is at the end of the
174                // contents (e.g., if the contents have already been read), this will write 0
175                // bytes, indicating EOF. If an error occurred, nothing will be written and the
176                // error will be returned.
177                let bytes = value.as_bytes();
178                let start = offset.min(bytes.len());
179                let end = (offset + data.available()).min(bytes.len());
180                data.write(&bytes[start..end])
181            }
182        }
183    }
184
185    fn write(
186        &self,
187        _locked: &mut Locked<FileOpsCore>,
188        _file: &FileObject,
189        _current_task: &CurrentTask,
190        _offset: usize,
191        data: &mut dyn InputBuffer,
192    ) -> Result<usize, Errno> {
193        // The entire contents of the input buffer are read and stored in the attribute, regardless
194        // of the current file position.
195        data.read_all()
196            .and_then(|bytes| {
197                String::from_utf8(bytes)
198                    .map_err(|e| {
199                        log_error!("Failed convert to input buffer to string: {e:?}");
200                        errno!(EINVAL)
201                    })
202                    .map(|v| (v.len(), v))
203            })
204            .and_then(|(num_bytes, value)| {
205                self.sysfs_ops
206                    .store(&self.service.lock(), value)
207                    .map(|_| num_bytes)
208                    .map_err(|e| e.0)
209            })
210    }
211}
212
213/// File node for sysfs files.
214pub struct SysfsNode<P: ProtocolMarker, O: SysfsOps<P::SynchronousProxy>> {
215    _phantom_p: PhantomData<P>,
216    _phantom_o: PhantomData<O>,
217}
218
219impl<P: DiscoverableProtocolMarker, O: SysfsOps<P::SynchronousProxy>> SysfsNode<P, O> {
220    pub fn new() -> Self {
221        Self { _phantom_p: PhantomData, _phantom_o: PhantomData }
222    }
223}
224
225impl<P: DiscoverableProtocolMarker, O: SysfsOps<P::SynchronousProxy>> FsNodeOps
226    for SysfsNode<P, O>
227{
228    fs_node_impl_not_dir!();
229
230    fn create_file_ops(
231        &self,
232        _locked: &mut Locked<FileOpsCore>,
233        _node: &FsNode,
234        _current_task: &CurrentTask,
235        _flags: OpenFlags,
236    ) -> Result<Box<dyn FileOps>, Errno> {
237        SysfsFile::<P, O>::new(Box::new(O::default())).map(|f| f as Box<dyn FileOps>)
238    }
239
240    /// Support no-op trunc for Nanohub sysfs endpoints
241    /// This is a safe no-op as these nodes do not have persistent file backings
242    /// and only support show/store operations which are mapped to read/write FileOps.
243    fn truncate(
244        &self,
245        _locked: &mut Locked<FileOpsCore>,
246        _guard: &AppendLockGuard<'_>,
247        _node: &FsNode,
248        _current_task: &CurrentTask,
249        _length: u64,
250    ) -> Result<(), Errno> {
251        Ok(())
252    }
253}