Commit 846f9974 authored by Eric W. Biederman's avatar Eric W. Biederman Committed by Linus Torvalds

sysfs: Add lockdep annotations for the sysfs active reference

Holding locks over device_del -> kobject_del -> sysfs_deactivate can
cause deadlocks if those same locks are grabbed in sysfs show or store
methods.

The I model s_active count + completion as a sleeping read/write lock.
I describe to lockdep sysfs_get_active as a read_trylock,
sysfs_put_active as a read_unlock, and sysfs_deactivate as a
write_lock and write_unlock pair.  This seems to capture the essence
for purposes of finding deadlocks, and in my testing gives finds real
issues and ignores non-issues.

This brings us back to holding locks over kobject_del is a problem
that ideally we should find a way of addressing, but at least lockdep
can tell us about the problems instead of requiring developers to debug
rare strange system deadlocks, that happen when sysfs files are removed
while being written to.
Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
Acked-by: default avatarTejun Heo <tj@kernel.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 3e27249c
...@@ -106,8 +106,10 @@ static struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd) ...@@ -106,8 +106,10 @@ static struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd)
return NULL; return NULL;
t = atomic_cmpxchg(&sd->s_active, v, v + 1); t = atomic_cmpxchg(&sd->s_active, v, v + 1);
if (likely(t == v)) if (likely(t == v)) {
rwsem_acquire_read(&sd->dep_map, 0, 1, _RET_IP_);
return sd; return sd;
}
if (t < 0) if (t < 0)
return NULL; return NULL;
...@@ -130,6 +132,7 @@ static void sysfs_put_active(struct sysfs_dirent *sd) ...@@ -130,6 +132,7 @@ static void sysfs_put_active(struct sysfs_dirent *sd)
if (unlikely(!sd)) if (unlikely(!sd))
return; return;
rwsem_release(&sd->dep_map, 1, _RET_IP_);
v = atomic_dec_return(&sd->s_active); v = atomic_dec_return(&sd->s_active);
if (likely(v != SD_DEACTIVATED_BIAS)) if (likely(v != SD_DEACTIVATED_BIAS))
return; return;
...@@ -194,15 +197,21 @@ static void sysfs_deactivate(struct sysfs_dirent *sd) ...@@ -194,15 +197,21 @@ static void sysfs_deactivate(struct sysfs_dirent *sd)
BUG_ON(sd->s_sibling || !(sd->s_flags & SYSFS_FLAG_REMOVED)); BUG_ON(sd->s_sibling || !(sd->s_flags & SYSFS_FLAG_REMOVED));
sd->s_sibling = (void *)&wait; sd->s_sibling = (void *)&wait;
rwsem_acquire(&sd->dep_map, 0, 0, _RET_IP_);
/* atomic_add_return() is a mb(), put_active() will always see /* atomic_add_return() is a mb(), put_active() will always see
* the updated sd->s_sibling. * the updated sd->s_sibling.
*/ */
v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active); v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active);
if (v != SD_DEACTIVATED_BIAS) if (v != SD_DEACTIVATED_BIAS) {
lock_contended(&sd->dep_map, _RET_IP_);
wait_for_completion(&wait); wait_for_completion(&wait);
}
sd->s_sibling = NULL; sd->s_sibling = NULL;
lock_acquired(&sd->dep_map, _RET_IP_);
rwsem_release(&sd->dep_map, 1, _RET_IP_);
} }
static int sysfs_alloc_ino(ino_t *pino) static int sysfs_alloc_ino(ino_t *pino)
...@@ -345,6 +354,7 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) ...@@ -345,6 +354,7 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type)
atomic_set(&sd->s_count, 1); atomic_set(&sd->s_count, 1);
atomic_set(&sd->s_active, 0); atomic_set(&sd->s_active, 0);
sysfs_dirent_init_lockdep(sd);
sd->s_name = name; sd->s_name = name;
sd->s_mode = mode; sd->s_mode = mode;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
* This file is released under the GPLv2. * This file is released under the GPLv2.
*/ */
#include <linux/lockdep.h>
#include <linux/fs.h> #include <linux/fs.h>
struct sysfs_open_dirent; struct sysfs_open_dirent;
...@@ -50,6 +51,9 @@ struct sysfs_inode_attrs { ...@@ -50,6 +51,9 @@ struct sysfs_inode_attrs {
struct sysfs_dirent { struct sysfs_dirent {
atomic_t s_count; atomic_t s_count;
atomic_t s_active; atomic_t s_active;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
struct sysfs_dirent *s_parent; struct sysfs_dirent *s_parent;
struct sysfs_dirent *s_sibling; struct sysfs_dirent *s_sibling;
const char *s_name; const char *s_name;
...@@ -84,6 +88,17 @@ static inline unsigned int sysfs_type(struct sysfs_dirent *sd) ...@@ -84,6 +88,17 @@ static inline unsigned int sysfs_type(struct sysfs_dirent *sd)
return sd->s_flags & SYSFS_TYPE_MASK; return sd->s_flags & SYSFS_TYPE_MASK;
} }
#ifdef CONFIG_DEBUG_LOCK_ALLOC
#define sysfs_dirent_init_lockdep(sd) \
do { \
static struct lock_class_key __key; \
\
lockdep_init_map(&sd->dep_map, "s_active", &__key, 0); \
} while(0)
#else
#define sysfs_dirent_init_lockdep(sd) do {} while(0)
#endif
/* /*
* Context structure to be used while adding/removing nodes. * Context structure to be used while adding/removing nodes.
*/ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment