Re: [PATCH v2 02/12] device-core: Add dev->lock_class to enable device_lock() lockdep validation

From: Dan Williams
Date: Thu Apr 14 2022 - 13:18:22 EST


On Thu, Apr 14, 2022 at 3:16 AM Peter Zijlstra <peterz@xxxxxxxxxxxxx> wrote:
>
> On Wed, Apr 13, 2022 at 03:01:21PM -0700, Dan Williams wrote:
>
> > > That's not an obvious conclusion; lockdep has lots of funny annotations,
> > > subclasses is just one.
> > >
> > > I think the big new development in lockdep since that time with Alan
> > > Stern is that lockdep now has support for dynamic keys; that is lock
> > > keys in heap memory (as opposed to static storage).
> >
> > Ah, I was not aware of that, that should allow for deep cleanups of
> > this proposal.
>
> > > If you want lockdep validation for one (or more) dev->mutex instances,
> > > why not pull them out of the no_validate class and use the normal
> > > locking?
> >
> > Sounds perfect, just didn't know how to do that with my current
> > understanding of how to communicate this to lockdep.
> >
> > >
> > > This is all quite insane.
> >
> > Yes, certainly in comparison to your suggestion on the next patch.
> > That looks much more sane, and even better I think it allows for
> > optional lockdep validation without even needing to touch
> > include/linux/device.h.
>
> Right, so lockdep has:
>
> - classes, based off of lock_class_key address;
>
> * lock_class_key should be static storage; except now we also have:
>
> lockdep_{,un}register_key()
>
> which allows dynamic memory (aka. heap) to be used for classes,
> important to note that lockdep memory usage is still static storage
> because the memory allocators use locks too. So if you register too
> many dynamic keys, you'll run out of lockdep memory etc.. so be
> careful.
>
> * things like mutex_init() have a static lock_class_key per site
> and hence every lock initialized by the same code ends up in the
> same class by default.
>
> * can be trivially changed at any time, assuming the lock isn't held,
> using lockdep_set_class*() family.
>
> (extensively used all over the kernel, for example by the vfs to
> give each filesystem type their own locking order rules)
>
> * lockdep_set_no_validate_class() is a magical variant of
> lockdep_set_class() that sets a magical lock_class_key.

Golden, thanks Peter!

>
> * can be changed while held using lock_set_class()

One more sanity check... So in driver subsystems there are cases where
a device on busA hosts a topology on busB. When that happens there's a
need to set the lock class late in a driver since busA knows nothing
about the locking rules of busB. Since the device has a longer
lifetime than a driver when the driver exits it must set dev->mutex
back to the novalidate class, otherwise it sets up a use after free of
the static lock_class_key. I came up with this and it seems to work,
just want to make sure I'm properly using the lock_set_class() API and
it is ok to transition back and forth from the novalidate case:

diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 990b6670222e..32673e1a736d 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -405,6 +405,29 @@ struct cxl_nvdimm_bridge
*cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd);
#define __mock static
#endif

+#ifdef CONFIG_PROVE_LOCKING
+static inline void cxl_lock_reset_class(void *_dev)
+{
+ struct device *dev = _dev;
+
+ lock_set_class(&dev->mutex.dep_map, "__lockdep_no_validate__",
+ &__lockdep_no_validate__, 0, _THIS_IP_);
+}
+
+static inline int cxl_lock_set_class(struct device *dev, const char *name,
+ struct lock_class_key *key)
+{
+ lock_set_class(&dev->mutex.dep_map, name, key, 0, _THIS_IP_);
+ return devm_add_action_or_reset(dev, cxl_lock_reset_class, dev);
+}
+#else
+static inline int cxl_lock_set_class(struct device *dev, const char *name,
+ struct lock_class_key *key)
+{
+ return 0;
+}
+#endif
+
#ifdef CONFIG_PROVE_CXL_LOCKING
enum cxl_lock_class {
CXL_ANON_LOCK,

> ( from a lockdep pov it unlocks the held stack,
> changes the class of your lock, and re-locks the
> held stack, now with a different class nesting ).
>
> Be carefule! It doesn't forget the old nesting order, so you
> can trivally generate cycles.
>
> - subclasses, basically distinct addresses inside above mentioned
> lock_class_key object, limited to 8. Normally used with
> *lock_nested() family of functions. Typically used to lock multiple
> instances of a single lock class where there is defined order between
> instances (see for instance: double_rq_lock()).
>
> - nest_lock; eg. mutex_lock_nest_lock(), which allows expressing things
> like: multiple locks of class A can be taken in any order, provided
> we hold lock B.
>
> With many of these, it's possible to get it wrong and annotate real
> deadlocks away, so be careful :-)

Noted.