From neilb@suse.de Tue Dec 20 15:19:48 2005 From: Neil Brown To: Greg KH , Date: Wed, 21 Dec 2005 10:14:29 +1100 Message-ID: <17320.36949.269788.520946@cse.unsw.edu.au> Subject: [PATCH - 2.6.15-rc5-mm3] Allow sysfs attribute files to be pollable. This allows an attribute file in sysfs to be polled for activity. It works like this: Open the file Read all the contents. Call poll requesting POLLERR or POLLPRI (so select/exceptfds works) When poll returns, close the file, and go to top of loop. Events are signaled by an object manager calling sysfs_notify(kobj, dir, attr); If the dir is non-NULL, it is used to find a subdirectory which contains the attribute (presumably created by sysfs_create_group). This has a cost of one int per attribute, one wait_queuehead per kobject, one int per open file. The name "sysfs_notify" may be confused with the inotify functionality. Maybe it would be nice to support inotify for sysfs attributes as well? This patch also uses sysfs_notify to allow /sys/block/md*/md/sync_action to be pollable Signed-off-by: Neil Brown Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 1 + fs/sysfs/file.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ fs/sysfs/inode.c | 21 +++++++++++++++++++++ fs/sysfs/sysfs.h | 1 + include/linux/kobject.h | 2 ++ include/linux/sysfs.h | 7 +++++++ lib/kobject.c | 1 + 7 files changed, 80 insertions(+) --- gregkh-2.6.orig/fs/sysfs/dir.c +++ gregkh-2.6/fs/sysfs/dir.c @@ -43,6 +43,7 @@ static struct sysfs_dirent * sysfs_new_d 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; --- gregkh-2.6.orig/fs/sysfs/file.c +++ gregkh-2.6/fs/sysfs/file.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -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 dentr 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); @@ -349,12 +353,55 @@ static int sysfs_release(struct inode * 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. + */ +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; + + return res; +} + +void sysfs_notify(struct kobject * k, char *dir, char *attr) +{ + struct sysfs_dirent * sd = k->dentry->d_fsdata; + if (sd && dir) + sd = sysfs_find(sd, dir); + if (sd && attr) + sd = sysfs_find(sd, attr); + if (sd) { + atomic_inc(&sd->s_event); + wake_up_interruptible(&k->poll); + } +} +EXPORT_SYMBOL_GPL(sysfs_notify); + 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, }; --- gregkh-2.6.orig/fs/sysfs/inode.c +++ gregkh-2.6/fs/sysfs/inode.c @@ -246,3 +246,24 @@ void sysfs_hash_and_remove(struct dentry } mutex_unlock(&dir->d_inode->i_mutex); } + +struct sysfs_dirent *sysfs_find(struct sysfs_dirent *dir, const char * name) +{ + struct sysfs_dirent * sd, * rv = NULL; + + if (dir->s_dentry == NULL || + dir->s_dentry->d_inode == NULL) + return NULL; + + mutex_lock(&dir->s_dentry->d_inode->i_mutex); + list_for_each_entry(sd, &dir->s_children, s_sibling) { + if (!sd->s_element) + continue; + if (!strcmp(sysfs_get_name(sd), name)) { + rv = sd; + break; + } + } + mutex_unlock(&dir->s_dentry->d_inode->i_mutex); + return rv; +} --- gregkh-2.6.orig/fs/sysfs/sysfs.h +++ gregkh-2.6/fs/sysfs/sysfs.h @@ -10,6 +10,7 @@ extern int sysfs_make_dirent(struct sysf 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 *); --- gregkh-2.6.orig/include/linux/kobject.h +++ gregkh-2.6/include/linux/kobject.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #define KOBJ_NAME_LEN 20 @@ -54,6 +55,7 @@ struct kobject { struct kset * kset; struct kobj_type * ktype; struct dentry * dentry; + wait_queue_head_t poll; }; extern int kobject_set_name(struct kobject *, const char *, ...) --- gregkh-2.6.orig/include/linux/sysfs.h +++ gregkh-2.6/include/linux/sysfs.h @@ -74,6 +74,7 @@ struct sysfs_dirent { umode_t s_mode; struct dentry * s_dentry; struct iattr * s_iattr; + atomic_t s_event; }; #define SYSFS_ROOT 0x0001 @@ -118,6 +119,7 @@ int sysfs_remove_bin_file(struct kobject int sysfs_create_group(struct kobject *, const struct attribute_group *); void sysfs_remove_group(struct kobject *, const struct attribute_group *); +void sysfs_notify(struct kobject * k, char *dir, char *attr); #else /* CONFIG_SYSFS */ static inline int sysfs_create_dir(struct kobject * k) @@ -185,6 +187,11 @@ static inline void sysfs_remove_group(st ; } +static inline void sysfs_notify(struct kobject * k, char *dir, char *attr) +{ + ; +} + #endif /* CONFIG_SYSFS */ #endif /* _SYSFS_H_ */ --- gregkh-2.6.orig/lib/kobject.c +++ gregkh-2.6/lib/kobject.c @@ -129,6 +129,7 @@ void kobject_init(struct kobject * kobj) WARN_ON(atomic_read(&kobj->kref.refcount)); kref_init(&kobj->kref); INIT_LIST_HEAD(&kobj->entry); + init_waitqueue_head(&kobj->poll); kobj->kset = kset_get(kobj->kset); }