On Tue, Sep 22, 2026 at 4:51 PM Boqun Feng <[email protected]> wrote:
>
> On Tue, Sep 22, 2026 at 03:09:48PM +0800, Kunwu Chan wrote:
> > lockdep_unregister_key() waits for is_dynamic_key() callers with
> > synchronize_rcu_expedited(), which sends IPIs to every online CPU.
> > Have is_dynamic_key() mark the hash bucket with a hazard pointer
> > and use hazptr_synchronize() to wait specifically for those
> > traversals.
> >
> > The hash bucket address from keyhashentry() is stable, making it a
> > suitable hazptr synchronize target. The rest of the key hashlist
> > lifetime (hlist_del_rcu/call_rcu) remains RCU-based.
> >
> > This adapts the lockdep use case from Boqun Feng's hazard-pointer
> > series to the current hazptr API.
> >
> > Signed-off-by: Kunwu Chan <[email protected]>
> > ---
> > kernel/locking/lockdep.c | 30 ++++++++++++++++++++----------
> > 1 file changed, 20 insertions(+), 10 deletions(-)
> >
> > diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
> > index c56a7f91d72e..f67d847f9abf 100644
> > --- a/kernel/locking/lockdep.c
> > +++ b/kernel/locking/lockdep.c
> > @@ -58,6 +58,7 @@
> > #include <linux/context_tracking.h>
> > #include <linux/console.h>
> > #include <linux/kasan.h>
> > +#include <linux/hazptr.h>
> >
> > #include <asm/sections.h>
> >
> > @@ -1280,14 +1281,24 @@ static bool is_dynamic_key(const struct
> > lock_class_key *key)
> >
> > hash_head = keyhashentry(key);
> >
> > - rcu_read_lock();
> > - hlist_for_each_entry_rcu(k, hash_head, hash_entry) {
> > - if (k == key) {
> > - found = true;
> > - break;
> > + /*
> > + * The traversal is protected by a hazard pointer rather
> > + * than an RCU read-side critical section.
> > + */
> > + {
> > + struct hazptr_ctx ctx;
> > + void *bucket = hash_head;
> > + void *addr;
> > +
> > + addr = hazptr_acquire(&ctx, &bucket);
> > + hlist_for_each_entry_rcu(k, hash_head, hash_entry, 1) {
> > + if (k == key) {
> > + found = true;
> > + break;
> > + }
> > }
> > + hazptr_release(&ctx, addr);
>
> This looks good to me. However, we probably want to use scoped_guard()
> here, that means cleanup.h support for hazptr.
>
Thanks for the suggestions. That makes sense.
I'll add a DEFINE_CLASS wrapper for hazptr so is_dynamic_key() can use
scoped_guard().
> The other thing that could be added is a debug option that force we
> skip the fast path, so we always go into the show path in
> hazptr_acquire(). This occurs to me because the slow path here means
> acquiring a lock inside lockdep code, it should work, but I just want to
> be careful. So something like:
>
> void *hazptr_acquire(..)
> {
> ...
> if (IS_ENABLED(CONFIG_HAZPTR_ACQUIRE_FORCE_SLOWPATH) ||
> unlikely(slot->addr))
> return __hazptr_acquire(ctx, addr_p);
> ...
> }
>
> Thoughts?
Agreed on the slow path. I'll add CONFIG_HAZPTR_ACQUIRE_FORCE_SLOWPATH to
always enter __hazptr_acquire(), and exercise it with lockdep-heavy
workloads under
CONFIG_PROVE_LOCKING.
The existing hazptrtorture module can also cover the forced-slowpath
path for additional
stress, including per-CPU slot contention that can trigger the backup-slot lock.
Thanks,
Kunwu
>
> Regards,
> Boqun
>
> > }
> > - rcu_read_unlock();
> >
> > return found;
> > }
> > @@ -6683,11 +6694,10 @@ void lockdep_unregister_key(struct lock_class_key
> > *key)
> > *
> > * Some operations like __qdisc_destroy() will call this in a debug
> > * kernel, and the network traffic is disabled while waiting, hence
> > - * the delay of the wait matters in debugging cases. Currently use a
> > - * synchronize_rcu_expedited() to speed up the wait at the cost of
> > - * system IPIs. TODO: Replace RCU with hazptr for this.
> > + * the delay of the wait matters in debugging cases. Replace the
> > + * expedited RCU wait with hazptr_synchronize().
> > */
> > - synchronize_rcu_expedited();
> > + hazptr_synchronize(keyhashentry(key));
> > }
> > EXPORT_SYMBOL_GPL(lockdep_unregister_key);
> >
> > --
> > 2.43.0
> >