|
5 | 5 | //! This module abstracts the parts of the kernel lockdep API relevant to Rust |
6 | 6 | //! modules, including lock classes. |
7 | 7 |
|
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}; |
9 | 21 |
|
10 | 22 | /// Represents a lockdep class. It's a wrapper around C's `lock_class_key`. |
11 | 23 | #[repr(transparent)] |
@@ -42,3 +54,136 @@ impl LockClassKey { |
42 | 54 | // actually dereferenced. |
43 | 55 | unsafe impl Send for LockClassKey {} |
44 | 56 | 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 pointer was either just created above or previously allocated, |
| 142 | + // and we never free these objects so the pointer is guaranteed to have a static lifetime. |
| 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 | +} |
0 commit comments