summaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
authorMiguel Ojeda <ojeda@users.noreply.github.com>2020-09-12 15:37:03 +0200
committerGitHub <noreply@github.com>2020-09-12 15:37:03 +0200
commite280b81cab4477f0709e9f519c86944af44a913c (patch)
treec9ab0fd92d707e3f3006c9f0c032928b9c4a1ee8 /rust
parentd012a7190fc1fd72ed48911e77ca97ba4521bccd (diff)
parent0a99f235bd70ef6a8e5bed61e17f312b9839db56 (diff)
Merge pull request #4 from Rust-for-Linux/rust-initial
Initial Rust support
Diffstat (limited to 'rust')
-rw-r--r--rust/Makefile4
-rw-r--r--rust/helpers.c21
-rw-r--r--rust/kernel/Cargo.toml16
-rw-r--r--rust/kernel/build.rs133
-rw-r--r--rust/kernel/src/.gitignore3
-rw-r--r--rust/kernel/src/allocator.rs26
-rw-r--r--rust/kernel/src/bindings.rs16
-rw-r--r--rust/kernel/src/bindings_helper.h12
-rw-r--r--rust/kernel/src/c_types.rs25
-rw-r--r--rust/kernel/src/chrdev.rs101
-rw-r--r--rust/kernel/src/error.rs32
-rw-r--r--rust/kernel/src/file_operations.rs219
-rw-r--r--rust/kernel/src/filesystem.rs84
-rw-r--r--rust/kernel/src/lib.rs176
-rw-r--r--rust/kernel/src/prelude.rs16
-rw-r--r--rust/kernel/src/printk.rs72
-rw-r--r--rust/kernel/src/random.rs43
-rw-r--r--rust/kernel/src/sysctl.rs175
-rw-r--r--rust/kernel/src/types.rs54
-rw-r--r--rust/kernel/src/user_ptr.rs176
-rw-r--r--rust/shlex/Cargo.toml11
-rw-r--r--rust/shlex/src/lib.rs231
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]