Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental PR for introducing klint #958

Draft
wants to merge 2 commits into
base: rust
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*.gcno
*.gz
*.i
*.klint
*.ko
*.lex.c
*.ll
Expand Down
1 change: 1 addition & 0 deletions rust/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
$(filter-out $(skip_flags),$(rust_flags) $(rustc_target_flags)) \
--emit=dep-info,obj,metadata --crate-type rlib \
-Zcrate-attr='feature(register_tool)' -Zcrate-attr='register_tool(klint)' \
--out-dir $(objtree)/$(obj) -L$(objtree)/$(obj) \
--crate-name $(patsubst %.o,%,$(notdir $@)) $<; \
mv $(objtree)/$(obj)/$(patsubst %.o,%,$(notdir $@)).d $(depfile); \
Expand Down
4 changes: 2 additions & 2 deletions rust/kernel/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
bindings,
revocable::{Revocable, RevocableGuard},
str::CStr,
sync::{LockClassKey, NeedsLockClass, RevocableMutex, RevocableMutexGuard, UniqueArc},
sync::{rcu, LockClassKey, NeedsLockClass, RevocableMutex, RevocableMutexGuard, UniqueArc},
Result,
};
use core::{
Expand Down Expand Up @@ -294,7 +294,7 @@ impl<T, U, V> Data<T, U, V> {
}

/// Returns the resources if they're still available.
pub fn resources(&self) -> Option<RevocableGuard<'_, U>> {
pub fn resources(&self) -> Result<RevocableGuard<'_, U>, rcu::Guard> {
self.resources.try_access()
}

Expand Down
2 changes: 1 addition & 1 deletion rust/kernel/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ impl From<core::convert::Infallible> for Error {
/// Note that even if a function does not return anything when it succeeds,
/// it should still be modeled as returning a `Result` rather than
/// just an [`Error`].
pub type Result<T = ()> = core::result::Result<T, Error>;
pub type Result<T = (), E = Error> = core::result::Result<T, E>;

impl From<AllocError> for Error {
fn from(_: AllocError) -> Error {
Expand Down
5 changes: 5 additions & 0 deletions rust/kernel/kasync/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ pub trait Executor: Sync + Send {
/// Types that implement this trait can get a [`Waker`] by calling [`ref_waker`].
pub trait ArcWake: Send + Sync {
/// Wakes a task up.
#[klint::preempt_count(expect = 0..)]
fn wake_by_ref(self: ArcBorrow<'_, Self>);

/// Wakes a task up and consumes a reference.
#[klint::preempt_count(expect = 0..)]
fn wake(self: Arc<Self>) {
self.as_arc_borrow().wake_by_ref();
}
Expand All @@ -83,18 +85,21 @@ pub fn ref_waker<T: 'static + ArcWake>(w: Arc<T>) -> Waker {
)
}

#[klint::preempt_count(expect = 0..)] // Required as `Waker::clone` must not sleep.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Waker::clone must not sleep, could you elaborate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is called by the callback passed to init_waitqueue_func_entry, which isn't allowed to sleep.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make senses, however, why do we need clone in wake_callback?

/me goes to search PR history

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course @wedsonaf may know the answer ;-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is done to invoke the waker after unlocking the mutex protecting the waker is released. I would expect this is to prevent a deadlock or something like that.

Copy link
Member

@fbq fbq Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to me that we can just do the following

        if let Some(mut guard) = s.waker.try_lock() {
            if let Some(w) = guard.take() {
                drop(guard);
                w.wake();
                return 1;
            }
        |

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I suggested in the meeting. But I didn't make the change because I think the whole NoWaitLock<Option<Waker>> thing should be replaced with AtomicWaker instead.

unsafe fn clone<T: 'static + ArcWake>(ptr: *const ()) -> RawWaker {
// SAFETY: The data stored in the raw waker is the result of a call to `into_pointer`.
let w = unsafe { Arc::<T>::borrow(ptr.cast()) };
raw_waker(w.into())
}

#[klint::preempt_count(expect = 0..)] // Required as `Waker::wake` must not sleep.
unsafe fn wake<T: 'static + ArcWake>(ptr: *const ()) {
// SAFETY: The data stored in the raw waker is the result of a call to `into_pointer`.
let w = unsafe { Arc::<T>::from_pointer(ptr.cast()) };
w.wake();
}

#[klint::preempt_count(expect = 0..)] // Required as `Waker::wake_by_ref` must not sleep.
unsafe fn wake_by_ref<T: 'static + ArcWake>(ptr: *const ()) {
// SAFETY: The data stored in the raw waker is the result of a call to `into_pointer`.
let w = unsafe { Arc::<T>::borrow(ptr.cast()) };
Expand Down
11 changes: 6 additions & 5 deletions rust/kernel/kasync/executor/workqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ impl<T: 'static + Send + Future> RevocableTask for Task<T> {
}

impl<T: 'static + Send + Future> ArcWake for Task<T> {
#[klint::preempt_count(expect = 0.., unchecked)] // FIXME: This is a bug.
fn wake(self: Arc<Self>) {
Self::wake_by_ref(self.as_arc_borrow());
}

fn wake_by_ref(self: ArcBorrow<'_, Self>) {
if self.future.is_revoked() {
return;
}
Expand All @@ -152,11 +157,7 @@ impl<T: 'static + Send + Future> ArcWake for Task<T> {
Left(q) => &**q,
Right(q) => *q,
}
.enqueue(self.clone());
}

fn wake_by_ref(self: ArcBorrow<'_, Self>) {
Arc::from(self).wake();
.enqueue(self);
}
}

