summaryrefslogtreecommitdiffstats
path: root/rust/kernel/src
diff options
context:
space:
mode:
Diffstat (limited to 'rust/kernel/src')
-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.rs82
-rw-r--r--rust/kernel/src/lib.rs161
-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
16 files changed, 1213 insertions, 0 deletions
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..11b415008f68
--- /dev/null
+++ b/rust/kernel/src/filesystem.rs
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+
+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..bdb070cca953
--- /dev/null
+++ b/rust/kernel/src/lib.rs
@@ -0,0 +1,161 @@
+// 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;
+
+ // TODO: find a proper way to emulate the C macro, including
+ // dealing with `HAVE_ARCH_PREL32_RELOCATIONS`
+ #[cfg(not(module))]
+ #[link_section = ".initcall6.init"]
+ #[used]
+ pub static __initcall: extern "C" fn() -> $crate::c_types::c_int = init_module;
+
+ #[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]
+ pub static LICENSE_KEY: [u8; 8] = *b"license=";
+ #[link_section = ".modinfo"]
+ #[used]
+ pub static LICENSE_VALUE: [u8; $value.len()] = *$value;
+ #[link_section = ".modinfo"]
+ #[used]
+ pub static LICENSE_NUL: [u8; 1] = *b"\0";
+ };
+}
+
+/// KernelModule is the top level entrypoint to implementing a kernel module. Your kernel module
+/// should implement the `init` method on it, which maps to the `module_init` macro in Linux C API.
+/// You can use this method to do whatever setup or registration your module should do. For any
+/// teardown or cleanup operations, your type may implement [`Drop`].
+///
+/// [`Drop`]: https://doc.rust-lang.org/stable/core/ops/trait.Drop.html
+pub trait KernelModule: Sized + Sync {
+ fn init() -> KernelResult<Self>;
+}
+
+extern "C" {
+ fn rust_helper_BUG() -> !;
+}
+
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ unsafe {
+ rust_helper_BUG();
+ }
+}
+
+#[global_allocator]
+static ALLOCATOR: allocator::KernelAllocator = allocator::KernelAllocator;
diff --git a/rust/kernel/src/prelude.rs b/rust/kernel/src/prelude.rs
new file mode 100644
index 000000000000..d88baa99c902
--- /dev/null
+++ b/rust/kernel/src/prelude.rs
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! The `kernel` prelude
+
+pub use alloc::{
+ string::String,
+ borrow::ToOwned,
+};
+
+pub use super::{
+ kernel_module,
+ println,
+ KernelResult,
+ KernelModule,
+};
+
diff --git a/rust/kernel/src/printk.rs b/rust/kernel/src/printk.rs
new file mode 100644
index 000000000000..1a27c034258a
--- /dev/null
+++ b/rust/kernel/src/printk.rs
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::cmp;
+use core::fmt;
+
+use crate::bindings;
+use crate::c_types::c_int;
+
+#[doc(hidden)]
+pub fn printk(s: &[u8]) {
+ // Don't copy the trailing NUL from `KERN_INFO`.
+ let mut fmt_str = [0; bindings::KERN_INFO.len() - 1 + b"%.*s\0".len()];
+ fmt_str[..bindings::KERN_INFO.len() - 1]
+ .copy_from_slice(&bindings::KERN_INFO[..bindings::KERN_INFO.len() - 1]);
+ fmt_str[bindings::KERN_INFO.len() - 1..].copy_from_slice(b"%.*s\0");
+
+ // TODO: I believe printk never fails
+ unsafe { bindings::printk(fmt_str.as_ptr() as _, s.len() as c_int, s.as_ptr()) };
+}
+
+// From kernel/print/printk.c
+const LOG_LINE_MAX: usize = 1024 - 32;
+
+#[doc(hidden)]
+pub struct LogLineWriter {
+ data: [u8; LOG_LINE_MAX],
+ pos: usize,
+}
+
+#[allow(clippy::new_without_default)]
+impl LogLineWriter {
+ pub fn new() -> LogLineWriter {
+ LogLineWriter {
+ data: [0u8; LOG_LINE_MAX],
+ pos: 0,
+ }
+ }
+
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.data[..self.pos]
+ }
+}
+
+impl fmt::Write for LogLineWriter {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ let copy_len = cmp::min(LOG_LINE_MAX - self.pos, s.as_bytes().len());
+ self.data[self.pos..self.pos + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]);
+ self.pos += copy_len;
+ Ok(())
+ }
+}
+
+/// [`println!`] functions the same as it does in `std`, except instead of
+/// printing to `stdout`, it writes to the kernel console at the `KERN_INFO`
+/// level.
+///
+/// [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html
+#[macro_export]
+macro_rules! println {
+ () => ({
+ $crate::printk::printk("\n".as_bytes());
+ });
+ ($fmt:expr) => ({
+ $crate::printk::printk(concat!($fmt, "\n").as_bytes());
+ });
+ ($fmt:expr, $($arg:tt)*) => ({
+ use ::core::fmt;
+ let mut writer = $crate::printk::LogLineWriter::new();
+ let _ = fmt::write(&mut writer, format_args!(concat!($fmt, "\n"), $($arg)*)).unwrap();
+ $crate::printk::printk(writer.as_bytes());
+ });
+}
diff --git a/rust/kernel/src/random.rs b/rust/kernel/src/random.rs
new file mode 100644
index 000000000000..bd80c835749d
--- /dev/null
+++ b/rust/kernel/src/random.rs
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::convert::TryInto;
+
+use crate::{bindings, c_types, error};
+
+/// Fills `dest` with random bytes generated from the kernel's CSPRNG. Ensures
+/// that the CSPRNG has been seeded before generating any random bytes, and
+/// will block until it's ready.
+pub fn getrandom(dest: &mut [u8]) -> error::KernelResult<()> {
+ let res = unsafe { bindings::wait_for_random_bytes() };
+ if res != 0 {
+ return Err(error::Error::from_kernel_errno(res));
+ }
+
+ unsafe {
+ bindings::get_random_bytes(
+ dest.as_mut_ptr() as *mut c_types::c_void,
+ dest.len().try_into()?,
+ );
+ }
+ Ok(())
+}
+
+/// Fills `dest` with random bytes generated from the kernel's CSPRNG. If the
+/// CSPRNG is not yet seeded, returns an `Err(EAGAIN)` immediately.
+pub fn getrandom_nonblock(dest: &mut [u8]) -> error::KernelResult<()> {
+ if !unsafe { bindings::rng_is_initialized() } {
+ return Err(error::Error::EAGAIN);
+ }
+ getrandom(dest)
+}
+
+/// Contributes the contents of `data` to the kernel's entropy pool. Does _not_
+/// credit the kernel entropy counter though.
+pub fn add_randomness(data: &[u8]) {
+ unsafe {
+ bindings::add_device_randomness(
+ data.as_ptr() as *const c_types::c_void,
+ data.len().try_into().unwrap(),
+ );
+ }
+}
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.