aboutsummaryrefslogtreecommitdiff
path: root/fs/sysfs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/sysfs')
-rw-r--r--fs/sysfs/dir.c3
-rw-r--r--fs/sysfs/file.c78
-rw-r--r--fs/sysfs/inode.c3
-rw-r--r--fs/sysfs/sysfs.h1
4 files changed, 81 insertions, 4 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index f26880a4785..610b5bdbe75 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -43,6 +43,7 @@ static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd,
memset(sd, 0, sizeof(*sd));
atomic_set(&sd->s_count, 1);
+ atomic_set(&sd->s_event, 0);
INIT_LIST_HEAD(&sd->s_children);
list_add(&sd->s_sibling, &parent_sd->s_children);
sd->s_element = element;
@@ -50,7 +51,7 @@ static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd,
return sd;
}
-/**
+/*
*
* Return -EEXIST if there is already a sysfs element with the same name for
* the same parent.
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index 830f76fa098..cf3786625bf 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -6,6 +6,7 @@
#include <linux/fsnotify.h>
#include <linux/kobject.h>
#include <linux/namei.h>
+#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
@@ -57,6 +58,7 @@ struct sysfs_buffer {
struct sysfs_ops * ops;
struct semaphore sem;
int needs_read_fill;
+ int event;
};
@@ -72,6 +74,7 @@ struct sysfs_buffer {
*/
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
+ struct sysfs_dirent * sd = dentry->d_fsdata;
struct attribute * attr = to_attr(dentry);
struct kobject * kobj = to_kobj(dentry->d_parent);
struct sysfs_ops * ops = buffer->ops;
@@ -83,6 +86,7 @@ static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer
if (!buffer->page)
return -ENOMEM;
+ buffer->event = atomic_read(&sd->s_event);
count = ops->show(kobj,attr,buffer->page);
buffer->needs_read_fill = 0;
BUG_ON(count > (ssize_t)PAGE_SIZE);
@@ -183,7 +187,7 @@ fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t
return -ENOMEM;
if (count >= PAGE_SIZE)
- count = PAGE_SIZE;
+ count = PAGE_SIZE - 1;
error = copy_from_user(buffer->page,buf,count);
buffer->needs_read_fill = 1;
return error ? -EFAULT : count;
@@ -348,12 +352,84 @@ static int sysfs_release(struct inode * inode, struct file * filp)
return 0;
}
+/* Sysfs attribute files are pollable. The idea is that you read
+ * the content and then you use 'poll' or 'select' to wait for
+ * the content to change. When the content changes (assuming the
+ * manager for the kobject supports notification), poll will
+ * return POLLERR|POLLPRI, and select will return the fd whether
+ * it is waiting for read, write, or exceptions.
+ * Once poll/select indicates that the value has changed, you
+ * need to close and re-open the file, as simply seeking and reading
+ * again will not get new data, or reset the state of 'poll'.
+ * Reminder: this only works for attributes which actively support
+ * it, and it is not possible to test an attribute from userspace
+ * to see if it supports poll (Nether 'poll' or 'select' return
+ * an appropriate error code). When in doubt, set a suitable timeout value.
+ */
+static unsigned int sysfs_poll(struct file *filp, poll_table *wait)
+{
+ struct sysfs_buffer * buffer = filp->private_data;
+ struct kobject * kobj = to_kobj(filp->f_dentry->d_parent);
+ struct sysfs_dirent * sd = filp->f_dentry->d_fsdata;
+ int res = 0;
+
+ poll_wait(filp, &kobj->poll, wait);
+
+ if (buffer->event != atomic_read(&sd->s_event)) {
+ res = POLLERR|POLLPRI;
+ buffer->needs_read_fill = 1;
+ }
+
+ return res;
+}
+
+
+static struct dentry *step_down(struct dentry *dir, const char * name)
+{
+ struct dentry * de;
+
+ if (dir == NULL || dir->d_inode == NULL)
+ return NULL;
+
+ mutex_lock(&dir->d_inode->i_mutex);
+ de = lookup_one_len(name, dir, strlen(name));
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(dir);
+ if (IS_ERR(de))
+ return NULL;
+ if (de->d_inode == NULL) {
+ dput(de);
+ return NULL;
+ }
+ return de;
+}
+
+void sysfs_notify(struct kobject * k, char *dir, char *attr)
+{
+ struct dentry *de = k->dentry;
+ if (de)
+ dget(de);
+ if (de && dir)
+ de = step_down(de, dir);
+ if (de && attr)
+ de = step_down(de, attr);
+ if (de) {
+ struct sysfs_dirent * sd = de->d_fsdata;
+ if (sd)
+ atomic_inc(&sd->s_event);
+ wake_up_interruptible(&k->poll);
+ dput(de);
+ }
+}
+EXPORT_SYMBOL_GPL(sysfs_notify);
+
const struct file_operations sysfs_file_operations = {
.read = sysfs_read_file,
.write = sysfs_write_file,
.llseek = generic_file_llseek,
.open = sysfs_open_file,
.release = sysfs_release,
+ .poll = sysfs_poll,
};
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c
index 4c29ac41ac3..f0b347bd12c 100644
--- a/fs/sysfs/inode.c
+++ b/fs/sysfs/inode.c
@@ -175,8 +175,7 @@ const unsigned char * sysfs_get_name(struct sysfs_dirent *sd)
struct bin_attribute * bin_attr;
struct sysfs_symlink * sl;
- if (!sd || !sd->s_element)
- BUG();
+ BUG_ON(!sd || !sd->s_element);
switch (sd->s_type) {
case SYSFS_DIR:
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 32958a7c50e..3651ffb5ec0 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -11,6 +11,7 @@ extern int sysfs_make_dirent(struct sysfs_dirent *, struct dentry *, void *,
extern int sysfs_add_file(struct dentry *, const struct attribute *, int);
extern void sysfs_hash_and_remove(struct dentry * dir, const char * name);
+extern struct sysfs_dirent *sysfs_find(struct sysfs_dirent *dir, const char * name);
extern int sysfs_create_subdir(struct kobject *, const char *, struct dentry **);
extern void sysfs_remove_subdir(struct dentry *);