Skip to content

Commit f74738f

Browse files
hoshinolinamarcan
authored andcommitted
rust: sync: Implement dynamic lockdep class creation
Using macros to create lock classes all over the place is unergonomic, and makes it impossible to add new features that require lock classes to code such as Arc<> without changing all callers. Rust has the ability to track the caller's identity by file/line/column number, and we can use that to dynamically generate lock classes instead. Signed-off-by: Asahi Lina <[email protected]>
1 parent e6b43d7 commit f74738f

2 files changed

Lines changed: 154 additions & 1 deletion

File tree

rust/kernel/sync/lockdep.rs

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@
55
//! This module abstracts the parts of the kernel lockdep API relevant to Rust
66
//! modules, including lock classes.
77
8-
use crate::types::Opaque;
8+
use crate::{
9+
c_str, fmt,
10+
init::InPlaceInit,
11+
new_mutex,
12+
prelude::{Box, Result, Vec},
13+
str::{CStr, CString},
14+
sync::Mutex,
15+
types::Opaque,
16+
};
17+
18+
use core::hash::{Hash, Hasher};
19+
use core::pin::Pin;
20+
use core::sync::atomic::{AtomicPtr, Ordering};
921

1022
/// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
1123
#[repr(transparent)]
@@ -42,3 +54,136 @@ impl LockClassKey {
4254
// actually dereferenced.
4355
unsafe impl Send for LockClassKey {}
4456
unsafe impl Sync for LockClassKey {}
57+
58+
// Location is 'static but not really, since module unloads will
59+
// invalidate existing static Locations within that module.
60+
// To avoid breakage, we maintain our own location struct which is
61+
// dynamically allocated on first reference. We store a hash of the
62+
// whole location (including the filename string), as well as the
63+
// line and column separately. The assumption is that this whole
64+
// struct is highly unlikely to ever collide with a reasonable
65+
// hash (this saves us from having to check the filename string
66+
// itself).
67+
#[derive(PartialEq, Debug)]
68+
struct LocationKey {
69+
hash: u64,
70+
line: u32,
71+
column: u32,
72+
}
73+
74+
struct DynLockClassKey {
75+
key: Opaque<bindings::lock_class_key>,
76+
loc: LocationKey,
77+
name: CString,
78+
}
79+
80+
impl LocationKey {
81+
fn new(loc: &'static core::panic::Location<'static>) -> Self {
82+
let mut hasher = crate::siphash::SipHasher::new();
83+
loc.hash(&mut hasher);
84+
85+
LocationKey {
86+
hash: hasher.finish(),
87+
line: loc.line(),
88+
column: loc.column(),
89+
}
90+
}
91+
}
92+
93+
impl DynLockClassKey {
94+
fn key(&'static self) -> LockClassKey {
95+
LockClassKey(self.key.get())
96+
}
97+
98+
fn name(&'static self) -> &CStr {
99+
&self.name
100+
}
101+
}
102+
103+
const LOCK_CLASS_BUCKETS: usize = 1024;
104+
105+
#[track_caller]
106+
fn caller_lock_class_inner() -> Result<&'static DynLockClassKey> {
107+
// This is just a hack to make the below static array initialization work.
108+
#[allow(clippy::declare_interior_mutable_const)]
109+
const ATOMIC_PTR: AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>> =
110+
AtomicPtr::new(core::ptr::null_mut());
111+
112+
#[allow(clippy::complexity)]
113+
static LOCK_CLASSES: [AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>>; LOCK_CLASS_BUCKETS] =
114+
[ATOMIC_PTR; LOCK_CLASS_BUCKETS];
115+
116+
let loc = core::panic::Location::caller();
117+
let loc_key = LocationKey::new(loc);
118+
119+
let index = (loc_key.hash % (LOCK_CLASS_BUCKETS as u64)) as usize;
120+
let slot = &LOCK_CLASSES[index];
121+
122+
let mut ptr = slot.load(Ordering::Relaxed);
123+
if ptr.is_null() {
124+
let new_element = Box::pin_init(new_mutex!(Vec::new()))?;
125+
126+
if let Err(e) = slot.compare_exchange(
127+
core::ptr::null_mut(),
128+
// SAFETY: We never move out of this Box
129+
Box::into_raw(unsafe { Pin::into_inner_unchecked(new_element) }),
130+
Ordering::Relaxed,
131+
Ordering::Relaxed,
132+
) {
133+
// SAFETY: We just got this pointer from `into_raw()`
134+
unsafe { Box::from_raw(e) };
135+
}
136+
137+
ptr = slot.load(Ordering::Relaxed);
138+
assert!(!ptr.is_null());
139+
}
140+
141+
// SAFETY: This mutex was either just created above or previously allocated,
142+
// and we never free these objects so the pointer is guaranteed to be valid.
143+
let mut guard = unsafe { (*ptr).lock() };
144+
145+
for i in guard.iter() {
146+
if i.loc == loc_key {
147+
return Ok(i);
148+
}
149+
}
150+
151+
// We immediately leak the class, so it becomes 'static
152+
let new_class = Box::leak(Box::try_new(DynLockClassKey {
153+
key: Opaque::zeroed(),
154+
loc: loc_key,
155+
name: CString::try_from_fmt(fmt!("{}:{}:{}", loc.file(), loc.line(), loc.column()))?,
156+
})?);
157+
158+
// SAFETY: This is safe to call with a pointer to a dynamically allocated lockdep key,
159+
// and we never free the objects so it is safe to never unregister the key.
160+
unsafe { bindings::lockdep_register_key(new_class.key.get()) };
161+
162+
guard.try_push(new_class)?;
163+
164+
Ok(new_class)
165+
}
166+
167+
#[track_caller]
168+
pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
169+
match caller_lock_class_inner() {
170+
Ok(a) => (a.key(), a.name()),
171+
Err(_) => {
172+
crate::pr_err!(
173+
"Failed to dynamically allocate lock class, lockdep may be unreliable.\n"
174+
);
175+
176+
let loc = core::panic::Location::caller();
177+
// SAFETY: LockClassKey is opaque and the lockdep implementation only needs
178+
// unique addresses for statically allocated keys, so it is safe to just cast
179+
// the Location reference directly into a LockClassKey. However, this will
180+
// result in multiple keys for the same callsite due to monomorphization,
181+
// as well as spuriously destroyed keys when the static key is allocated in
182+
// the wrong module, which is what makes this unreliable.
183+
(
184+
LockClassKey(loc as *const _ as *mut _),
185+
c_str!("fallback_lock_class"),
186+
)
187+
}
188+
}
189+
}

rust/kernel/sync/no_lockdep.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//!
55
//! Takes the place of the `lockdep` module when lockdep is disabled.
66
7+
use crate::{c_str, str::CStr};
8+
79
/// A dummy, zero-sized lock class.
810
pub struct StaticLockClassKey();
911

@@ -28,3 +30,9 @@ impl LockClassKey {
2830
core::ptr::null_mut()
2931
}
3032
}
33+
34+
pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
35+
static DUMMY_LOCK_CLASS: StaticLockClassKey = StaticLockClassKey::new();
36+
37+
(DUMMY_LOCK_CLASS.key(), c_str!("dummy"))
38+
}

0 commit comments

Comments
 (0)