0ab66088c8
sysfs: implement sysfs_dirent active reference and immediate disconnect Opening a sysfs node references its associated kobject, so userland can arbitrarily prolong lifetime of a kobject which complicates lifetime rules in drivers. This patch implements active reference and makes the association between kobject and sysfs immediately breakable. Now each sysfs_dirent has two reference counts - s_count and s_active. s_count is a regular reference count which guarantees that the containing sysfs_dirent is accessible. As long as s_count reference is held, all sysfs internal fields in sysfs_dirent are accessible including s_parent and s_name. The newly added s_active is active reference count. This is acquired by invoking sysfs_get_active() and it's the caller's responsibility to ensure sysfs_dirent itself is accessible (should be holding s_count one way or the other). Dereferencing sysfs_dirent to access objects out of sysfs proper requires active reference. This includes access to the associated kobjects, attributes and ops. The active references can be drained and denied by calling sysfs_deactivate(). All active sysfs_dirents must be deactivated after deletion but before the default reference is dropped. This enables immediate disconnect of sysfs nodes. Once a sysfs_dirent is deleted, it won't access any entity external to sysfs proper. Because attr/bin_attr ops access both the node itself and its parent for kobject, they need to hold active references to both. sysfs_get/put_active_two() helpers are provided to help grabbing both references. Parent's is acquired first and released last. Unlike other operations, mmapped area lingers on after mmap() is finished and the module implement implementing it and kobj need to stay referenced till all the mapped pages are gone. This is accomplished by holding one set of active references to the bin_attr and its parent if there have been any mmap during lifetime of an openfile. The references are dropped when the openfile is released. This change makes sysfs lifetime rules independent from both kobject's and module's. It not only fixes several race conditions caused by sysfs not holding onto the proper module when referencing kobject, but also helps fixing and simplifying lifetime management in driver model and drivers by taking sysfs out of the equation. Please read the following message for more info. http://article.gmane.org/gmane.linux.kernel/510293 Signed-off-by: Tejun Heo <htejun@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
270 lines
5.8 KiB
C
270 lines
5.8 KiB
C
/*
|
|
* bin.c - binary file operations for sysfs.
|
|
*
|
|
* Copyright (c) 2003 Patrick Mochel
|
|
* Copyright (c) 2003 Matthew Wilcox
|
|
* Copyright (c) 2004 Silicon Graphics, Inc.
|
|
*/
|
|
|
|
#undef DEBUG
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/semaphore.h>
|
|
|
|
#include "sysfs.h"
|
|
|
|
struct bin_buffer {
|
|
struct mutex mutex;
|
|
void *buffer;
|
|
int mmapped;
|
|
};
|
|
|
|
static int
|
|
fill_read(struct dentry *dentry, char *buffer, loff_t off, size_t count)
|
|
{
|
|
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
|
|
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
|
|
struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
|
|
int rc;
|
|
|
|
/* need attr_sd for attr, its parent for kobj */
|
|
if (!sysfs_get_active_two(attr_sd))
|
|
return -ENODEV;
|
|
|
|
rc = -EIO;
|
|
if (attr->read)
|
|
rc = attr->read(kobj, buffer, off, count);
|
|
|
|
sysfs_put_active_two(attr_sd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t
|
|
read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off)
|
|
{
|
|
struct bin_buffer *bb = file->private_data;
|
|
struct dentry *dentry = file->f_path.dentry;
|
|
int size = dentry->d_inode->i_size;
|
|
loff_t offs = *off;
|
|
int count = min_t(size_t, bytes, PAGE_SIZE);
|
|
|
|
if (size) {
|
|
if (offs > size)
|
|
return 0;
|
|
if (offs + count > size)
|
|
count = size - offs;
|
|
}
|
|
|
|
mutex_lock(&bb->mutex);
|
|
|
|
count = fill_read(dentry, bb->buffer, offs, count);
|
|
if (count < 0)
|
|
goto out_unlock;
|
|
|
|
if (copy_to_user(userbuf, bb->buffer, count)) {
|
|
count = -EFAULT;
|
|
goto out_unlock;
|
|
}
|
|
|
|
pr_debug("offs = %lld, *off = %lld, count = %d\n", offs, *off, count);
|
|
|
|
*off = offs + count;
|
|
|
|
out_unlock:
|
|
mutex_unlock(&bb->mutex);
|
|
return count;
|
|
}
|
|
|
|
static int
|
|
flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count)
|
|
{
|
|
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
|
|
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
|
|
struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
|
|
int rc;
|
|
|
|
/* need attr_sd for attr, its parent for kobj */
|
|
if (!sysfs_get_active_two(attr_sd))
|
|
return -ENODEV;
|
|
|
|
rc = -EIO;
|
|
if (attr->write)
|
|
rc = attr->write(kobj, buffer, offset, count);
|
|
|
|
sysfs_put_active_two(attr_sd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t write(struct file *file, const char __user *userbuf,
|
|
size_t bytes, loff_t *off)
|
|
{
|
|
struct bin_buffer *bb = file->private_data;
|
|
struct dentry *dentry = file->f_path.dentry;
|
|
int size = dentry->d_inode->i_size;
|
|
loff_t offs = *off;
|
|
int count = min_t(size_t, bytes, PAGE_SIZE);
|
|
|
|
if (size) {
|
|
if (offs > size)
|
|
return 0;
|
|
if (offs + count > size)
|
|
count = size - offs;
|
|
}
|
|
|
|
mutex_lock(&bb->mutex);
|
|
|
|
if (copy_from_user(bb->buffer, userbuf, count)) {
|
|
count = -EFAULT;
|
|
goto out_unlock;
|
|
}
|
|
|
|
count = flush_write(dentry, bb->buffer, offs, count);
|
|
if (count > 0)
|
|
*off = offs + count;
|
|
|
|
out_unlock:
|
|
mutex_unlock(&bb->mutex);
|
|
return count;
|
|
}
|
|
|
|
static int mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
struct bin_buffer *bb = file->private_data;
|
|
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
|
|
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
|
|
struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
|
|
int rc;
|
|
|
|
mutex_lock(&bb->mutex);
|
|
|
|
/* need attr_sd for attr, its parent for kobj */
|
|
if (!sysfs_get_active_two(attr_sd))
|
|
return -ENODEV;
|
|
|
|
rc = -EINVAL;
|
|
if (attr->mmap)
|
|
rc = attr->mmap(kobj, attr, vma);
|
|
|
|
if (rc == 0 && !bb->mmapped)
|
|
bb->mmapped = 1;
|
|
else
|
|
sysfs_put_active_two(attr_sd);
|
|
|
|
mutex_unlock(&bb->mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int open(struct inode * inode, struct file * file)
|
|
{
|
|
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
|
|
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
|
|
struct bin_buffer *bb = NULL;
|
|
int error;
|
|
|
|
/* need attr_sd for attr */
|
|
if (!sysfs_get_active(attr_sd))
|
|
return -ENODEV;
|
|
|
|
/* Grab the module reference for this attribute */
|
|
error = -ENODEV;
|
|
if (!try_module_get(attr->attr.owner))
|
|
goto err_sput;
|
|
|
|
error = -EACCES;
|
|
if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap))
|
|
goto err_mput;
|
|
if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap))
|
|
goto err_mput;
|
|
|
|
error = -ENOMEM;
|
|
bb = kzalloc(sizeof(*bb), GFP_KERNEL);
|
|
if (!bb)
|
|
goto err_mput;
|
|
|
|
bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!bb->buffer)
|
|
goto err_mput;
|
|
|
|
mutex_init(&bb->mutex);
|
|
file->private_data = bb;
|
|
|
|
/* open succeeded, put active reference and pin attr_sd */
|
|
sysfs_put_active(attr_sd);
|
|
sysfs_get(attr_sd);
|
|
return 0;
|
|
|
|
err_mput:
|
|
module_put(attr->attr.owner);
|
|
err_sput:
|
|
sysfs_put_active(attr_sd);
|
|
kfree(bb);
|
|
return error;
|
|
}
|
|
|
|
static int release(struct inode * inode, struct file * file)
|
|
{
|
|
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
|
|
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
|
|
struct bin_buffer *bb = file->private_data;
|
|
|
|
if (bb->mmapped)
|
|
sysfs_put_active_two(attr_sd);
|
|
sysfs_put(attr_sd);
|
|
module_put(attr->attr.owner);
|
|
kfree(bb->buffer);
|
|
kfree(bb);
|
|
return 0;
|
|
}
|
|
|
|
const struct file_operations bin_fops = {
|
|
.read = read,
|
|
.write = write,
|
|
.mmap = mmap,
|
|
.llseek = generic_file_llseek,
|
|
.open = open,
|
|
.release = release,
|
|
};
|
|
|
|
/**
|
|
* sysfs_create_bin_file - create binary file for object.
|
|
* @kobj: object.
|
|
* @attr: attribute descriptor.
|
|
*/
|
|
|
|
int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr)
|
|
{
|
|
BUG_ON(!kobj || !kobj->dentry || !attr);
|
|
|
|
return sysfs_add_file(kobj->dentry, &attr->attr, SYSFS_KOBJ_BIN_ATTR);
|
|
}
|
|
|
|
|
|
/**
|
|
* sysfs_remove_bin_file - remove binary file for object.
|
|
* @kobj: object.
|
|
* @attr: attribute descriptor.
|
|
*/
|
|
|
|
void sysfs_remove_bin_file(struct kobject * kobj, struct bin_attribute * attr)
|
|
{
|
|
if (sysfs_hash_and_remove(kobj->dentry, attr->attr.name) < 0) {
|
|
printk(KERN_ERR "%s: "
|
|
"bad dentry or inode or no such file: \"%s\"\n",
|
|
__FUNCTION__, attr->attr.name);
|
|
dump_stack();
|
|
}
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(sysfs_create_bin_file);
|
|
EXPORT_SYMBOL_GPL(sysfs_remove_bin_file);
|