/*
* dir.c - Operations for sysfs directories.
*/
#undef DEBUG
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/namei.h>
#include <linux/idr.h>
#include <linux/completion.h>
#include <asm/semaphore.h>
#include "sysfs.h"
DECLARE_RWSEM(sysfs_rename_sem);
spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED;
spinlock_t kobj_sysfs_assoc_lock = SPIN_LOCK_UNLOCKED;
static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED;
static DEFINE_IDA(sysfs_ino_ida);
/**
* sysfs_link_sibling - link sysfs_dirent into sibling list
* @sd: sysfs_dirent of interest
*
* Link @sd into its sibling list which starts from
* sd->s_parent->s_children.
*
* Locking:
* mutex_lock(sd->s_parent->dentry->d_inode->i_mutex)
*/
static void sysfs_link_sibling(struct sysfs_dirent *sd)
{
struct sysfs_dirent *parent_sd = sd->s_parent;
BUG_ON(sd->s_sibling);
sd->s_sibling = parent_sd->s_children;
parent_sd->s_children = sd;
}
/**
* sysfs_unlink_sibling - unlink sysfs_dirent from sibling list
* @sd: sysfs_dirent of interest
*
* Unlink @sd from its sibling list which starts from
* sd->s_parent->s_children.
*
* Locking:
* mutex_lock(sd->s_parent->dentry->d_inode->i_mutex)
*/
static void sysfs_unlink_sibling(struct sysfs_dirent *sd)
{
struct sysfs_dirent **pos;
for (pos = &sd->s_parent->s_children; *pos; pos = &(*pos)->s_sibling) {
if (*pos == sd) {
*pos = sd->s_sibling;
sd->s_sibling = NULL;
break;
}
}
}
/**
* sysfs_get_active - get an active reference to sysfs_dirent
* @sd: sysfs_dirent to get an active reference to
*
* Get an active reference of @sd. This function is noop if @sd
* is NULL.
*
* RETURNS:
* Pointer to @sd on success, NULL on failure.
*/
struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd)
{
if (unlikely(!sd))
return NULL;
while (1) {
int v, t;
v = atomic_read(&sd->s_active);
if (unlikely(v < 0))
return NULL;
t = atomic_cmpxchg(&sd->s_active, v, v + 1);
if (likely(t == v))
return sd;
if (t < 0)
return NULL;
cpu_relax();
}
}
/**
* sysfs_put_active - put an active reference to sysfs_dirent
* @sd: sysfs_dirent to put an active reference to
*
* Put an active reference to @sd. This function is noop if @sd
* is NULL.
*/
void sysfs_put_active(struct sysfs_dirent *sd)
{
struct completion *cmpl;
int v;
if (unlikely(!sd))
return;
v = atomic_dec_return(&sd->s_active);
if (likely(v != SD_DEACTIVATED_BIAS))
return;
/* atomic_dec_return() is a mb(), we'll always see the updated
* sd->s_sibling.
*/
cmpl = (void *)sd->s_sibling;
complete(cmpl);
}
/**
* sysfs_get_active_two - get active references to sysfs_dirent and parent
* @sd: sysfs_dirent of interest
*
* Get active reference to @sd and its parent. Parent's active
* reference is grabbed first. This function is noop if @sd is
* NULL.
*
* RETURNS:
* Pointer to @sd on success, NULL on failure.
*/
struct sysfs_dirent *sysfs_get_active_two(struct sysfs_dirent *sd)
{
if (sd) {
if (sd->s_parent && unlikely(!sysfs_get_active(sd->s_parent)))
return NULL;
if (unlikely(!sysfs_get_active(sd))) {
sysfs_put_active(sd->s_parent);
return NULL;
}
}
return sd;
}
/**
* sysfs_put_active_two - put active references to sysfs_dirent and parent
* @sd: sysfs_dirent of interest
*
* Put active references to @sd and its parent. This function is
* noop if @sd is NULL.
*/
void sysfs_put_active_two(struct sysfs_dirent *sd)
{
if (sd) {
sysfs_put_active(sd);
sysfs_put_active(sd->s_parent);
}
}
/**
* sysfs_deactivate - deactivate sysfs_dirent
* @sd: sysfs_dirent to deactivate
*
* Deny new active references and drain existing ones.
*/
void sysfs_deactivate(struct sysfs_dirent *sd)
{
DECLARE_COMPLETION_ONSTACK(wait);
int v;
BUG_ON(sd->s_sibling || !(sd->s_flags & SYSFS_FLAG_REMOVED));
sd->s_sibling = (void *)&wait;
/* atomic_add_return() is a mb(), put_active() will always see
* the updated sd->s_sibling.
*/
v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active);
if (v != SD_DEACTIVATED_BIAS)
wait_for_completion(&wait);
sd->s_sibling = NULL;
}
static int sysfs_alloc_ino(ino_t *pino)
{
int ino, rc;
retry:
spin_lock(&sysfs_ino_lock);
rc = ida_get_new_above(&sysfs_ino_ida, 2, &ino);
spin_unlock(&sysfs_ino_lock);
if (rc == -EAGAIN) {
if (ida_pre_get(&sysfs_ino_ida, GFP_KERNEL))
goto retry;
rc = -ENOMEM;
}
*pino = ino;
return rc;
}
static void sysfs_free_ino(ino_t ino)
{
spin_l