diff options
Diffstat (limited to 'samples')
-rw-r--r-- | samples/rust/Kconfig | 136 | ||||
-rw-r--r-- | samples/rust/Makefile | 14 | ||||
-rw-r--r-- | samples/rust/rust_chrdev.rs | 49 | ||||
-rw-r--r-- | samples/rust/rust_echo_server.rs | 60 | ||||
-rw-r--r-- | samples/rust/rust_fs.rs | 59 | ||||
-rw-r--r-- | samples/rust/rust_minimal.rs | 10 | ||||
-rw-r--r-- | samples/rust/rust_miscdev.rs | 142 | ||||
-rw-r--r-- | samples/rust/rust_module_parameters.rs | 69 | ||||
-rw-r--r-- | samples/rust/rust_netfilter.rs | 54 | ||||
-rw-r--r-- | samples/rust/rust_platform.rs | 22 | ||||
-rw-r--r-- | samples/rust/rust_print.rs | 54 | ||||
-rw-r--r-- | samples/rust/rust_random.rs | 60 | ||||
-rw-r--r-- | samples/rust/rust_selftests.rs | 99 | ||||
-rw-r--r-- | samples/rust/rust_semaphore.rs | 170 | ||||
-rw-r--r-- | samples/rust/rust_semaphore_c.c | 212 | ||||
-rw-r--r-- | samples/rust/rust_stack_probing.rs | 36 | ||||
-rw-r--r-- | samples/rust/rust_sync.rs | 93 |
17 files changed, 1334 insertions, 5 deletions
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 841e0906e943..189c10ced6d4 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -20,6 +20,135 @@ config SAMPLE_RUST_MINIMAL If unsure, say N. +config SAMPLE_RUST_PRINT + tristate "Printing macros" + help + This option builds the Rust printing macros sample. + + To compile this as a module, choose M here: + the module will be called rust_print. + + If unsure, say N. + +config SAMPLE_RUST_MODULE_PARAMETERS + tristate "Module parameters" + help + This option builds the Rust module parameters sample. + + To compile this as a module, choose M here: + the module will be called rust_module_parameters. + + If unsure, say N. + +config SAMPLE_RUST_SYNC + tristate "Synchronisation primitives" + help + This option builds the Rust synchronisation primitives sample. + + To compile this as a module, choose M here: + the module will be called rust_sync. + + If unsure, say N. + +config SAMPLE_RUST_CHRDEV + tristate "Character device" + help + This option builds the Rust character device sample. + + To compile this as a module, choose M here: + the module will be called rust_chrdev. + + If unsure, say N. + +config SAMPLE_RUST_MISCDEV + tristate "Miscellaneous device" + help + This option builds the Rust miscellaneous device sample. + + To compile this as a module, choose M here: + the module will be called rust_miscdev. + + If unsure, say N. + +config SAMPLE_RUST_STACK_PROBING + tristate "Stack probing" + help + This option builds the Rust stack probing sample. + + To compile this as a module, choose M here: + the module will be called rust_stack_probing. + + If unsure, say N. + +config SAMPLE_RUST_SEMAPHORE + tristate "Semaphore" + help + This option builds the Rust semaphore sample. + + To compile this as a module, choose M here: + the module will be called rust_semaphore. + + If unsure, say N. + +config SAMPLE_RUST_SEMAPHORE_C + tristate "Semaphore (in C, for comparison)" + help + This option builds the Rust semaphore sample (in C, for comparison). + + To compile this as a module, choose M here: + the module will be called rust_semaphore_c. + + If unsure, say N. + +config SAMPLE_RUST_RANDOM + tristate "Random" + help + This option builds the Rust random sample. + + To compile this as a module, choose M here: + the module will be called rust_random. + + If unsure, say N. + +config SAMPLE_RUST_PLATFORM + tristate "Platform device driver" + help + This option builds the Rust platform device driver sample. + + To compile this as a module, choose M here: + the module will be called rust_platform. + +config SAMPLE_RUST_FS + tristate "File system" + help + This option builds the Rust file system sample. + + To compile this as a module, choose M here: + the module will be called rust_fs. + + If unsure, say N. + +config SAMPLE_RUST_NETFILTER + tristate "Network filter module" + depends on NETFILTER + help + This option builds the Rust netfilter module sample. + + To compile this as a module, choose M here: + the module will be called rust_netfilter. + + If unsure, say N. + +config SAMPLE_RUST_ECHO_SERVER + tristate "Echo server module" + help + This option builds the Rust echo server module sample. + + To compile this as a module, choose M here: + the module will be called rust_echo_server. + + If unsure, say N. + config SAMPLE_RUST_HOSTPROGS bool "Host programs" help @@ -27,4 +156,11 @@ config SAMPLE_RUST_HOSTPROGS If unsure, say N. +config SAMPLE_RUST_SELFTESTS + tristate "Self tests" + help + This option builds the self test cases for Rust. + + If unsure, say N. + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 1daba5f8658a..420bcefeb082 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -1,5 +1,19 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o +obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o +obj-$(CONFIG_SAMPLE_RUST_MODULE_PARAMETERS) += rust_module_parameters.o +obj-$(CONFIG_SAMPLE_RUST_SYNC) += rust_sync.o +obj-$(CONFIG_SAMPLE_RUST_CHRDEV) += rust_chrdev.o +obj-$(CONFIG_SAMPLE_RUST_MISCDEV) += rust_miscdev.o +obj-$(CONFIG_SAMPLE_RUST_STACK_PROBING) += rust_stack_probing.o +obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o +obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o +obj-$(CONFIG_SAMPLE_RUST_RANDOM) += rust_random.o +obj-$(CONFIG_SAMPLE_RUST_PLATFORM) += rust_platform.o +obj-$(CONFIG_SAMPLE_RUST_NETFILTER) += rust_netfilter.o +obj-$(CONFIG_SAMPLE_RUST_ECHO_SERVER) += rust_echo_server.o +obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o +obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_chrdev.rs b/samples/rust/rust_chrdev.rs new file mode 100644 index 000000000000..d87557bff275 --- /dev/null +++ b/samples/rust/rust_chrdev.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust character device sample. + +use kernel::prelude::*; +use kernel::{chrdev, file}; + +module! { + type: RustChrdev, + name: "rust_chrdev", + author: "Rust for Linux Contributors", + description: "Rust character device sample", + license: "GPL", +} + +struct RustFile; + +#[vtable] +impl file::Operations for RustFile { + fn open(_shared: &(), _file: &file::File) -> Result { + Ok(()) + } +} + +struct RustChrdev { + _dev: Pin<Box<chrdev::Registration<2>>>, +} + +impl kernel::Module for RustChrdev { + fn init(name: &'static CStr, module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust character device sample (init)\n"); + + let mut chrdev_reg = chrdev::Registration::new_pinned(name, 0, module)?; + + // Register the same kind of device twice, we're just demonstrating + // that you can use multiple minors. There are two minors in this case + // because its type is `chrdev::Registration<2>` + chrdev_reg.as_mut().register::<RustFile>()?; + chrdev_reg.as_mut().register::<RustFile>()?; + + Ok(RustChrdev { _dev: chrdev_reg }) + } +} + +impl Drop for RustChrdev { + fn drop(&mut self) { + pr_info!("Rust character device sample (exit)\n"); + } +} diff --git a/samples/rust/rust_echo_server.rs b/samples/rust/rust_echo_server.rs new file mode 100644 index 000000000000..e57f3dd9cd64 --- /dev/null +++ b/samples/rust/rust_echo_server.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust echo server sample. + +use kernel::{ + kasync::executor::{workqueue::Executor as WqExecutor, AutoStopHandle, Executor}, + kasync::net::{TcpListener, TcpStream}, + net::{self, Ipv4Addr, SocketAddr, SocketAddrV4}, + prelude::*, + spawn_task, + sync::{Arc, ArcBorrow}, +}; + +async fn echo_server(stream: TcpStream) -> Result { + let mut buf = [0u8; 1024]; + loop { + let n = stream.read(&mut buf).await?; + if n == 0 { + return Ok(()); + } + stream.write_all(&buf[..n]).await?; + } +} + +async fn accept_loop(listener: TcpListener, executor: Arc<impl Executor>) { + loop { + if let Ok(stream) = listener.accept().await { + let _ = spawn_task!(executor.as_arc_borrow(), echo_server(stream)); + } + } +} + +fn start_listener(ex: ArcBorrow<'_, impl Executor + Send + Sync + 'static>) -> Result { + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::ANY, 8080)); + let listener = TcpListener::try_new(net::init_ns(), &addr)?; + spawn_task!(ex, accept_loop(listener, ex.into()))?; + Ok(()) +} + +struct RustEchoServer { + _handle: AutoStopHandle<dyn Executor>, +} + +impl kernel::Module for RustEchoServer { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + let handle = WqExecutor::try_new(kernel::workqueue::system())?; + start_listener(handle.executor())?; + Ok(Self { + _handle: handle.into(), + }) + } +} + +module! { + type: RustEchoServer, + name: "rust_echo_server", + author: "Rust for Linux Contributors", + description: "Rust tcp echo sample", + license: "GPL v2", +} diff --git a/samples/rust/rust_fs.rs b/samples/rust/rust_fs.rs new file mode 100644 index 000000000000..064ead97dd98 --- /dev/null +++ b/samples/rust/rust_fs.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust file system sample. + +use kernel::prelude::*; +use kernel::{c_str, fs}; + +module_fs! { + type: RustFs, + name: "rust_fs", + author: "Rust for Linux Contributors", + license: "GPL", +} + +struct RustFs; + +#[vtable] +impl fs::Context<Self> for RustFs { + type Data = (); + + kernel::define_fs_params! {(), + {flag, "flag", |_, v| { pr_info!("flag passed-in: {v}\n"); Ok(()) } }, + {flag_no, "flagno", |_, v| { pr_info!("flagno passed-in: {v}\n"); Ok(()) } }, + {bool, "bool", |_, v| { pr_info!("bool passed-in: {v}\n"); Ok(()) } }, + {u32, "u32", |_, v| { pr_info!("u32 passed-in: {v}\n"); Ok(()) } }, + {u32oct, "u32oct", |_, v| { pr_info!("u32oct passed-in: {v}\n"); Ok(()) } }, + {u32hex, "u32hex", |_, v| { pr_info!("u32hex passed-in: {v}\n"); Ok(()) } }, + {s32, "s32", |_, v| { pr_info!("s32 passed-in: {v}\n"); Ok(()) } }, + {u64, "u64", |_, v| { pr_info!("u64 passed-in: {v}\n"); Ok(()) } }, + {string, "string", |_, v| { pr_info!("string passed-in: {v}\n"); Ok(()) } }, + {enum, "enum", [("first", 10), ("second", 20)], |_, v| { + pr_info!("enum passed-in: {v}\n"); Ok(()) } + }, + } + + fn try_new() -> Result { + pr_info!("context created!\n"); + Ok(()) + } +} + +impl fs::Type for RustFs { + type Context = Self; + const SUPER_TYPE: fs::Super = fs::Super::Independent; + const NAME: &'static CStr = c_str!("rustfs"); + const FLAGS: i32 = fs::flags::USERNS_MOUNT; + + fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBlock<Self>> { + let sb = sb.init( + (), + &fs::SuperParams { + magic: 0x72757374, + ..fs::SuperParams::DEFAULT + }, + )?; + let sb = sb.init_root()?; + Ok(sb) + } +} diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs index 54ad17685742..54de32b78cec 100644 --- a/samples/rust/rust_minimal.rs +++ b/samples/rust/rust_minimal.rs @@ -6,10 +6,10 @@ use kernel::prelude::*; module! { type: RustMinimal, - name: b"rust_minimal", - author: b"Rust for Linux Contributors", - description: b"Rust minimal sample", - license: b"GPL", + name: "rust_minimal", + author: "Rust for Linux Contributors", + description: "Rust minimal sample", + license: "GPL", } struct RustMinimal { @@ -17,7 +17,7 @@ struct RustMinimal { } impl kernel::Module for RustMinimal { - fn init(_module: &'static ThisModule) -> Result<Self> { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { pr_info!("Rust minimal sample (init)\n"); pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); diff --git a/samples/rust/rust_miscdev.rs b/samples/rust/rust_miscdev.rs new file mode 100644 index 000000000000..24766b2040bd --- /dev/null +++ b/samples/rust/rust_miscdev.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust miscellaneous device sample. + +use kernel::prelude::*; +use kernel::{ + file::{self, File}, + io_buffer::{IoBufferReader, IoBufferWriter}, + miscdev, + sync::{Arc, ArcBorrow, CondVar, Mutex, UniqueArc}, +}; + +module! { + type: RustMiscdev, + name: "rust_miscdev", + author: "Rust for Linux Contributors", + description: "Rust miscellaneous device sample", + license: "GPL", +} + +const MAX_TOKENS: usize = 3; + +struct SharedStateInner { + token_count: usize, +} + +struct SharedState { + state_changed: CondVar, + inner: Mutex<SharedStateInner>, +} + +impl SharedState { + fn try_new() -> Result<Arc<Self>> { + let mut state = Pin::from(UniqueArc::try_new(Self { + // SAFETY: `condvar_init!` is called below. + state_changed: unsafe { CondVar::new() }, + // SAFETY: `mutex_init!` is called below. + inner: unsafe { Mutex::new(SharedStateInner { token_count: 0 }) }, + })?); + + // SAFETY: `state_changed` is pinned when `state` is. + let pinned = unsafe { state.as_mut().map_unchecked_mut(|s| &mut s.state_changed) }; + kernel::condvar_init!(pinned, "SharedState::state_changed"); + + // SAFETY: `inner` is pinned when `state` is. + let pinned = unsafe { state.as_mut().map_unchecked_mut(|s| &mut s.inner) }; + kernel::mutex_init!(pinned, "SharedState::inner"); + + Ok(state.into()) + } +} + +struct Token; +#[vtable] +impl file::Operations for Token { + type Data = Arc<SharedState>; + type OpenData = Arc<SharedState>; + + fn open(shared: &Arc<SharedState>, _file: &File) -> Result<Self::Data> { + Ok(shared.clone()) + } + + fn read( + shared: ArcBorrow<'_, SharedState>, + _: &File, + data: &mut impl IoBufferWriter, + offset: u64, + ) -> Result<usize> { + // Succeed if the caller doesn't provide a buffer or if not at the start. + if data.is_empty() || offset != 0 { + return Ok(0); + } + + { + let mut inner = shared.inner.lock(); + + // Wait until we are allowed to decrement the token count or a signal arrives. + while inner.token_count == 0 { + if shared.state_changed.wait(&mut inner) { + return Err(EINTR); + } + } + + // Consume a token. + inner.token_count -= 1; + } + + // Notify a possible writer waiting. + shared.state_changed.notify_all(); + + // Write a one-byte 1 to the reader. + data.write_slice(&[1u8; 1])?; + Ok(1) + } + + fn write( + shared: ArcBorrow<'_, SharedState>, + _: &File, + data: &mut impl IoBufferReader, + _offset: u64, + ) -> Result<usize> { + { + let mut inner = shared.inner.lock(); + + // Wait until we are allowed to increment the token count or a signal arrives. + while inner.token_count == MAX_TOKENS { + if shared.state_changed.wait(&mut inner) { + return Err(EINTR); + } + } + + // Increment the number of token so that a reader can be released. + inner.token_count += 1; + } + + // Notify a possible reader waiting. + shared.state_changed.notify_all(); + Ok(data.len()) + } +} + +struct RustMiscdev { + _dev: Pin<Box<miscdev::Registration<Token>>>, +} + +impl kernel::Module for RustMiscdev { + fn init(name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust miscellaneous device sample (init)\n"); + + let state = SharedState::try_new()?; + + Ok(RustMiscdev { + _dev: miscdev::Registration::new_pinned(fmt!("{name}"), state)?, + }) + } +} + +impl Drop for RustMiscdev { + fn drop(&mut self) { + pr_info!("Rust miscellaneous device sample (exit)\n"); + } +} diff --git a/samples/rust/rust_module_parameters.rs b/samples/rust/rust_module_parameters.rs new file mode 100644 index 000000000000..557cba7b4815 --- /dev/null +++ b/samples/rust/rust_module_parameters.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust module parameters sample. + +use kernel::prelude::*; + +module! { + type: RustModuleParameters, + name: "rust_module_parameters", + author: "Rust for Linux Contributors", + description: "Rust module parameters sample", + license: "GPL", + params: { + my_bool: bool { + default: true, + permissions: 0, + description: "Example of bool", + }, + my_i32: i32 { + default: 42, + permissions: 0o644, + description: "Example of i32", + }, + my_str: str { + default: b"default str val", + permissions: 0o644, + description: "Example of a string param", + }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: "Example of usize", + }, + my_array: ArrayParam<i32, 3> { + default: [0, 1], + permissions: 0, + description: "Example of array", + }, + }, +} + +struct RustModuleParameters; + +impl kernel::Module for RustModuleParameters { + fn init(_name: &'static CStr, module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust module parameters sample (init)\n"); + + { + let lock = module.kernel_param_lock(); + pr_info!("Parameters:\n"); + pr_info!(" my_bool: {}\n", my_bool.read()); + pr_info!(" my_i32: {}\n", my_i32.read(&lock)); + pr_info!( + " my_str: {}\n", + core::str::from_utf8(my_str.read(&lock))? + ); + pr_info!(" my_usize: {}\n", my_usize.read(&lock)); + pr_info!(" my_array: {:?}\n", my_array.read()); + } + + Ok(RustModuleParameters) + } +} + +impl Drop for RustModuleParameters { + fn drop(&mut self) { + pr_info!("Rust module parameters sample (exit)\n"); + } +} diff --git a/samples/rust/rust_netfilter.rs b/samples/rust/rust_netfilter.rs new file mode 100644 index 000000000000..b59d54610f0a --- /dev/null +++ b/samples/rust/rust_netfilter.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust netfilter sample. + +use kernel::net; +use kernel::net::filter::{self as netfilter, inet, Disposition, Family}; +use kernel::prelude::*; + +module! { + type: RustNetfilter, + name: "rust_netfilter", + author: "Rust for Linux Contributors", + description: "Rust netfilter sample", + license: "GPL", +} + +struct RustNetfilter { + _in: Pin<Box<netfilter::Registration<Self>>>, + _out: Pin<Box<netfilter::Registration<Self>>>, +} + +impl netfilter::Filter for RustNetfilter { + fn filter(_: (), skb: &net::SkBuff) -> Disposition { + let data = skb.head_data(); + pr_info!( + "packet headlen={}, len={}, first bytes={:02x?}\n", + data.len(), + skb.len(), + &data[..core::cmp::min(10, data.len())] + ); + Disposition::Accept + } +} + +impl kernel::Module for RustNetfilter { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + Ok(Self { + _in: netfilter::Registration::new_pinned( + Family::INet(inet::Hook::PreRouting), + 0, + net::init_ns().into(), + None, + (), + )?, + _out: netfilter::Registration::new_pinned( + Family::INet(inet::Hook::PostRouting), + 0, + net::init_ns().into(), + None, + (), + )?, + }) + } +} diff --git a/samples/rust/rust_platform.rs b/samples/rust/rust_platform.rs new file mode 100644 index 000000000000..5a1e972b75d2 --- /dev/null +++ b/samples/rust/rust_platform.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust platform device driver sample. + +use kernel::{module_platform_driver, of, platform, prelude::*}; + +module_platform_driver! { + type: Driver, + name: "rust_platform", + license: "GPL", +} + +struct Driver; +impl platform::Driver for Driver { + kernel::define_of_id_table! {(), [ + (of::DeviceId::Compatible(b"rust,sample"), None), + ]} + + fn probe(_dev: &mut platform::Device, _id_info: Option<&Self::IdInfo>) -> Result { + Ok(()) + } +} diff --git a/samples/rust/rust_print.rs b/samples/rust/rust_print.rs new file mode 100644 index 000000000000..f3e06c78d0c3 --- /dev/null +++ b/samples/rust/rust_print.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust printing macros sample. + +use kernel::prelude::*; +use kernel::{pr_cont, str::CStr, ThisModule}; + +module! { + type: RustPrint, + name: "rust_print", + author: "Rust for Linux Contributors", + description: "Rust printing macros sample", + license: "GPL", +} + +struct RustPrint; + +impl kernel::Module for RustPrint { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust printing macros sample (init)\n"); + + pr_emerg!("Emergency message (level 0) without args\n"); + pr_alert!("Alert message (level 1) without args\n"); + pr_crit!("Critical message (level 2) without args\n"); + pr_err!("Error message (level 3) without args\n"); + pr_warn!("Warning message (level 4) without args\n"); + pr_notice!("Notice message (level 5) without args\n"); + pr_info!("Info message (level 6) without args\n"); + + pr_info!("A line that"); + pr_cont!(" is continued"); + pr_cont!(" without args\n"); + + pr_emerg!("{} message (level {}) with args\n", "Emergency", 0); + pr_alert!("{} message (level {}) with args\n", "Alert", 1); + pr_crit!("{} message (level {}) with args\n", "Critical", 2); + pr_err!("{} message (level {}) with args\n", "Error", 3); + pr_warn!("{} message (level {}) with args\n", "Warning", 4); + pr_notice!("{} message (level {}) with args\n", "Notice", 5); + pr_info!("{} message (level {}) with args\n", "Info", 6); + + pr_info!("A {} that", "line"); + pr_cont!(" is {}", "continued"); + pr_cont!(" with {}\n", "args"); + + Ok(RustPrint) + } +} + +impl Drop for RustPrint { + fn drop(&mut self) { + pr_info!("Rust printing macros sample (exit)\n"); + } +} diff --git a/samples/rust/rust_random.rs b/samples/rust/rust_random.rs new file mode 100644 index 000000000000..222d1170a1bc --- /dev/null +++ b/samples/rust/rust_random.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust random device. +//! +//! Adapted from Alex Gaynor's original available at +//! <https://github.com/alex/just-use/blob/master/src/lib.rs>. + +use kernel::{ + file::{self, File}, + io_buffer::{IoBufferReader, IoBufferWriter}, + prelude::*, +}; + +module_misc_device! { + type: RandomFile, + name: "rust_random", + author: "Rust for Linux Contributors", + description: "Just use /dev/urandom: Now with early-boot safety", + license: "GPL", +} + +struct RandomFile; + +#[vtable] +impl file::Operations for RandomFile { + fn open(_data: &(), _file: &File) -> Result { + Ok(()) + } + + fn read(_this: (), file: &File, buf: &mut impl IoBufferWriter, _: u64) -> Result<usize> { + let total_len = buf.len(); + let mut chunkbuf = [0; 256]; + + while !buf.is_empty() { + let len = chunkbuf.len().min(buf.len()); + let chunk = &mut chunkbuf[0..len]; + let blocking = (file.flags() & file::flags::O_NONBLOCK) == 0; + + if blocking { + kernel::random::getrandom(chunk)?; + } else { + kernel::random::getrandom_nonblock(chunk)?; + } + buf.write_slice(chunk)?; + } + Ok(total_len) + } + + fn write(_this: (), _file: &File, buf: &mut impl IoBufferReader, _: u64) -> Result<usize> { + let total_len = buf.len(); + let mut chunkbuf = [0; 256]; + while !buf.is_empty() { + let len = chunkbuf.len().min(buf.len()); + let chunk = &mut chunkbuf[0..len]; + buf.read_slice(chunk)?; + kernel::random::add_randomness(chunk); + } + Ok(total_len) + } +} diff --git a/samples/rust/rust_selftests.rs b/samples/rust/rust_selftests.rs new file mode 100644 index 000000000000..91341cbf9e78 --- /dev/null +++ b/samples/rust/rust_selftests.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Self test cases for Rust. + +use kernel::prelude::*; +// Keep the `use` for a test in its test function. Module-level `use`s are only for the test +// framework. + +module! { + type: RustSelftests, + name: "rust_selftests", + author: "Rust for Linux Contributors", + description: "Self test cases for Rust", + license: "GPL", +} + +struct RustSelftests; + +/// A summary of testing. +/// +/// A test can +/// +/// * pass (successfully), or +/// * fail (without hitting any error), or +/// * hit an error (interrupted). +/// +/// This is the type that differentiates the first two (pass and fail) cases. +/// +/// When a test hits an error, the test function should skip and return the error. Note that this +/// doesn't mean the test fails, for example if the system doesn't have enough memory for +/// testing, the test function may return an `Err(ENOMEM)` and skip. +#[allow(dead_code)] +enum TestSummary { + Pass, + Fail, +} + +use TestSummary::Fail; +use TestSummary::Pass; + +macro_rules! do_tests { + ($($name:ident),*) => { + let mut total = 0; + let mut pass = 0; + let mut fail = 0; + + $({ + total += 1; + + match $name() { + Ok(Pass) => { + pass += 1; + pr_info!("{} passed!", stringify!($name)); + }, + Ok(Fail) => { + fail += 1; + pr_info!("{} failed!", stringify!($name)); + }, + Err(err) => { + pr_info!("{} hit error {:?}", stringify!($name), err); + } + } + })* + + pr_info!("{} tests run, {} passed, {} failed, {} hit errors\n", + total, pass, fail, total - pass - fail); + + if total == pass { + pr_info!("All tests passed. Congratulations!\n"); + } + } +} + +/// An example of test. +#[allow(dead_code)] +fn test_example() -> Result<TestSummary> { + // `use` declarations for the test can be put here, e.g. `use foo::bar;`. + + // Always pass. + Ok(Pass) +} + +impl kernel::Module for RustSelftests { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust self tests (init)\n"); + + do_tests! { + test_example // TODO: Remove when there is at least a real test. + }; + + Ok(RustSelftests) + } +} + +impl Drop for RustSelftests { + fn drop(&mut self) { + pr_info!("Rust self tests (exit)\n"); + } +} diff --git a/samples/rust/rust_semaphore.rs b/samples/rust/rust_semaphore.rs new file mode 100644 index 000000000000..379494614032 --- /dev/null +++ b/samples/rust/rust_semaphore.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust semaphore sample. +//! +//! A counting semaphore that can be used by userspace. +//! +//! The count is incremented by writes to the device. A write of `n` bytes results in an increment +//! of `n`. It is decremented by reads; each read results in the count being decremented by 1. If +//! the count is already zero, a read will block until another write increments it. +//! +//! This can be used in user space from the shell for example as follows (assuming a node called +//! `semaphore`): `cat semaphore` decrements the count by 1 (waiting for it to become non-zero +//! before decrementing); `echo -n 123 > semaphore` increments the semaphore by 3, potentially +//! unblocking up to 3 blocked readers. + +use core::sync::atomic::{AtomicU64, Ordering}; +use kernel::{ + condvar_init, + file::{self, File, IoctlCommand, IoctlHandler}, + io_buffer::{IoBufferReader, IoBufferWriter}, + miscdev::Registration, + mutex_init, + prelude::*, + sync::{Arc, CondVar, Mutex, UniqueArc}, + user_ptr::{UserSlicePtrReader, UserSlicePtrWriter}, +}; + +module! { + type: RustSemaphore, + name: "rust_semaphore", + author: "Rust for Linux Contributors", + description: "Rust semaphore sample", + license: "GPL", +} + +struct SemaphoreInner { + count: usize, + max_seen: usize, +} + +struct Semaphore { + changed: CondVar, + inner: Mutex<SemaphoreInner>, +} + +struct FileState { + read_count: AtomicU64, + shared: Arc<Semaphore>, +} + +impl FileState { + fn consume(&self) -> Result { + let mut inner = self.shared.inner.lock(); + while inner.count == 0 { + if self.shared.changed.wait(&mut inner) { + return Err(EINTR); + } + } + inner.count -= 1; + Ok(()) + } +} + +#[vtable] +impl file::Operations for FileState { + type Data = Box<Self>; + type OpenData = Arc<Semaphore>; + + fn open(shared: &Arc<Semaphore>, _file: &File) -> Result<Box<Self>> { + Ok(Box::try_new(Self { + read_count: AtomicU64::new(0), + shared: shared.clone(), + })?) + } + + fn read(this: &Self, _: &File, data: &mut impl IoBufferWriter, offset: u64) -> Result<usize> { + if data.is_empty() || offset > 0 { + return Ok(0); + } + this.consume()?; + data.write_slice(&[0u8; 1])?; + this.read_count.fetch_add(1, Ordering::Relaxed); + Ok(1) + } + + fn write(this: &Self, _: &File, data: &mut impl IoBufferReader, _offs: u64) -> Result<usize> { + { + let mut inner = this.shared.inner.lock(); + inner.count = inner.count.saturating_add(data.len()); + if inner.count > inner.max_seen { + inner.max_seen = inner.count; + } + } + + this.shared.changed.notify_all(); + Ok(data.len()) + } + + fn ioctl(this: &Self, file: &File, cmd: &mut IoctlCommand) -> Result<i32> { + cmd.dispatch::<Self>(this, file) + } +} + +struct RustSemaphore { + _dev: Pin<Box<Registration<FileState>>>, +} + +impl kernel::Module for RustSemaphore { + fn init(name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust semaphore sample (init)\n"); + + let mut sema = Pin::from(UniqueArc::try_new(Semaphore { + // SAFETY: `condvar_init!` is called below. + changed: unsafe { CondVar::new() }, + + // SAFETY: `mutex_init!` is called below. + inner: unsafe { + Mutex::new(SemaphoreInner { + count: 0, + max_seen: 0, + }) + }, + })?); + + // SAFETY: `changed` is pinned when `sema` is. + let pinned = unsafe { sema.as_mut().map_unchecked_mut(|s| &mut s.changed) }; + condvar_init!(pinned, "Semaphore::changed"); + + // SAFETY: `inner` is pinned when `sema` is. + let pinned = unsafe { sema.as_mut().map_unchecked_mut(|s| &mut s.inner) }; + mutex_init!(pinned, "Semaphore::inner"); + + Ok(Self { + _dev: Registration::new_pinned(fmt!("{name}"), sema.into())?, + }) + } +} + +impl Drop for RustSemaphore { + fn drop(&mut self) { + pr_info!("Rust semaphore sample (exit)\n"); + } +} + +const IOCTL_GET_READ_COUNT: u32 = 0x80086301; +const IOCTL_SET_READ_COUNT: u32 = 0x40086301; + +impl IoctlHandler for FileState { + type Target<'a> = &'a Self; + + fn read(this: &Self, _: &File, cmd: u32, writer: &mut UserSlicePtrWriter) -> Result<i32> { + match cmd { + IOCTL_GET_READ_COUNT => { + writer.write(&this.read_count.load(Ordering::Relaxed))?; + Ok(0) + } + _ => Err(EINVAL), + } + } + + fn write(this: &Self, _: &File, cmd: u32, reader: &mut UserSlicePtrReader) -> Result<i32> { + match cmd { + IOCTL_SET_READ_COUNT => { + this.read_count.store(reader.read()?, Ordering::Relaxed); + Ok(0) + } + _ => Err(EINVAL), + } + } +} diff --git a/samples/rust/rust_semaphore_c.c b/samples/rust/rust_semaphore_c.c new file mode 100644 index 000000000000..7672b0b4c105 --- /dev/null +++ b/samples/rust/rust_semaphore_c.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rust semaphore sample (in C, for comparison) + * + * This is a C implementation of `rust_semaphore.rs`. Refer to the description + * in that file for details on the device. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/refcount.h> +#include <linux/wait.h> + +#define IOCTL_GET_READ_COUNT _IOR('c', 1, u64) +#define IOCTL_SET_READ_COUNT _IOW('c', 1, u64) + +struct semaphore_state { + struct kref ref; + struct miscdevice miscdev; + wait_queue_head_t changed; + struct mutex mutex; + size_t count; + size_t max_seen; +}; + +struct file_state { + atomic64_t read_count; + struct semaphore_state *shared; +}; + +static int semaphore_consume(struct semaphore_state *state) +{ + DEFINE_WAIT(wait); + + mutex_lock(&state->mutex); + while (state->count == 0) { + prepare_to_wait(&state->changed, &wait, TASK_INTERRUPTIBLE); + mutex_unlock(&state->mutex); + schedule(); + finish_wait(&state->changed, &wait); + if (signal_pending(current)) + return -EINTR; + mutex_lock(&state->mutex); + } + + state->count--; + mutex_unlock(&state->mutex); + + return 0; +} + +static int semaphore_open(struct inode *nodp, struct file *filp) +{ + struct semaphore_state *shared = + container_of(filp->private_data, struct semaphore_state, miscdev); + struct file_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + kref_get(&shared->ref); + state->shared = shared; + atomic64_set(&state->read_count, 0); + + filp->private_data = state; + + return 0; +} + +static ssize_t semaphore_write(struct file *filp, const char __user *buffer, size_t count, + loff_t *ppos) +{ + struct file_state *state = filp->private_data; + struct semaphore_state *shared = state->shared; + + mutex_lock(&shared->mutex); + + shared->count += count; + if (shared->count < count) + shared->count = SIZE_MAX; + + if (shared->count > shared->max_seen) + shared->max_seen = shared->count; + + mutex_unlock(&shared->mutex); + + wake_up_all(&shared->changed); + + return count; +} + +static ssize_t semaphore_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct file_state *state = filp->private_data; + char c = 0; + int ret; + + if (count == 0 || *ppos > 0) + return 0; + + ret = semaphore_consume(state->shared); + if (ret) + return ret; + + if (copy_to_user(buffer, &c, sizeof(c))) + return -EFAULT; + + atomic64_add(1, &state->read_count); + *ppos += 1; + return 1; +} + +static long semaphore_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct file_state *state = filp->private_data; + void __user *buffer = (void __user *)arg; + u64 value; + + switch (cmd) { + case IOCTL_GET_READ_COUNT: + value = atomic64_read(&state->read_count); + if (copy_to_user(buffer, &value, sizeof(value))) + return -EFAULT; + return 0; + case IOCTL_SET_READ_COUNT: + if (copy_from_user(&value, buffer, sizeof(value))) + return -EFAULT; + atomic64_set(&state->read_count, value); + return 0; + default: + return -EINVAL; + } +} + +static void semaphore_free(struct kref *kref) +{ + struct semaphore_state *device; + + device = container_of(kref, struct semaphore_state, ref); + kfree(device); +} + +static int semaphore_release(struct inode *nodp, struct file *filp) +{ + struct file_state *state = filp->private_data; + + kref_put(&state->shared->ref, semaphore_free); + kfree(state); + return 0; +} + +static const struct file_operations semaphore_fops = { + .owner = THIS_MODULE, + .open = semaphore_open, + .read = semaphore_read, + .write = semaphore_write, + .compat_ioctl = semaphore_ioctl, + .release = semaphore_release, +}; + +static struct semaphore_state *device; + +static int __init semaphore_init(void) +{ + int ret; + struct semaphore_state *state; + + pr_info("Rust semaphore sample (in C, for comparison) (init)\n"); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->mutex); + kref_init(&state->ref); + init_waitqueue_head(&state->changed); + + state->miscdev.fops = &semaphore_fops; + state->miscdev.minor = MISC_DYNAMIC_MINOR; + state->miscdev.name = "semaphore"; + + ret = misc_register(&state->miscdev); + if (ret < 0) { + kfree(state); + return ret; + } + + device = state; + + return 0; +} + +static void __exit semaphore_exit(void) +{ + pr_info("Rust semaphore sample (in C, for comparison) (exit)\n"); + + misc_deregister(&device->miscdev); + kref_put(&device->ref, semaphore_free); +} + +module_init(semaphore_init); +module_exit(semaphore_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rust for Linux Contributors"); +MODULE_DESCRIPTION("Rust semaphore sample (in C, for comparison)"); diff --git a/samples/rust/rust_stack_probing.rs b/samples/rust/rust_stack_probing.rs new file mode 100644 index 000000000000..f44f48cb36e7 --- /dev/null +++ b/samples/rust/rust_stack_probing.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust stack probing sample. + +use kernel::prelude::*; + +module! { + type: RustStackProbing, + name: "rust_stack_probing", + author: "Rust for Linux Contributors", + description: "Rust stack probing sample", + license: "GPL", +} + +struct RustStackProbing; + +impl kernel::Module for RustStackProbing { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust stack probing sample (init)\n"); + + // Including this large variable on the stack will trigger + // stack probing on the supported archs. + // This will verify that stack probing does not lead to + // any errors if we need to link `__rust_probestack`. + let x: [u64; 514] = core::hint::black_box([5; 514]); + pr_info!("Large array has length: {}\n", x.len()); + + Ok(RustStackProbing) + } +} + +impl Drop for RustStackProbing { + fn drop(&mut self) { + pr_info!("Rust stack probing sample (exit)\n"); + } +} diff --git a/samples/rust/rust_sync.rs b/samples/rust/rust_sync.rs new file mode 100644 index 000000000000..2e0714fcc3d6 --- /dev/null +++ b/samples/rust/rust_sync.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust synchronisation primitives sample. + +use kernel::prelude::*; +use kernel::{ + condvar_init, mutex_init, spinlock_init, + sync::{CondVar, Mutex, SpinLock}, +}; + +module! { + type: RustSync, + name: "rust_sync", + author: "Rust for Linux Contributors", + description: "Rust synchronisation primitives sample", + license: "GPL", +} + +kernel::init_static_sync! { + static SAMPLE_MUTEX: Mutex<u32> = 10; + static SAMPLE_CONDVAR: CondVar; +} + +struct RustSync; + +impl kernel::Module for RustSync { + fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> { + pr_info!("Rust synchronisation primitives sample (init)\n"); + + // Test mutexes. + { + // SAFETY: `init` is called below. + let mut data = Pin::from(Box::try_new(unsafe { Mutex::new(0) })?); + mutex_init!(data.as_mut(), "RustSync::init::data1"); + *data.lock() = 10; + pr_info!("Value: {}\n", *data.lock()); + + // SAFETY: `init` is called below. + let mut cv = Pin::from(Box::try_new(unsafe { CondVar::new() })?); + condvar_init!(cv.as_mut(), "RustSync::init::cv1"); + + { + let mut guard = data.lock(); + while *guard != 10 { + let _ = cv.wait(&mut guard); + } + } + cv.notify_one(); + cv.notify_all(); + cv.free_waiters(); + } + + // Test static mutex + condvar. + *SAMPLE_MUTEX.lock() = 20; + + { + let mut guard = SAMPLE_MUTEX.lock(); + while *guard != 20 { + let _ = SAMPLE_CONDVAR.wait(&mut guard); + } + } + + // Test spinlocks. + { + // SAFETY: `init` is called below. + let mut data = Pin::from(Box::try_new(unsafe { SpinLock::new(0) })?); + spinlock_init!(data.as_mut(), "RustSync::init::data2"); + *data.lock() = 10; + pr_info!("Value: {}\n", *data.lock()); + + // SAFETY: `init` is called below. + let mut cv = Pin::from(Box::try_new(unsafe { CondVar::new() })?); + condvar_init!(cv.as_mut(), "RustSync::init::cv2"); + { + let mut guard = data.lock(); + while *guard != 10 { + let _ = cv.wait(&mut guard); + } + } + cv.notify_one(); + cv.notify_all(); + cv.free_waiters(); + } + + Ok(RustSync) + } +} + +impl Drop for RustSync { + fn drop(&mut self) { + pr_info!("Rust synchronisation primitives sample (exit)\n"); + } +} |