diff options
author | Miguel Ojeda <ojeda@users.noreply.github.com> | 2020-09-12 15:37:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-12 15:37:03 +0200 |
commit | e280b81cab4477f0709e9f519c86944af44a913c (patch) | |
tree | c9ab0fd92d707e3f3006c9f0c032928b9c4a1ee8 /rust | |
parent | d012a7190fc1fd72ed48911e77ca97ba4521bccd (diff) | |
parent | 0a99f235bd70ef6a8e5bed61e17f312b9839db56 (diff) |
Merge pull request #4 from Rust-for-Linux/rust-initial
Initial Rust support
Diffstat (limited to 'rust')
-rw-r--r-- | rust/Makefile | 4 | ||||
-rw-r--r-- | rust/helpers.c | 21 | ||||
-rw-r--r-- | rust/kernel/Cargo.toml | 16 | ||||
-rw-r--r-- | rust/kernel/build.rs | 133 | ||||
-rw-r--r-- | rust/kernel/src/.gitignore | 3 | ||||
-rw-r--r-- | rust/kernel/src/allocator.rs | 26 | ||||
-rw-r--r-- | rust/kernel/src/bindings.rs | 16 | ||||
-rw-r--r-- | rust/kernel/src/bindings_helper.h | 12 | ||||
-rw-r--r-- | rust/kernel/src/c_types.rs | 25 | ||||
-rw-r--r-- | rust/kernel/src/chrdev.rs | 101 | ||||
-rw-r--r-- | rust/kernel/src/error.rs | 32 | ||||
-rw-r--r-- | rust/kernel/src/file_operations.rs | 219 | ||||
-rw-r--r-- | rust/kernel/src/filesystem.rs | 84 | ||||
-rw-r--r-- | rust/kernel/src/lib.rs | 176 | ||||
-rw-r--r-- | rust/kernel/src/prelude.rs | 16 | ||||
-rw-r--r-- | rust/kernel/src/printk.rs | 72 | ||||
-rw-r--r-- | rust/kernel/src/random.rs | 43 | ||||
-rw-r--r-- | rust/kernel/src/sysctl.rs | 175 | ||||
-rw-r--r-- | rust/kernel/src/types.rs | 54 | ||||
-rw-r--r-- | rust/kernel/src/user_ptr.rs | 176 | ||||
-rw-r--r-- | rust/shlex/Cargo.toml | 11 | ||||
-rw-r--r-- | rust/shlex/src/lib.rs | 231 |
22 files changed, 1646 insertions, 0 deletions
diff --git a/rust/Makefile b/rust/Makefile new file mode 100644 index 000000000000..02f2459083e4 --- /dev/null +++ b/rust/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_HAS_RUST) = helpers.o + diff --git a/rust/helpers.c b/rust/helpers.c new file mode 100644 index 000000000000..1efb4776b9e8 --- /dev/null +++ b/rust/helpers.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bug.h> +#include <linux/build_bug.h> +#include <linux/uaccess.h> + +void rust_helper_BUG(void) +{ + BUG(); +} +EXPORT_SYMBOL(rust_helper_BUG); + +int rust_helper_access_ok(const void __user *addr, unsigned long n) +{ + return access_ok(addr, n); +} +EXPORT_SYMBOL(rust_helper_access_ok); + +// See https://github.com/rust-lang/rust-bindgen/issues/1671 +static_assert(__builtin_types_compatible_p(size_t, uintptr_t), + "size_t must match uintptr_t, what architecture is this??"); diff --git a/rust/kernel/Cargo.toml b/rust/kernel/Cargo.toml new file mode 100644 index 000000000000..d7f6b5aa375d --- /dev/null +++ b/rust/kernel/Cargo.toml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +[package] +name = "kernel" +version = "0.1.0" +authors = ["Rust for Linux Contributors"] +edition = "2018" +publish = false + +[dependencies] +bitflags = "1" + +[build-dependencies] +bindgen = "0.54" +shlex = { path = "../shlex" } + diff --git a/rust/kernel/build.rs b/rust/kernel/build.rs new file mode 100644 index 000000000000..b9085a6367b4 --- /dev/null +++ b/rust/kernel/build.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 + +use std::path::PathBuf; +use std::env; + +const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"]; +const INCLUDED_FUNCTIONS: &[&str] = &[ + "cdev_add", + "cdev_init", + "cdev_del", + "register_filesystem", + "unregister_filesystem", + "krealloc", + "kfree", + "mount_nodev", + "kill_litter_super", + "register_sysctl", + "unregister_sysctl_table", + "access_ok", + "_copy_to_user", + "_copy_from_user", + "alloc_chrdev_region", + "unregister_chrdev_region", + "wait_for_random_bytes", + "get_random_bytes", + "rng_is_initialized", + "printk", + "add_device_randomness", +]; +const INCLUDED_VARS: &[&str] = &[ + "EINVAL", + "ENOMEM", + "ESPIPE", + "EFAULT", + "EAGAIN", + "__this_module", + "FS_REQUIRES_DEV", + "FS_BINARY_MOUNTDATA", + "FS_HAS_SUBTYPE", + "FS_USERNS_MOUNT", + "FS_RENAME_DOES_D_MOVE", + "BINDINGS_GFP_KERNEL", + "KERN_INFO", + "VERIFY_WRITE", + "LINUX_VERSION_CODE", + "SEEK_SET", + "SEEK_CUR", + "SEEK_END", + "O_NONBLOCK", +]; +const OPAQUE_TYPES: &[&str] = &[ + // These need to be opaque because they're both packed and aligned, which rustc + // doesn't support yet. See https://github.com/rust-lang/rust/issues/59154 + // and https://github.com/rust-lang/rust-bindgen/issues/1538 + "desc_struct", + "xregs_state", +]; + +// Takes the CFLAGS from the kernel Makefile and changes all the include paths to be absolute +// instead of relative. +fn prepare_cflags(cflags: &str, kernel_dir: &str) -> Vec<String> { + let cflag_parts = shlex::split(&cflags).unwrap(); + let mut cflag_iter = cflag_parts.iter(); + let mut kernel_args = vec![]; + while let Some(arg) = cflag_iter.next() { + // TODO: bindgen complains + if arg.starts_with("-Wp,-MMD") { + continue; + } + + if arg.starts_with("-I") && !arg.starts_with("-I/") { + kernel_args.push(format!("-I{}/{}", kernel_dir, &arg[2..])); + } else if arg == "-include" { + kernel_args.push(arg.to_string()); + let include_path = cflag_iter.next().unwrap(); + if include_path.starts_with('/') { + kernel_args.push(include_path.to_string()); + } else { + kernel_args.push(format!("{}/{}", kernel_dir, include_path)); + } + } else { + kernel_args.push(arg.to_string()); + } + } + kernel_args +} + +fn main() { + println!("cargo:rerun-if-env-changed=CC"); + println!("cargo:rerun-if-env-changed=RUST_BINDGEN_CFLAGS"); + + let kernel_dir = "../../"; + let cflags = env::var("RUST_BINDGEN_CFLAGS") + .expect("Must be invoked from kernel makefile"); + + let kernel_args = prepare_cflags(&cflags, &kernel_dir); + + let target = env::var("TARGET").unwrap(); + + let mut builder = bindgen::Builder::default() + .use_core() + .ctypes_prefix("c_types") + .derive_default(true) + .size_t_is_usize(true) + .rustfmt_bindings(true); + + builder = builder.clang_arg(format!("--target={}", target)); + for arg in kernel_args.iter() { + builder = builder.clang_arg(arg.clone()); + } + + println!("cargo:rerun-if-changed=src/bindings_helper.h"); + builder = builder.header("src/bindings_helper.h"); + + for t in INCLUDED_TYPES { + builder = builder.whitelist_type(t); + } + for f in INCLUDED_FUNCTIONS { + builder = builder.whitelist_function(f); + } + for v in INCLUDED_VARS { + builder = builder.whitelist_var(v); + } + for t in OPAQUE_TYPES { + builder = builder.opaque_type(t); + } + let bindings = builder.generate().expect("Unable to generate bindings"); + + let out_path = PathBuf::from("src/bindings_gen.rs"); + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} diff --git a/rust/kernel/src/.gitignore b/rust/kernel/src/.gitignore new file mode 100644 index 000000000000..61552b03b12d --- /dev/null +++ b/rust/kernel/src/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +bindings_gen.rs diff --git a/rust/kernel/src/allocator.rs b/rust/kernel/src/allocator.rs new file mode 100644 index 000000000000..27647be92b51 --- /dev/null +++ b/rust/kernel/src/allocator.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr; + +use crate::bindings; +use crate::c_types; + +pub struct KernelAllocator; + +unsafe impl GlobalAlloc for KernelAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // krealloc is used instead of kmalloc because kmalloc is an inline function and can't be + // bound to as a result + bindings::krealloc(ptr::null(), layout.size(), bindings::GFP_KERNEL) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + bindings::kfree(ptr as *const c_types::c_void); + } +} + +#[alloc_error_handler] +fn oom(_layout: Layout) -> ! { + panic!("Out of memory!"); +} diff --git a/rust/kernel/src/bindings.rs b/rust/kernel/src/bindings.rs new file mode 100644 index 000000000000..cfb004a6d786 --- /dev/null +++ b/rust/kernel/src/bindings.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow( + clippy::all, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + improper_ctypes +)] +mod bindings_raw { + use crate::c_types; + include!("bindings_gen.rs"); +} +pub use bindings_raw::*; + +pub const GFP_KERNEL: gfp_t = BINDINGS_GFP_KERNEL; diff --git a/rust/kernel/src/bindings_helper.h b/rust/kernel/src/bindings_helper.h new file mode 100644 index 000000000000..b5b0b2e1d18d --- /dev/null +++ b/rust/kernel/src/bindings_helper.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/version.h> + +// bindgen gets confused at certain things +const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; diff --git a/rust/kernel/src/c_types.rs b/rust/kernel/src/c_types.rs new file mode 100644 index 000000000000..35776920c99d --- /dev/null +++ b/rust/kernel/src/c_types.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![allow(non_camel_case_types)] + +#[cfg(target_arch = "x86_64")] +mod c { + use core::ffi; + + pub type c_int = i32; + pub type c_char = i8; + pub type c_long = i64; + pub type c_longlong = i64; + pub type c_short = i16; + pub type c_uchar = u8; + pub type c_uint = u32; + pub type c_ulong = u64; + pub type c_ulonglong = u64; + pub type c_ushort = u16; + pub type c_schar = i8; + pub type c_size_t = usize; + pub type c_ssize_t = isize; + pub type c_void = ffi::c_void; +} + +pub use c::*; diff --git a/rust/kernel/src/chrdev.rs b/rust/kernel/src/chrdev.rs new file mode 100644 index 000000000000..4606a7bd5a73 --- /dev/null +++ b/rust/kernel/src/chrdev.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::TryInto; +use core::mem; +use core::ops::Range; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use crate::bindings; +use crate::c_types; +use crate::error::{Error, KernelResult}; +use crate::file_operations; +use crate::types::CStr; + +pub fn builder(name: &'static CStr, minors: Range<u16>) -> KernelResult<Builder> { + Ok(Builder { + name, + minors, + file_ops: vec![], + }) +} + +pub struct Builder { + name: &'static CStr, + minors: Range<u16>, + file_ops: Vec<&'static bindings::file_operations>, +} + +impl Builder { + pub fn register_device<T: file_operations::FileOperations>(mut self) -> Builder { + if self.file_ops.len() >= self.minors.len() { + panic!("More devices registered than minor numbers allocated.") + } + self.file_ops + .push(&file_operations::FileOperationsVtable::<T>::VTABLE); + self + } + + pub fn build(self) -> KernelResult<Registration> { + let mut dev: bindings::dev_t = 0; + let res = unsafe { + bindings::alloc_chrdev_region( + &mut dev, + self.minors.start.into(), + self.minors.len().try_into()?, + self.name.as_ptr() as *const c_types::c_char, + ) + }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + + // Turn this into a boxed slice immediately because the kernel stores pointers into it, and + // so that data should never be moved. + let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice(); + for (i, file_op) in self.file_ops.iter().enumerate() { + unsafe { + bindings::cdev_init(&mut cdevs[i], *file_op); + // TODO: proper `THIS_MODULE` handling + cdevs[i].owner = core::ptr::null_mut(); + let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1); + if rc != 0 { + // Clean up the ones that were allocated. + for j in 0..=i { + bindings::cdev_del(&mut cdevs[j]); + } + bindings::unregister_chrdev_region(dev, self.minors.len() as _); + return Err(Error::from_kernel_errno(rc)); + } + } + } + + Ok(Registration { + dev, + count: self.minors.len(), + cdevs, + }) + } +} + +pub struct Registration { + dev: bindings::dev_t, + count: usize, + cdevs: Box<[bindings::cdev]>, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl Sync for Registration {} + +impl Drop for Registration { + fn drop(&mut self) { + unsafe { + for dev in self.cdevs.iter_mut() { + bindings::cdev_del(dev); + } + bindings::unregister_chrdev_region(self.dev, self.count as _); + } + } +} diff --git a/rust/kernel/src/error.rs b/rust/kernel/src/error.rs new file mode 100644 index 000000000000..95e322e39a88 --- /dev/null +++ b/rust/kernel/src/error.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::num::TryFromIntError; + +use crate::bindings; +use crate::c_types; + +pub struct Error(c_types::c_int); + +impl Error { + pub const EINVAL: Self = Error(-(bindings::EINVAL as i32)); + pub const ENOMEM: Self = Error(-(bindings::ENOMEM as i32)); + pub const EFAULT: Self = Error(-(bindings::EFAULT as i32)); + pub const ESPIPE: Self = Error(-(bindings::ESPIPE as i32)); + pub const EAGAIN: Self = Error(-(bindings::EAGAIN as i32)); + + pub fn from_kernel_errno(errno: c_types::c_int) -> Error { + Error(errno) + } + + pub fn to_kernel_errno(&self) -> c_types::c_int { + self.0 + } +} + +impl From<TryFromIntError> for Error { + fn from(_: TryFromIntError) -> Error { + Error::EINVAL + } +} + +pub type KernelResult<T> = Result<T, Error>; 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; +} diff --git a/rust/kernel/src/filesystem.rs b/rust/kernel/src/filesystem.rs new file mode 100644 index 000000000000..a5b7774292f1 --- /dev/null +++ b/rust/kernel/src/filesystem.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 + +// TODO: `filesystem` is really incomplete -- deletion to be considered + +use alloc::boxed::Box; +use core::default::Default; +use core::marker; + +use crate::bindings; +use crate::c_types; +use crate::error; +use crate::types::CStr; + +pub struct Registration<T: FileSystem> { + _phantom: marker::PhantomData<T>, + ptr: Box<bindings::file_system_type>, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl<T> Sync for Registration<T> where T: FileSystem {} + +impl<T: FileSystem> Drop for Registration<T> { + fn drop(&mut self) { + unsafe { bindings::unregister_filesystem(&mut *self.ptr) }; + } +} + +pub trait FileSystem: Sync { + const NAME: &'static CStr; + const FLAGS: FileSystemFlags; +} + +bitflags::bitflags! { + pub struct FileSystemFlags: c_types::c_int { + const REQUIRES_DEV = bindings::FS_REQUIRES_DEV as c_types::c_int; + const BINARY_MOUNTDATA = bindings::FS_BINARY_MOUNTDATA as c_types::c_int; + const HAS_SUBTYPE = bindings::FS_HAS_SUBTYPE as c_types::c_int; + const USERNS_MOUNT = bindings::FS_USERNS_MOUNT as c_types::c_int; + const RENAME_DOES_D_MOVE = bindings::FS_RENAME_DOES_D_MOVE as c_types::c_int; + } +} + +extern "C" fn fill_super_callback<T: FileSystem>( + _sb: *mut bindings::super_block, + _data: *mut c_types::c_void, + _silent: c_types::c_int, +) -> c_types::c_int { + // T::fill_super(...) + // This should actually create an object that gets dropped by + // file_system_registration::kill_sb. You can point to it with + // sb->s_fs_info. + unimplemented!(); +} + +extern "C" fn mount_callback<T: FileSystem>( + fs_type: *mut bindings::file_system_type, + flags: c_types::c_int, + _dev_name: *const c_types::c_char, + data: *mut c_types::c_void, +) -> *mut bindings::dentry { + unsafe { bindings::mount_nodev(fs_type, flags, data, Some(fill_super_callback::<T>)) } +} + +pub fn register<T: FileSystem>() -> error::KernelResult<Registration<T>> { + let mut fs_registration = Registration { + ptr: Box::new(bindings::file_system_type { + name: T::NAME.as_ptr() as *const i8, + // TODO: proper `THIS_MODULE` handling + owner: core::ptr::null_mut(), + fs_flags: T::FLAGS.bits(), + mount: Some(mount_callback::<T>), + kill_sb: Some(bindings::kill_litter_super), + + ..Default::default() + }), + _phantom: marker::PhantomData, + }; + let result = unsafe { bindings::register_filesystem(&mut *fs_registration.ptr) }; + if result != 0 { + return Err(error::Error::from_kernel_errno(result)); + } + + Ok(fs_registration) +} diff --git a/rust/kernel/src/lib.rs b/rust/kernel/src/lib.rs new file mode 100644 index 000000000000..18d553c094ff --- /dev/null +++ b/rust/kernel/src/lib.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The `kernel` crate + +#![no_std] +#![feature(allocator_api, alloc_error_handler, const_raw_ptr_deref)] + +extern crate alloc; + +use core::panic::PanicInfo; + +mod allocator; +pub mod bindings; +pub mod c_types; +pub mod chrdev; +mod error; +pub mod file_operations; +pub mod filesystem; +pub mod prelude; +pub mod printk; +pub mod random; +pub mod sysctl; +mod types; +pub mod user_ptr; + +pub use crate::error::{Error, KernelResult}; +pub use crate::types::{CStr, Mode}; + +/// Declares the entrypoint for a kernel module. The first argument should be a type which +/// implements the [`KernelModule`] trait. Also accepts various forms of kernel metadata. +/// +/// Example: +/// ```rust,no_run +/// use kernel::prelude::*; +/// +/// struct MyKernelModule; +/// impl KernelModule for MyKernelModule { +/// fn init() -> KernelResult<Self> { +/// Ok(MyKernelModule) +/// } +/// } +/// +/// kernel_module!( +/// MyKernelModule, +/// author: b"Rust for Linux Contributors", +/// description: b"My very own kernel module!", +/// license: b"GPL" +/// ); +#[macro_export] +macro_rules! kernel_module { + ($module:ty, $($name:ident : $value:expr),*) => { + static mut __MOD: Option<$module> = None; + + // Built-in modules are initialized through an initcall pointer + // + // TODO: should we compile a C file on the fly to avoid duplication? + #[cfg(not(MODULE))] + #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))] + #[link_section = ".initcall6.init"] + #[used] + pub static __initcall: extern "C" fn() -> $crate::c_types::c_int = init_module; + + #[cfg(not(MODULE))] + #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)] + global_asm!( + r#".section ".initcall6.init", "a" + __initcall: + .long init_module - . + .previous + "# + ); + + // TODO: pass the kernel module name here to generate a unique, + // helpful symbol name (the name would also useful for the `modinfo` + // issue below). + #[no_mangle] + pub extern "C" fn init_module() -> $crate::c_types::c_int { + match <$module as $crate::KernelModule>::init() { + Ok(m) => { + unsafe { + __MOD = Some(m); + } + return 0; + } + Err(e) => { + return e.to_kernel_errno(); + } + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + unsafe { + // Invokes drop() on __MOD, which should be used for cleanup. + __MOD = None; + } + } + + $( + $crate::kernel_module!(@attribute $name, $value); + )* + }; + + // TODO: The modinfo attributes below depend on the compiler placing + // the variables in order in the .modinfo section, so that you end up + // with b"key=value\0" in order in the section. This is a reasonably + // standard trick in C, but I'm not sure that rustc guarantees it. + // + // Ideally we'd be able to use concat_bytes! + stringify_bytes! + + // some way of turning a string literal (or at least a string + // literal token) into a bytes literal, and get a single static + // [u8; * N] with the whole thing, but those don't really exist yet. + // Most of the alternatives (e.g. .as_bytes() as a const fn) give + // you a pointer, not an array, which isn't right. + + // TODO: `modules.builtin.modinfo` etc. is missing the prefix (module name) + (@attribute author, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_KEY: [u8; 7] = *b"author="; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute description, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_KEY: [u8; 12] = *b"description="; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute license, $value:expr) => { + #[link_section = ".modinfo"] + #[used] |