diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3a45f818b8494c..e3f8e9e7fda2e1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -354,6 +354,10 @@ jobs: grep '] rust_sync: Value: 10$' qemu-stdout.log grep '] rust_sync: Rust synchronisation primitives sample (exit)$' qemu-stdout.log + - run: | + grep '] rust_thread: Rust thread APIs sample (init)$' qemu-stdout.log + grep '] rust_thread: Rust thread APIs sample (exit)$' qemu-stdout.log + - run: | grep '] rust_chrdev: Rust character device sample (init)$' qemu-stdout.log grep '] rust_chrdev: Rust character device sample (exit)$' qemu-stdout.log diff --git a/.github/workflows/kernel-arm-debug.config b/.github/workflows/kernel-arm-debug.config index 8f72fd45fde2d5..029438b03e45bc 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1774,6 +1774,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-arm-release.config b/.github/workflows/kernel-arm-release.config index afa7b02dd464e2..fa498ccfb76242 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1698,6 +1698,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index f7b4a6d37e8562..0040d623727401 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1426,6 +1426,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 49b53b9722e689..fae2562b69efd1 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1344,6 +1344,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index 5cee6896c4946b..1f884f9f85f77a 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1486,6 +1486,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-ppc64le-release.config b/.github/workflows/kernel-ppc64le-release.config index b7c076cb1ac793..3e1fae63696505 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1448,6 +1448,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-riscv64-debug.config b/.github/workflows/kernel-riscv64-debug.config index b4e3c4fcd424f9..5f009eba6f738c 100644 --- a/.github/workflows/kernel-riscv64-debug.config +++ b/.github/workflows/kernel-riscv64-debug.config @@ -1280,6 +1280,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-riscv64-release.config b/.github/workflows/kernel-riscv64-release.config index c0c43ade16999e..7fc06b02341bd9 100644 --- a/.github/workflows/kernel-riscv64-release.config +++ b/.github/workflows/kernel-riscv64-release.config @@ -1196,6 +1196,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-x86_64-debug.config b/.github/workflows/kernel-x86_64-debug.config index 05ae0d5af161dc..509717ad8c747f 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1438,6 +1438,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/kernel-x86_64-release.config b/.github/workflows/kernel-x86_64-release.config index f581b18e760500..5ddf6a7331552f 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1386,6 +1386,7 @@ CONFIG_SAMPLE_RUST_MINIMAL=m CONFIG_SAMPLE_RUST_PRINT=m CONFIG_SAMPLE_RUST_MODULE_PARAMETERS=m CONFIG_SAMPLE_RUST_SYNC=m +CONFIG_SAMPLE_RUST_THREAD=m CONFIG_SAMPLE_RUST_CHRDEV=m CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 078de0887c6dba..112b675bf06596 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -12,6 +12,9 @@ busybox rmmod rust_module_parameters.ko busybox insmod rust_sync.ko busybox rmmod rust_sync.ko +busybox insmod rust_thread.ko +busybox rmmod rust_thread.ko + busybox insmod rust_chrdev.ko busybox rmmod rust_chrdev.ko diff --git a/.github/workflows/qemu-initramfs.desc b/.github/workflows/qemu-initramfs.desc index 5fdcff849dc004..c449c2c3b6c700 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -9,6 +9,7 @@ file /rust_minimal.ko samples/rust/rust_minimal.ko 0755 file /rust_print.ko samples/rust/rust_print.ko 0755 0 0 file /rust_module_parameters.ko samples/rust/rust_module_parameters.ko 0755 0 0 file /rust_sync.ko samples/rust/rust_sync.ko 0755 0 0 +file /rust_thread.ko samples/rust/rust_thread.ko 0755 0 0 file /rust_chrdev.ko samples/rust/rust_chrdev.ko 0755 0 0 file /rust_miscdev.ko samples/rust/rust_miscdev.ko 0755 0 0 file /rust_stack_probing.ko samples/rust/rust_stack_probing.ko 0755 0 0 diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index b01169f7609f32..b20a03ae776424 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index ec370efb9cb7da..99148234849cc3 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -490,8 +490,6 @@ macro_rules! from_kernel_result { /// } /// } /// ``` -// TODO: remove `dead_code` marker once an in-kernel client is available. -#[allow(dead_code)] pub(crate) fn from_kernel_err_ptr(ptr: *mut T) -> Result<*mut T> { // CAST: casting a pointer to `*const c_types::c_void` is always valid. let const_ptr: *const c_types::c_void = ptr.cast(); diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index df5b8629fe0f9d..e8ccd6fa51f678 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -69,6 +69,7 @@ pub mod print; pub mod random; mod static_assert; pub mod sync; +pub mod thread; #[cfg(any(CONFIG_SYSCTL, doc))] #[doc(cfg(CONFIG_SYSCTL))] diff --git a/rust/kernel/task.rs b/rust/kernel/task.rs index 7a2aff0e921911..9321ff9e4cd4ad 100644 --- a/rust/kernel/task.rs +++ b/rust/kernel/task.rs @@ -108,6 +108,16 @@ impl Task { // SAFETY: By the type invariant, we know that `self.ptr` is non-null and valid. unsafe { bindings::signal_pending(self.ptr) != 0 } } + + /// Wakes up the task. + pub fn wake_up(&self) { + // SAFETY: By the type invariant, we know that `self.ptr` is non-null and valid. + // And `wake_up_process` is safe to be called for any valid task, even if the task is + // running. + unsafe { + bindings::wake_up_process(self.ptr); + } + } } impl PartialEq for Task { diff --git a/rust/kernel/thread.rs b/rust/kernel/thread.rs new file mode 100644 index 00000000000000..08f6fe6bb2664c --- /dev/null +++ b/rust/kernel/thread.rs @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! A kernel thread (kthread). +//! +//! This module allows Rust code to create/wakeup/stop a kernel thread. + +use crate::c_types; +use crate::error::{from_kernel_err_ptr, Error, Result}; +use crate::str::CStr; +use crate::task::{Task, TaskRef}; +use crate::{bindings, c_str}; + +use alloc::boxed::Box; +use core::ops::FnOnce; + +/// Raw kernel threads. +/// +/// The threads are created via C-style functions and machine-word sized +/// arguments. +/// +/// # Safety +/// +/// The creation of [`RawThread`] is unsafe because if a caller provides +/// an incorrect thread function or an incorrect thread argument, the +/// new thread will corrupt memory or do other unsafe behaviors. +pub struct RawThread { + task: Task, +} + +impl RawThread { + /// Creates a new thread using a C-style function pointer. + /// + /// # Safety + /// + /// This function actually doesn't dereference `arg` or call `f`, so even if + /// the users pass incorrect parameters this function won't run into + /// trouble. But if the users provide incorrect `arg` or `f` the new + /// thread will corrupt memory or do other unsafe behaviors, so + /// make it `unsafe`. + /// + /// The safety requirements of calling this function are: + /// + /// - Make sure `arg` is a proper pointer that points to a valid memory + /// location when the new thread begins to run. + /// + /// - Make sure `f` is a proper function pointer and `f` handles `arg` + /// correctly. + /// + /// # Context + /// + /// This function might sleep in `kthread_create_on_node` due to the memory + /// allocation and waiting for the completion, therefore do not call this + /// in atomic contexts (i.e. preemption-off contexts). + pub unsafe fn try_new( + name: &CStr, + f: unsafe extern "C" fn(*mut c_types::c_void) -> c_types::c_int, + arg: *mut c_types::c_void, + ) -> Result { + // SAFETY: `kthread_create_on_node` will copy the content of `name`, + // so we don't need to make the `name` live longer. + let task_ptr = from_kernel_err_ptr(unsafe { + bindings::kthread_create_on_node( + Some(f), + arg, + bindings::NUMA_NO_NODE, + c_str!("%s").as_ptr() as _, + name.as_ptr(), + ) + })?; + + // SAFETY: `task_ptr` is a valid pointer for a `task_struct` because + // we've checked with `from_kernel_err_ptr` above. + let task_ref = unsafe { TaskRef::from_ptr(task_ptr) }; + + // Increases the refcount of the task, so that it won't go away if it + // `do_exit`. + Ok(RawThread { + task: task_ref.clone(), + }) + } + + /// Wakes up the thread. + /// + /// Note that a newly created thread (via [`RawThread::try_new`]) will not + /// run until a [`RawThread::wake_up`] is called. + /// + /// # Context + /// + /// This function might sleep, don't call it in atomic contexts. + pub fn wake_up(&self) { + self.task.wake_up(); + } + + /// Stops the thread. + /// + /// - If the thread hasn't been waken up after creation, the thread function + /// won't be called, and this function will return `-EINTR`. Note that a + /// thread may not be waken up even after [`RawThread::wake_up`] is + /// called. + /// + /// - Otherwise, waits for the thread function to return or the thread + /// `do_exit` itself. + /// + /// Consumes the [`RawThread`] so that it's not accessible and return + /// the exit code of the thread. + /// + /// # Context + /// + /// This function might sleep, don't call it in atomic contexts. + pub fn stop(self) -> Result { + // SAFETY: `task.ptr` is a valid pointer to a kernel thread structure, + // the refcount of which is increased in `[RawThread::try_new`], so it + // won't point to a freed `task_struct`. And it's not stopped because + // `stop` will consume the [`RawThread`]. + let ret = unsafe { bindings::kthread_stop(self.task.ptr) }; + + if ret == 0 { + return Ok(()); + } + Err(Error::from_kernel_errno(ret)) + } +} + +/// Bridge function of type `F` from Rust ABI to C. +extern "C" fn bridge(data: *mut c_types::c_void) -> i32 +where + F: FnOnce() -> Result, +{ + // SAFETY: `data` is the result of [`Box::into_raw`], therefore it's safe + // to [`Box::from_raw`] here. + let f = unsafe { Box::from_raw(data as *mut F) }; + + match f() { + Ok(_) => 0, + Err(e) => + // Changes the return value if it's `-ENITR`, in other words, we + // don't allow a thread function to return `-EINTR`. + // + // Rationale: the kernel uses `-EINTR` to indicate that the kernel + // thread gets stopped before it's able to run (the exit code is + // set at C side via a special call to `do_exit`). In that case, + // there is no new thread to own the thread argument, therefore the + // `stop` functions need to recycle the thread argument. If we allow + // thread function to return `-EINTR`, [`Thread::stop`] will receive + // it as the exit code, and we're unable to differ these two cases: + // 1) stopped before run and 2) returning `-ENITR` from thread + // function. + // + // Note that currently in kernel, no one actually calls `do_exit` + // with `-EINTR` or returns `-EINTR` from thread function other + // than the special case mentioned above. + { + if e.to_kernel_errno() == -(bindings::EINTR as i32) { + -(bindings::EINVAL as i32) + } else { + e.to_kernel_errno() + } + } + } +} + +/// A kernel thread handle. +pub struct Thread { + /// Raw kernel thread. + raw: RawThread, + + /// Pointer to the thread argument. + arg_ptr: *const c_types::c_void, + + /// Drop function to recycle arg + drop_arg: fn(*const c_types::c_void), +} + +impl Thread { + /// Creates a new thread. + /// + /// # Examples + /// + /// ```rust + /// #![feature(allocator_api)] + /// + /// # use kernel::prelude::*; + /// use kernel::{c_str, thread::Thread}; + /// + /// fn test() -> Result { + /// let boxed = Box::try_new(42)?; + /// + /// let t = Thread::try_new(c_str!("rust-thread"), move || { + /// pr_info!("boxed is {}", boxed); + /// Ok(()) + /// })?; + /// + /// t.wake_up(); + /// + /// Ok(()) + /// } + /// ``` + /// + /// # Context + /// + /// This function might sleep in `kthread_create_on_node` due to the memory + /// allocation and waiting for the completion, therefore do not call this + /// in atomic contexts (i.e. preemption-off contexts). + pub fn try_new(name: &CStr, f: F) -> Result + where + F: FnOnce() -> Result, + F: Send + 'static, + { + // Allocate closure here, because this function may return before + // the bridged function (the function that uses the closure) get executed. + let boxed_fn = Box::try_new(f)?; + let data = Box::into_raw(boxed_fn); + + // SAFETY: `bridge::` is a proper function pointer to a C function, + // and [`Box::from_raw`] will be used in it to consume the raw pointer + // in the new thread. + let result = unsafe { RawThread::try_new(name, bridge::, data as _) }; + + if result.is_err() { + // Creation fails, we need to consume the raw pointer `data` because + // there is no new thread to own the underlying object, we should + // let the current thread own it. + // + // SAFETY: We [`Box::from_raw`] back what we just [`Box::into_raw`], + // and since the new thread is not created, so no one touches + // `data`. + unsafe { + Box::from_raw(data); + } + } + + result.map(|raw| Thread { + raw, + arg_ptr: data as _, + drop_arg: |ptr: *const c_types::c_void| { + // SAFETY: `ptr` is what we [`Box::into_raw`] and store at + // [`Thread::arg_ptr`], and `drop_arg` only get called if + // the thread hasn't run, which means no one [`Box::from_raw`] + // the `ptr`, and that means we can safely do it. + unsafe { + Box::from_raw(ptr as *mut F); + } + }, + }) + } + + /// Wakes up the thread. + /// + /// Note that a newly created thread (e.g. via [`Thread::try_new`]) will not + /// run until a [`Thread::wake_up`] is called. + /// + /// # Context + /// + /// This function might sleep, don't call it in atomic contexts. + pub fn wake_up(&self) { + self.raw.wake_up() + } + + /// Stops the thread. + /// + /// - If the thread hasn't been waken up after creation, the thread closure + /// won't be called, and will return `-EINTR`. Note that a thread may not + /// be waken up even after [`Thread::wake_up`] is called. + /// + /// - Otherwise, waits for the closure to return or the thread `do_exit` + /// itself. + /// + /// Consumes the [`Thread`]. In case of error, returns the exit code of the + /// thread. + /// + /// # Context + /// + /// This function might sleep, don't call it in atomic contexts. + pub fn stop(self) -> Result { + let result = self.raw.stop(); + + if let Err(e) = result { + if e.to_kernel_errno() == -(bindings::EINTR as i32) { + (self.drop_arg)(self.arg_ptr); + } + return Err(e); + } + result + } +} + +/// Tries to give up the CPU and lets another thread run. +/// +/// This maps to kernel's `schedule` function, which is similar to +/// [`std::thread::yield_now`]. +/// +/// # Context +/// +/// This function might sleep, don't call in atomic contexts. +/// +/// [`std::thread::yield_now`]: https://doc.rust-lang.org/std/thread/fn.yield_now.html +#[doc(alias = "yield_now")] +pub fn schedule() { + // SAFETY: If we can schedule back from other thread, then this can be + // treated as a no-op. A special case occurs when a thread sets its state to + // `TASK_DEAD`, and then `schedule` will not come. Currently we don't have a + // way to do this safely in Rust, and in the future, we probably still won't + // allow it. + unsafe { + bindings::schedule(); + } +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 183a3c4dc80cd7..cc4ae9c918a399 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -50,6 +50,16 @@ config SAMPLE_RUST_SYNC If unsure, say N. +config SAMPLE_RUST_THREAD + tristate "Thread APIs" + help + This option builds the Rust thread APIs sample. + + To compile this as a module, choose M here: + the module will be called rust_thread. + + If unsure, say N. + config SAMPLE_RUST_CHRDEV tristate "Character device" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 48bc871ea1f8ff..e180493fdc18da 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -4,6 +4,7 @@ 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_THREAD) += rust_thread.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 diff --git a/samples/rust/rust_thread.rs b/samples/rust/rust_thread.rs new file mode 100644 index 00000000000000..2893ed981e6174 --- /dev/null +++ b/samples/rust/rust_thread.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust thread APIs sample + +#![no_std] +#![feature(allocator_api, global_asm)] + +use alloc::boxed::Box; +use core::sync::atomic::{AtomicBool, Ordering}; +use kernel::prelude::*; +use kernel::{ + c_str, pr_cont, + sync::Ref, + thread::{schedule, Thread}, +}; + +module! { + type: RustThread, + name: b"rust_thread", + author: b"Rust for Linux Contributors", + description: b"Rust thread APIs sample", + license: b"GPL v2", + params: { + }, +} + +struct RustThread; + +impl KernelModule for RustThread { + fn init() -> Result { + pr_info!("Rust thread APIs sample (init)"); + + // Test threads. + { + Thread::try_new(c_str!("rust-thread0"), || { + pr_info!("Hello Rust Thread"); + Ok(()) + })? + .wake_up(); + } + + // Test threads (wait for the thread to finish). + { + let mut a = 1; + // Flag that tells whether the thread gets executed. + let flag = Ref::try_new(AtomicBool::new(false))?; + let other = flag.clone(); + let boxed_slice: Box<[i32]> = Box::try_new([1, 3, 4, 5])?; + + let t1 = Thread::try_new(c_str!("rust-thread1"), move || { + other.store(true, Ordering::Release); + let b = Box::try_new(42)?; + for _ in 0..20 { + a += 1; + pr_info!("Hello Rust Thread {}", a + b.as_ref()); + } + pr_info!("A Box<[i32]>: "); + for i in boxed_slice.iter() { + pr_cont!("{} ", i); + } + pr_info!(""); + + Ok(()) + })?; + + t1.wake_up(); + + // Waits to observe the thread run. + while !flag.load(Ordering::Acquire) { + schedule(); + } + + // `t1` should exit normally. + t1.stop().expect("Rust thread should exit normally"); + } + + // Test threads (not up for running). + { + // One-off struct only to call `drop`. + struct ToDrop(Ref); + + impl Drop for ToDrop { + fn drop(&mut self) { + // Indicate the drop happened. + self.0.store(true, Ordering::Relaxed); + } + } + + unsafe impl Send for ToDrop {} + + // Flag that tells whether the thread gets executed. + let flag = Ref::try_new(AtomicBool::new(false))?; + let other = flag.clone(); + + // Flag that tells whether `drop` gets executed. + let drop_flag = Ref::try_new(AtomicBool::new(false))?; + let to_drop = ToDrop(drop_flag.clone()); + + let mut a = 1; + + let t1 = Thread::try_new(c_str!("rust-thread2"), move || { + let b = Box::try_new(42)?; + for _ in 0..20 { + a += 1; + pr_info!("Hello Rust Thread {}", a + b.as_ref()); + } + + // moves `to_drop` in. + let _ = to_drop; + + // sets the flag to show that the thread gets executed. + other.store(true, Ordering::Relaxed); + + Ok(()) + })?; + + // The thread hasn't executed and the `to_drop` is not dropped. + assert!(!(flag.load(Ordering::Relaxed)) && !(drop_flag.load(Ordering::Relaxed))); + + // Without `wake_up`, `stop` will cause the thread to exits with `-EINTR`. + t1.stop().expect_err("Rust thread should exit abnormally"); + + // The thread hasn't executed and the `to_drop` is dropped. + assert!(!(flag.load(Ordering::Relaxed)) && drop_flag.load(Ordering::Relaxed)); + } + + Ok(RustThread) + } +} + +impl Drop for RustThread { + fn drop(&mut self) { + pr_info!("Rust thread APIs sample (exit)"); + } +}