summaryrefslogtreecommitdiffstats
path: root/rust/kernel/src/file_operations.rs
diff options
context:
space:
mode:
authorMiguel Ojeda <miguel.ojeda.sandonis@gmail.com>2019-09-24 01:14:15 +0200
committerMiguel Ojeda <miguel.ojeda.sandonis@gmail.com>2020-09-04 23:36:48 +0200
commit40cbc3f043eeea8b8f27ece943f42437a264239b (patch)
tree12724d003fde303aa06a7f94d40992fdd71cd23e /rust/kernel/src/file_operations.rs
parentd012a7190fc1fd72ed48911e77ca97ba4521bccd (diff)
Initial Rust support
Currently we have several lines of work:   - Integrating with the kernel tree and build system (Nick's & mine, both uploaded as branches, based on `rustc`; and another one I have been working on, based on `cargo`).   - Bindings and the first bits of functionality (Alex's & Geoffrey's, based on `cargo`). This patch effectively merges the work we have been doing and integrates it in the latest mainline kernel tree. This does *not* mean anything needs to stay as-is, but this gives us a working, common base to work and experiment upon. Hopefully, it will also attract external people to join! As a summary, I added: - `cargo` integration with the kernel `Makefile`s:   + Virtual `cargo` workspace to have a single lock file and to share deps between `cargo` jobs. + Picks the same optimization level as configured for C. + Verbose output on `V=1`. + A `cargoclean` target to clean all the Rust-related artifacts. - Initial support for built-in modules (i.e. `Y` as well as `M`): + It is a hack, we need to implement a few things (see the `TODO`s), but it is enough to start playing with things that depend on `MODULE`. + Passes `--cfg module` to built-in modules to be able to compile conditionally inside Rust. + Increased `KSYM_NAME_LEN` length to avoid warnings due to Rust long mangled symbols.   - Rust infrastructure in a new top level folder `rust/`: + A `kernel` package which contains the sources from Alex & Geoffrey's work plus some changes:     * Adapted `build.rs`. * Removed the `THIS_MODULE` emulation until it is implemented.     * Removed `Makefile` logic and the code that `cfg`-depended on kernel version (no need in mainline). * Moved the helpers to be triggered via normal compilation, renamed them under `rust_*` and exported via `EXPORT_SYMBOL` instead. * Added a prelude. + A `shlex` package which serves as an example of an "inline" dependency (i.e. package checked out copy to avoid the network) - The example driver was setup at `drivers/char/rust_example/`. - Misc + The beginning of `Documentation/rust/` with a quick start guide. + `MAINTAINERS` entry. + SPDXs for all files. Other notes that aren't in `TODO`s:   - We could minimize the network requirements (use `bindgen` binary, use more inline dependencies...), but it is not clear it would be a good idea for the moment due to `core`/`alloc`/`compiler-builtins`. - The intention of `rust/` is to have a place to put extra dependencies and split the `kernel` package into several in the future if it grows. It could resemble the usual kernel tree structure. - With several drivers being built-in, `cargo` recompiles `kernel` and triggers duplicate symbol errors when merging thin archives. We need to make it only compile `kernel` once.   - When the above works, then `make`'s `-j` calling concurrent `cargo`s (e.g. several drivers at the same time) should be OK, I think. According to https://github.com/rust-lang/cargo/pull/2486 it shouldn't miscompile anything, but if they start locking on each other we will have make jobs waiting that could have been doing something else. Signed-off-by: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com>
Diffstat (limited to 'rust/kernel/src/file_operations.rs')
-rw-r--r--rust/kernel/src/file_operations.rs219
1 files changed, 219 insertions, 0 deletions
diff --git a/rust/kernel/src/file_operations.rs b/rust/kernel/src/file_operations.rs
new file mode 100644
index 000000000000..100fb62281e9
--- /dev/null
+++ b/rust/kernel/src/file_operations.rs
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::convert::{TryFrom, TryInto};
+use core::{marker, mem, ptr};
+
+use alloc::boxed::Box;
+
+use crate::bindings;
+use crate::c_types;
+use crate::error::{Error, KernelResult};
+use crate::user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter};
+
+bitflags::bitflags! {
+ pub struct FileFlags: c_types::c_uint {
+ const NONBLOCK = bindings::O_NONBLOCK;
+ }
+}
+
+pub struct File {
+ ptr: *const bindings::file,
+}
+
+impl File {
+ unsafe fn from_ptr(ptr: *const bindings::file) -> File {
+ File { ptr }
+ }
+
+ pub fn pos(&self) -> u64 {
+ unsafe { (*self.ptr).f_pos as u64 }
+ }
+
+ pub fn flags(&self) -> FileFlags {
+ FileFlags::from_bits_truncate(unsafe { (*self.ptr).f_flags })
+ }
+}
+
+// Matches std::io::SeekFrom in the Rust stdlib
+pub enum SeekFrom {
+ Start(u64),
+ End(i64),
+ Current(i64),
+}
+
+unsafe extern "C" fn open_callback<T: FileOperations>(
+ _inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> c_types::c_int {
+ let f = match T::open() {
+ Ok(f) => Box::new(f),
+ Err(e) => return e.to_kernel_errno(),
+ };
+ (*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
+ 0
+}
+
+unsafe extern "C" fn read_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ buf: *mut c_types::c_char,
+ len: c_types::c_size_t,
+ offset: *mut bindings::loff_t,
+) -> c_types::c_ssize_t {
+ let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
+ Ok(ptr) => ptr.writer(),
+ Err(e) => return e.to_kernel_errno().try_into().unwrap(),
+ };
+ let f = &*((*file).private_data as *const T);
+ // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63).
+ // See discussion in #113
+ let positive_offset = match (*offset).try_into() {
+ Ok(v) => v,
+ Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(),
+ };
+ let read = T::READ.unwrap();
+ match read(f, &File::from_ptr(file), &mut data, positive_offset) {
+ Ok(()) => {
+ let written = len - data.len();
+ (*offset) += bindings::loff_t::try_from(written).unwrap();
+ written.try_into().unwrap()
+ }
+ Err(e) => e.to_kernel_errno().try_into().unwrap(),
+ }
+}
+
+unsafe extern "C" fn write_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ buf: *const c_types::c_char,
+ len: c_types::c_size_t,
+ offset: *mut bindings::loff_t,
+) -> c_types::c_ssize_t {
+ let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
+ Ok(ptr) => ptr.reader(),
+ Err(e) => return e.to_kernel_errno().try_into().unwrap(),
+ };
+ let f = &*((*file).private_data as *const T);
+ // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63).
+ // See discussion in #113
+ let positive_offset = match (*offset).try_into() {
+ Ok(v) => v,
+ Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(),
+ };
+ let write = T::WRITE.unwrap();
+ match write(f, &mut data, positive_offset) {
+ Ok(()) => {
+ let read = len - data.len();
+ (*offset) += bindings::loff_t::try_from(read).unwrap();
+ read.try_into().unwrap()
+ }
+ Err(e) => e.to_kernel_errno().try_into().unwrap(),
+ }
+}
+
+unsafe extern "C" fn release_callback<T: FileOperations>(
+ _inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> c_types::c_int {
+ let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut());
+ drop(Box::from_raw(ptr as *mut T));
+ 0
+}
+
+unsafe extern "C" fn llseek_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ offset: bindings::loff_t,
+ whence: c_types::c_int,
+) -> bindings::loff_t {
+ let off = match whence as u32 {
+ bindings::SEEK_SET => match offset.try_into() {
+ Ok(v) => SeekFrom::Start(v),
+ Err(_) => return Error::EINVAL.to_kernel_errno().into(),
+ },
+ bindings::SEEK_CUR => SeekFrom::Current(offset),
+ bindings::SEEK_END => SeekFrom::End(offset),
+ _ => return Error::EINVAL.to_kernel_errno().into(),
+ };
+ let f = &*((*file).private_data as *const T);
+ let seek = T::SEEK.unwrap();
+ match seek(f, &File::from_ptr(file), off) {
+ Ok(off) => off as bindings::loff_t,
+ Err(e) => e.to_kernel_errno().into(),
+ }
+}
+
+pub(crate) struct FileOperationsVtable<T>(marker::PhantomData<T>);
+
+impl<T: FileOperations> FileOperationsVtable<T> {
+ pub(crate) const VTABLE: bindings::file_operations = bindings::file_operations {
+ open: Some(open_callback::<T>),
+ release: Some(release_callback::<T>),
+ read: if let Some(_) = T::READ {
+ Some(read_callback::<T>)
+ } else {
+ None
+ },
+ write: if let Some(_) = T::WRITE {
+ Some(write_callback::<T>)
+ } else {
+ None
+ },
+ llseek: if let Some(_) = T::SEEK {
+ Some(llseek_callback::<T>)
+ } else {
+ None
+ },
+
+ check_flags: None,
+ compat_ioctl: None,
+ copy_file_range: None,
+ fallocate: None,
+ fadvise: None,
+ fasync: None,
+ flock: None,
+ flush: None,
+ fsync: None,
+ get_unmapped_area: None,
+ iterate: None,
+ iterate_shared: None,
+ iopoll: None,
+ lock: None,
+ mmap: None,
+ mmap_supported_flags: 0,
+ owner: ptr::null_mut(),
+ poll: None,
+ read_iter: None,
+ remap_file_range: None,
+ sendpage: None,
+ setlease: None,
+ show_fdinfo: None,
+ splice_read: None,
+ splice_write: None,
+ unlocked_ioctl: None,
+ write_iter: None,
+ };
+}
+
+pub type ReadFn<T> = Option<fn(&T, &File, &mut UserSlicePtrWriter, u64) -> KernelResult<()>>;
+pub type WriteFn<T> = Option<fn(&T, &mut UserSlicePtrReader, u64) -> KernelResult<()>>;
+pub type SeekFn<T> = Option<fn(&T, &File, SeekFrom) -> KernelResult<u64>>;
+
+/// `FileOperations` corresponds to the kernel's `struct file_operations`. You
+/// implement this trait whenever you'd create a `struct file_operations`.
+/// File descriptors may be used from multiple threads (or processes)
+/// concurrently, so your type must be `Sync`.
+pub trait FileOperations: Sync + Sized {
+ /// Creates a new instance of this file. Corresponds to the `open` function
+ /// pointer in `struct file_operations`.
+ fn open() -> KernelResult<Self>;
+
+ /// Reads data from this file to userspace. Corresponds to the `read`
+ /// function pointer in `struct file_operations`.
+ const READ: ReadFn<Self> = None;
+
+ /// Writes data from userspace o this file. Corresponds to the `write`
+ /// function pointer in `struct file_operations`.
+ const WRITE: WriteFn<Self> = None;
+
+ /// Changes the position of the file. Corresponds to the `llseek` function
+ /// pointer in `struct file_operations`.
+ const SEEK: SeekFn<Self> = None;
+}