diff options
Diffstat (limited to 'fs/sysfs/bin.c')
-rw-r--r-- | fs/sysfs/bin.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c new file mode 100644 index 0000000..f2c478c --- /dev/null +++ b/fs/sysfs/bin.c @@ -0,0 +1,285 @@ +/* + * fs/sysfs/bin.c - sysfs binary file implementation + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Matthew Wilcox + * Copyright (c) 2004 Silicon Graphics, Inc. + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + * + * Please see Documentation/filesystems/sysfs.txt for more information. + */ + +#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 <linux/mutex.h> + +#include <asm/uaccess.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_bin_attr.bin_attr; + struct kobject *kobj = attr_sd->s_parent->s_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, attr, 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); + char *temp; + + if (!bytes) + return 0; + + if (size) { + if (offs > size) + return 0; + if (offs + count > size) + count = size - offs; + } + + temp = kmalloc(count, GFP_KERNEL); + if (!temp) + return -ENOMEM; + + mutex_lock(&bb->mutex); + + count = fill_read(dentry, bb->buffer, offs, count); + if (count < 0) { + mutex_unlock(&bb->mutex); + goto out_free; + } + + memcpy(temp, bb->buffer, count); + + mutex_unlock(&bb->mutex); + + if (copy_to_user(userbuf, temp, count)) { + count = -EFAULT; + goto out_free; + } + + pr_debug("offs = %lld, *off = %lld, count = %d\n", offs, *off, count); + + *off = offs + count; + + out_free: + kfree(temp); + 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_bin_attr.bin_attr; + struct kobject *kobj = attr_sd->s_parent->s_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, attr, 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); + char *temp; + + if (!bytes) + return 0; + + if (size) { + if (offs > size) + return 0; + if (offs + count > size) + count = size - offs; + } + + temp = kmalloc(count, GFP_KERNEL); + if (!temp) + return -ENOMEM; + + if (copy_from_user(temp, userbuf, count)) { + count = -EFAULT; + goto out_free; + } + + mutex_lock(&bb->mutex); + + memcpy(bb->buffer, temp, count); + + count = flush_write(dentry, bb->buffer, offs, count); + mutex_unlock(&bb->mutex); + + if (count > 0) + *off = offs + count; + +out_free: + kfree(temp); + 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_bin_attr.bin_attr; + struct kobject *kobj = attr_sd->s_parent->s_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_bin_attr.bin_attr; + struct bin_buffer *bb = NULL; + int error; + + /* binary file operations requires both @sd and its parent */ + if (!sysfs_get_active_two(attr_sd)) + return -ENODEV; + + error = -EACCES; + if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap)) + goto err_out; + if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap)) + goto err_out; + + error = -ENOMEM; + bb = kzalloc(sizeof(*bb), GFP_KERNEL); + if (!bb) + goto err_out; + + bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!bb->buffer) + goto err_out; + + mutex_init(&bb->mutex); + file->private_data = bb; + + /* open succeeded, put active references */ + sysfs_put_active_two(attr_sd); + return 0; + + err_out: + sysfs_put_active_two(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_buffer *bb = file->private_data; + + if (bb->mmapped) + sysfs_put_active_two(attr_sd); + 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->sd || !attr); + + return sysfs_add_file(kobj->sd, &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) +{ + sysfs_hash_and_remove(kobj->sd, attr->attr.name); +} + +EXPORT_SYMBOL_GPL(sysfs_create_bin_file); +EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); |