Expand Down
1 change: 1 addition & 0 deletions rust/kernel/kasync/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ impl<'a, Out, F: FnMut() -> Result<Out> + Send + 'a> SocketFuture<'a, Out, F> {
///
/// If the state matches the one we're waiting on, we wake up the task so that the future can be
/// polled again.
#[klint::preempt_count(expect = 0..)] // This function can be called from `wake_up` which must noy sleep.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
#[klint::preempt_count(expect = 0..)] // This function can be called from `wake_up` which must noy sleep.
#[klint::preempt_count(expect = 0..)] // This function can be called from `wake_up` which must not sleep.

unsafe extern "C" fn wake_callback(
wq_entry: *mut bindings::wait_queue_entry,
_mode: core::ffi::c_uint,
Expand Down
6 changes: 3 additions & 3 deletions rust/kernel/revocable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ impl<T> Revocable<T> {
/// Returns a guard that gives access to the object otherwise; the object is guaranteed to
/// remain accessible while the guard is alive. In such cases, callers are not allowed to sleep
/// because another CPU may be waiting to complete the revocation of this object.
pub fn try_access(&self) -> Option<RevocableGuard<'_, T>> {
pub fn try_access(&self) -> Result<RevocableGuard<'_, T>, rcu::Guard> {
let guard = rcu::read_lock();
if self.is_available.load(Ordering::Relaxed) {
// SAFETY: Since `self.is_available` is true, data is initialised and has to remain
// valid because the RCU read side lock prevents it from being dropped.
Some(unsafe { RevocableGuard::new(self.data.assume_init_ref().get(), guard) })
Ok(unsafe { RevocableGuard::new(self.data.assume_init_ref().get(), guard) })
} else {
None
Err(guard)
}
}

Expand Down
23 changes: 10 additions & 13 deletions rust/kernel/workqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use crate::{
bindings, c_str,
error::code::*,
sync::{Arc, LockClassKey, UniqueArc},
sync::{Arc, ArcBorrow, LockClassKey, UniqueArc},
Opaque, Result,
};
use core::{fmt, ops::Deref, ptr::NonNull};
Expand Down Expand Up @@ -233,32 +233,29 @@ impl Queue {
///
/// Returns `true` if the work item was successfully enqueue; returns `false` if it had already
/// been (and continued to be) enqueued.
pub fn enqueue<T: WorkAdapter<Target = T>>(&self, w: Arc<T>) -> bool {
pub fn enqueue<T: WorkAdapter<Target = T>>(&self, w: ArcBorrow<'_, T>) -> bool {
self.enqueue_adapter::<T>(w)
}

/// Enqueues a work item with an explicit adapter.
///
/// Returns `true` if the work item was successfully enqueue; returns `false` if it had already
/// been (and continued to be) enqueued.
pub fn enqueue_adapter<A: WorkAdapter + ?Sized>(&self, w: Arc<A::Target>) -> bool {
let ptr = Arc::into_raw(w);
pub fn enqueue_adapter<A: WorkAdapter + ?Sized>(&self, w: ArcBorrow<'_, A::Target>) -> bool {
let ptr = &*w as *const A::Target;
let field_ptr =
(ptr as *const u8).wrapping_offset(A::FIELD_OFFSET) as *mut bindings::work_struct;

// SAFETY: Having a shared reference to work queue guarantees that it remains valid, while
// the work item remains valid because we called `into_raw` and only call `from_raw` again
// if the object was already queued (so a previous call already guarantees it remains
// alive), when the work item runs, or when the work item is canceled.
// the work item remains valid because we will increment the reference count later if this function
// returns true.
let ret = unsafe {
bindings::queue_work_on(bindings::WORK_CPU_UNBOUND as _, self.0.get(), field_ptr)
};

if !ret {
// SAFETY: `ptr` comes from a previous call to `into_raw`. Additionally, given that
// `queue_work_on` returned `false`, we know that no-one is going to use the result of
// `into_raw`, so we must drop it here to avoid a reference leak.
unsafe { Arc::from_raw(ptr) };
if ret {
// Increase the reference count because the work item has a copy.
core::mem::forget(Arc::from(w));
}

ret
Expand All @@ -279,7 +276,7 @@ impl Queue {
func,
})?;
Work::init(&w, key);
self.enqueue(w.into());
self.enqueue(Arc::from(w).as_arc_borrow());
Ok(())
}
}
Expand Down
4 changes: 3 additions & 1 deletion scripts/Makefile.build
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,11 @@ rust_allowed_features := allocator_api,const_refs_to_cell

rust_common_cmd = \
RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \
-Zallow-features=$(rust_allowed_features) \
-Zallow-features=$(rust_allowed_features),register_tool \
-Zcrate-attr=no_std \
-Zcrate-attr='feature($(rust_allowed_features))' \
-Zcrate-attr='feature(register_tool)' \
-Zcrate-attr='register_tool(klint)' \
--extern alloc --extern kernel \
--crate-type rlib --out-dir $(obj) -L $(objtree)/rust/ \
--crate-name $(basename $(notdir $@))
Expand Down