diff options
author | Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> | 2019-09-24 01:14:15 +0200 |
---|---|---|
committer | Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> | 2020-09-04 23:36:48 +0200 |
commit | 40cbc3f043eeea8b8f27ece943f42437a264239b (patch) | |
tree | 12724d003fde303aa06a7f94d40992fdd71cd23e /rust/kernel/src/sysctl.rs | |
parent | d012a7190fc1fd72ed48911e77ca97ba4521bccd (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/sysctl.rs')
-rw-r--r-- | rust/kernel/src/sysctl.rs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/rust/kernel/src/sysctl.rs b/rust/kernel/src/sysctl.rs new file mode 100644 index 000000000000..654b7122ba3b --- /dev/null +++ b/rust/kernel/src/sysctl.rs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 + +use alloc::boxed::Box; +use alloc::vec; +use core::mem; +use core::ptr; +use core::sync::atomic; + +use crate::bindings; +use crate::c_types; +use crate::error; +use crate::types; +use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter}; + +pub trait SysctlStorage: Sync { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>); + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>); +} + +fn trim_whitespace(mut data: &[u8]) -> &[u8] { + while !data.is_empty() && (data[0] == b' ' || data[0] == b'\t' || data[0] == b'\n') { + data = &data[1..]; + } + while !data.is_empty() + && (data[data.len() - 1] == b' ' + || data[data.len() - 1] == b'\t' + || data[data.len() - 1] == b'\n') + { + data = &data[..data.len() - 1]; + } + data +} + +impl<T> SysctlStorage for &T +where + T: SysctlStorage, +{ + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>) { + (*self).store_value(data) + } + + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>) { + (*self).read_value(data) + } +} + +impl SysctlStorage for atomic::AtomicBool { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>) { + let result = match trim_whitespace(data) { + b"0" => { + self.store(false, atomic::Ordering::Relaxed); + Ok(()) + } + b"1" => { + self.store(true, atomic::Ordering::Relaxed); + Ok(()) + } + _ => Err(error::Error::EINVAL), + }; + (data.len(), result) + } + + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>) { + let value = if self.load(atomic::Ordering::Relaxed) { + b"1\n" + } else { + b"0\n" + }; + (value.len(), data.write(value)) + } +} + +pub struct Sysctl<T: SysctlStorage> { + inner: Box<T>, + // Responsible for keeping the ctl_table alive. + _table: Box<[bindings::ctl_table]>, + header: *mut bindings::ctl_table_header, +} + +// This is safe because the only public method we have is get(), which returns +// &T, and T: Sync. Any new methods must adhere to this requirement. +unsafe impl<T: SysctlStorage> Sync for Sysctl<T> {} + +unsafe extern "C" fn proc_handler<T: SysctlStorage>( + ctl: *mut bindings::ctl_table, + write: c_types::c_int, + buffer: *mut c_types::c_void, + len: *mut usize, + ppos: *mut bindings::loff_t, +) -> c_types::c_int { + // If we're reading from some offset other than the beginning of the file, + // return an empty read to signal EOF. + if *ppos != 0 && write == 0 { + *len = 0; + return 0; + } + + let data = match UserSlicePtr::new(buffer, *len) { + Ok(ptr) => ptr, + Err(e) => return e.to_kernel_errno(), + }; + let storage = &*((*ctl).data as *const T); + let (bytes_processed, result) = if write != 0 { + let data = match data.read_all() { + Ok(r) => r, + Err(e) => return e.to_kernel_errno(), + }; + storage.store_value(&data) + } else { + let mut writer = data.writer(); + storage.read_value(&mut writer) + }; + *len = bytes_processed; + *ppos += *len as bindings::loff_t; + match result { + Ok(()) => 0, + Err(e) => e.to_kernel_errno(), + } +} + +impl<T: SysctlStorage> Sysctl<T> { + pub fn register( + path: &'static types::CStr, + name: &'static types::CStr, + storage: T, + mode: types::Mode, + ) -> error::KernelResult<Sysctl<T>> { + if name.contains('/') { + return Err(error::Error::EINVAL); + } + + let storage = Box::new(storage); + let mut table = vec![ + bindings::ctl_table { + procname: name.as_ptr() as *const i8, + mode: mode.as_int(), + data: &*storage as *const T as *mut c_types::c_void, + proc_handler: Some(proc_handler::<T>), + + maxlen: 0, + child: ptr::null_mut(), + poll: ptr::null_mut(), + extra1: ptr::null_mut(), + extra2: ptr::null_mut(), + }, + unsafe { mem::zeroed() }, + ] + .into_boxed_slice(); + + let result = + unsafe { bindings::register_sysctl(path.as_ptr() as *const i8, table.as_mut_ptr()) }; + if result.is_null() { + return Err(error::Error::ENOMEM); + } + + Ok(Sysctl { + inner: storage, + _table: table, + header: result, + }) + } + + pub fn get(&self) -> &T { + &self.inner + } +} + +impl<T: SysctlStorage> Drop for Sysctl<T> { + fn drop(&mut self) { + unsafe { + bindings::unregister_sysctl_table(self.header); + } + self.header = ptr::null_mut(); + } +} |