diff --git a/fs/inode.c b/fs/inode.c index d8d04bd..92bb9b6 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -22,6 +22,7 @@ #include #include #include +#include /* * This is needed for the following functions: @@ -165,6 +166,9 @@ static struct inode *alloc_inode(struct } memset(&inode->u, 0, sizeof(inode->u)); inode->i_mapping = mapping; +#ifdef CONFIG_KEVENT_INODE + kevent_storage_init(KEVENT_INODE, KEVENT_MASK_ALL, inode, &inode->st); +#endif } return inode; } @@ -173,6 +177,9 @@ void destroy_inode(struct inode *inode) { if (inode_has_buffers(inode)) BUG(); +#ifdef CONFIG_KEVENT_INODE + kevent_storage_fini(&inode->st); +#endif security_inode_free(inode); if (inode->i_sb->s_op->destroy_inode) inode->i_sb->s_op->destroy_inode(inode); diff --git a/include/linux/fs.h b/include/linux/fs.h index cc35b6a..bda8114 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -224,6 +224,9 @@ extern int dir_notify_enable; #include #include #include +#ifdef CONFIG_KEVENT_INODE +#include +#endif struct iovec; struct nameidata; @@ -483,6 +486,10 @@ struct inode { struct semaphore inotify_sem; /* protects the watches list */ #endif +#ifdef CONFIG_KEVENT_INODE + struct kevent_storage st; +#endif + unsigned long i_state; unsigned long dirtied_when; /* jiffies of first dirtying */ diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h index 03b8e79..85449ac 100644 --- a/include/linux/fsnotify.h +++ b/include/linux/fsnotify.h @@ -15,6 +15,7 @@ #include #include +#include /* * fsnotify_move - file old_name at old_dir was moved to new_name at new_dir @@ -56,6 +57,7 @@ static inline void fsnotify_nameremove(s isdir = IN_ISDIR; dnotify_parent(dentry, DN_DELETE); inotify_dentry_parent_queue_event(dentry, IN_DELETE|isdir, 0, dentry->d_name.name); + kevent_inode_notify_parent(dentry, KEVENT_INODE_REMOVE); } /* @@ -65,6 +67,7 @@ static inline void fsnotify_inoderemove( { inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL); inotify_inode_is_dead(inode); + kevent_inode_remove(inode); } /* @@ -74,6 +77,7 @@ static inline void fsnotify_create(struc { inode_dir_notify(inode, DN_CREATE); inotify_inode_queue_event(inode, IN_CREATE, 0, name); + kevent_inode_notify(inode, KEVENT_INODE_CREATE); } /* @@ -83,6 +87,7 @@ static inline void fsnotify_mkdir(struct { inode_dir_notify(inode, DN_CREATE); inotify_inode_queue_event(inode, IN_CREATE | IN_ISDIR, 0, name); + kevent_inode_notify(inode, KEVENT_INODE_CREATE); } /* diff --git a/include/linux/kevent.h b/include/linux/kevent.h new file mode 100644 index 0000000..9a37036 --- /dev/null +++ b/include/linux/kevent.h @@ -0,0 +1,202 @@ +/* + * kevent.h + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __KEVENT_H +#define __KEVENT_H + +/* + * Kevent request flags. + */ + +#define KEVENT_REQ_ONESHOT 0x1 /* Process this event only once and then dequeue. */ + +/* + * Kevent return flags. + */ +#define KEVENT_RET_BROKEN 0x1 /* Kevent is broken. */ +#define KEVENT_RET_DONE 0x2 /* Kevent processing was finished successfully. */ + +/* + * Kevent type set. + */ +enum { + KEVENT_SOCKET = 0, + KEVENT_INODE, + KEVENT_TIMER, + + KEVENT_MAX, +}; + +/* + * Per-type event sets. + * Number of per-event sets should be exactly as number of kevent types. + */ + +/* + * Timer events. + */ +enum { + KEVENT_TIMER_FIRED = 0x1, +}; + +/* + * Socket events. + */ +enum { + KEVENT_SOCK_RECV = 0x1, + KEVENT_SOCK_ACCEPT = 0x2, +}; + +/* + * Inode events. + */ +enum { + KEVENT_INODE_CREATE = 0x1, + KEVENT_INODE_REMOVE = 0x2, +}; + + +#define KEVENT_MASK_ALL 0xffffffff /* Mask of all possible event values. */ + +struct kevent_id +{ + __u32 raw[2]; +}; + +struct ukevent +{ + struct kevent_id id; /* Id of this request, e.g. socket number, file descriptor and so on... */ + __u32 type; /* Event type, e.g. KEVENT_SOCK, KEVENT_INODE, KEVENT_TIMER and so on... */ + __u32 event; /* Event itself, e.g. SOCK_ACCEPT, INODE_CREATED, TIMER_FIRED... */ + __u32 req_flags; /* Per-event request flags */ + __u32 ret_flags; /* Per-event return flags */ + __u32 ret_data[2]; /* Event return data. Event originator fills it with anything it likes. */ + __u32 user[2]; /* User's data. It is not used, just copied to/from user. */ +}; + +struct kevent_user_control +{ + unsigned int cmd; /* Control command, e.g. KEVENT_ADD, KEVENT_REMOVE... */ + unsigned int num; /* Number of ukevents this strucutre controls. */ + unsigned int timeout; /* Timeout in milliseconds waiting for "ready_num" events to become ready. */ + unsigned int ready_num; /* Minimum number of ready events to inform about. */ +}; + +#define KEVENT_USER_SYMBOL 'K' +#define KEVENT_USER_CTL _IOWR(KEVENT_USER_SYMBOL, 0, struct kevent_user_control) +#define KEVENT_USER_WAIT _IOWR(KEVENT_USER_SYMBOL, 1, struct kevent_user_control) + +#ifdef __KERNEL__ + +#include +#include +#include +#include +#include + +struct inode; +struct dentry; + +struct kevent; +struct kevent_storage; +typedef int (* kevent_callback_t)(struct kevent *); + +struct kevent +{ + struct ukevent event; + spinlock_t lock; /* This lock protects ukevent manipulations, e.g. ret_flags changes. */ + + struct list_head kevent_entry; /* Entry of user's queue. */ + struct list_head entry; /* Entry of origin's queue or user's ready queue. */ + + struct kevent_user *user; /* User who requested this kevent. */ + struct kevent_storage *st; /* Kevent container. */ + + kevent_callback_t callback; /* Is called each time new event has been caught. */ + kevent_callback_t enqueue; /* Is called each time new event is queued. */ + kevent_callback_t dequeue; /* Is called each time new event is dequeued. */ +}; + +struct kevent_user +{ + struct kqueue queue; /* List of all kevents from this user. */ + spinlock_t lock; /* Protects all manipulations with queue of kevents. */ + + struct kqueue ready_queue; /* Kevent is dequeued from storage->queue and placed here when it is ready. */ + spinlock_t ready_lock; /* Protects all manipulations with ready queue. */ + + unsigned int max_ready_num; /* Requested number of kevents. */ + + struct semaphore ctl_mutex; /* Protects against simultaneous kevent_user control manipulations. */ + struct semaphore wait_mutex; /* Protects against simultaneous kevent_user waits. */ + wait_queue_head_t wait; /* Wait until some events are ready. */ + + atomic_t refcnt; /* Reference counter, increased for each new kevent. */ +}; + +#define KEVENT_MAX_REQUESTS PAGE_SIZE/sizeof(struct kevent) + +struct kevent *kevent_alloc(gfp_t mask); +void kevent_free(struct kevent *k); +int kevent_enqueue(struct kevent *k); +int kevent_dequeue(struct kevent *k); +int kevent_requeue(struct kevent *k); +int kevent_init(struct kevent *k); +struct kevent *__kqueue_dequeue_one(struct kqueue *q); +void __kqueue_dequeue_kevent(struct kqueue *q, struct kevent *k); +void __kqueue_enqueue_kevent(struct kqueue *q, struct kevent *k); +int kqueue_init(struct kqueue *q); +int kevent_storage_enqueue(struct kevent_storage *st, struct kevent *k); +void kevent_storage_dequeue(struct kevent_storage *st, struct kevent *k); + +#define list_for_each_entry_reverse_safe(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = n, n = list_entry(pos->member.prev, typeof(*pos), member)) + +int kevent_break(struct kevent *k); +int kevent_init(struct kevent *k); +int kevent_init_socket(struct kevent *k); +int kevent_init_inode(struct kevent *k); +int kevent_init_timer(struct kevent *k); + +void kevent_storage_ready(struct kevent_storage *st, kevent_callback_t ready_callback, u32 event); +int kevent_storage_init(__u32 type, __u32 event, void *origin, struct kevent_storage *st); +void kevent_storage_fini(struct kevent_storage *st); + +#ifdef CONFIG_KEVENT_INODE +void kevent_inode_notify(struct inode *inode, u32 event); +void kevent_inode_notify_parent(struct dentry *dentry, u32 event); +void kevent_inode_remove(struct inode *inode); +#else +static inline void kevent_inode_notify(struct inode *inode, u32 event) +{ +} +static inline void kevent_inode_notify_parent(struct dentry *dentry, u32 event) +{ +} +static inline void kevent_inode_remove(struct inode *inode) +{ +} +#endif /* CONFIG_KEVENT_INODE */ +#endif /* __KERNEL__ */ +#endif /* __KEVENT_H */ diff --git a/include/linux/kevent_storage.h b/include/linux/kevent_storage.h new file mode 100644 index 0000000..7cba0a8 --- /dev/null +++ b/include/linux/kevent_storage.h @@ -0,0 +1,19 @@ +#ifndef __KEVENT_STORAGE_H +#define __KEVENT_STORAGE_H + +struct kqueue +{ + struct list_head list; /* List of queued kevents. */ + unsigned int qlen; +}; + +struct kevent_storage +{ + __u32 type; /* Event type, e.g. KEVENT_SOCK, KEVENT_INODE, KEVENT_TIMER and so on... */ + __u32 event; /* Event itself, e.g. SOCK_ACCEPT, INODE_CREATED, TIMER_FIRED... */ + void *origin; /* Originator's pointer, e.g. struct sock or struct file. Can be NULL. */ + struct kqueue queue; /* Queue of registered users. */ + spinlock_t lock; /* Protects users queue. */ +}; + +#endif /* __KEVENT_STORAGE_H */ diff --git a/init/Kconfig b/init/Kconfig index 9fc0759..4a4f26c 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -224,6 +224,8 @@ config KOBJECT_UEVENT Say Y, unless you are building a system requiring minimal memory consumption. +source "kernel/kevent/Kconfig" + config IKCONFIG bool "Kernel .config support" ---help--- diff --git a/kernel/Makefile b/kernel/Makefile index 4f5a145..7c5aa88 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_GENERIC_HARDIRQS) += irq/ obj-$(CONFIG_CRASH_DUMP) += crash_dump.o obj-$(CONFIG_SECCOMP) += seccomp.o obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o +obj-$(CONFIG_KEVENT) += kevent/ ifneq ($(CONFIG_SCHED_NO_NO_OMIT_FRAME_POINTER),y) # According to Alan Modra , the -fno-omit-frame-pointer is diff --git a/kernel/kevent/Kconfig b/kernel/kevent/Kconfig new file mode 100644 index 0000000..c8a62e7 --- /dev/null +++ b/kernel/kevent/Kconfig @@ -0,0 +1,27 @@ +config KEVENT + bool "Kernel event notification mechanism" + help + This option enables event queue mechanism. + It can be used as replacement for poll()/select(), AIO callback invocations, + advanced timer notifications and other kernel object status changes. + +config KEVENT_SOCKET + bool "Kernel event notifications for sockets" + depends on NET && KEVENT + help + This option enables notifications through KEVENT subsystem of + sockets operations, like new packet receiving conditions, ready for accept + conditions and so on. + +config KEVENT_INODE + bool "Kernel event notifications for inodes" + depends on KEVENT + help + This option enables notifications through KEVENT subsystem of + inode operations, like file creation, removal and so on. + +config KEVENT_TIMER + bool "Kernel event notifications for timers" + depends on KEVENT + help + This option allows to use timers through KEVENT subsystem. diff --git a/kernel/kevent/Makefile b/kernel/kevent/Makefile new file mode 100644 index 0000000..b7999e4 --- /dev/null +++ b/kernel/kevent/Makefile @@ -0,0 +1,4 @@ +obj-y := kevent.o kevent_user.o kevent_init.o +obj-$(CONFIG_KEVENT_SOCKET) += kevent_socket.o +obj-$(CONFIG_KEVENT_INODE) += kevent_inode.o +obj-$(CONFIG_KEVENT_TIMER) += kevent_timer.o diff --git a/kernel/kevent/kevent.c b/kernel/kevent/kevent.c new file mode 100644 index 0000000..3df5fdf --- /dev/null +++ b/kernel/kevent/kevent.c @@ -0,0 +1,297 @@ +/* + * kevent.c + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static kmem_cache_t *kevent_cache; + +/* + * Attempts to add an event into appropriate origin's queue. + * Returns positive value if this event is ready immediately, + * negative value in case of error and zero if event has been queued. + * If event is ready it is not queued into origin's queue. + * ->enqueue() callback must increase origin's reference counter. + */ +int kevent_enqueue(struct kevent *k) +{ + if (k->event.type >= KEVENT_MAX) + return -E2BIG; + + if (!k->enqueue) { + kevent_break(k); + return -EINVAL; + } + + return k->enqueue(k); +} + +/* + * Remove event from the appropriate queue. + * ->dequeue() callback must decrease origin's reference counter. + */ +int kevent_dequeue(struct kevent *k) +{ + if (k->event.type >= KEVENT_MAX) + return -E2BIG; + + if (!k->dequeue) { + kevent_break(k); + return -EINVAL; + } + + return k->dequeue(k); +} + +/* + * Reference for storage is being held until we call kevent_dequeue() and + * thus ->dequeue() callback which will decrement origin's reference counter, + * so kevent->st pointer is still valid. + */ +int kevent_requeue(struct kevent *k) +{ + unsigned long flags; + + spin_lock_irqsave(&k->st->lock, flags); + spin_lock(&k->user->lock); + __kqueue_enqueue_kevent(&k->st->queue, k); + spin_unlock(&k->user->lock); + spin_unlock_irqrestore(&k->st->lock, flags); + return 0; +} + +/* + * Must be called before event is going to be added into some origin's queue. + * Initializes ->enqueue(), ->dequeue() and ->callback() callbacks. + * If failed, kevent should not be used or kevent_add() will fail to add + * this kevent into origin's queue with setting + * KEVENT_RET_BROKEN flag in kevent->event.ret_flags. + */ +int kevent_init(struct kevent *k) +{ + int err; + + spin_lock_init(&k->lock); + k->kevent_entry.next = LIST_POISON1; + k->entry.next = LIST_POISON1; + + if (k->event.type >= KEVENT_MAX) + return -E2BIG; + + switch (k->event.type) { + case KEVENT_SOCKET: + err = kevent_init_socket(k); + break; + case KEVENT_INODE: + err = kevent_init_inode(k); + break; + case KEVENT_TIMER: + err = kevent_init_timer(k); + break; + default: + err = -ENODEV; + } + + return err; +} + +/* + * Dequeue one entry from either origin's queue or user's ready queue. + */ +struct kevent *__kqueue_dequeue_one(struct kqueue *q) +{ + struct kevent *k = NULL; + + if (q->qlen) { + k = list_entry(q->list.next, struct kevent, entry); + list_del(&k->entry); + q->qlen--; + } + + return k; +} + +/* + * Dequeue kevent from either origin's queue or user's ready queue. + */ +void __kqueue_dequeue_kevent(struct kqueue *q, struct kevent *k) +{ + BUG_ON(k->entry.next == LIST_POISON1); + + list_del(&k->entry); + q->qlen--; +} + +/* + * Dequeue kevent into either origin's queue or user's ready queue. + */ +void __kqueue_enqueue_kevent(struct kqueue *q, struct kevent *k) +{ + BUG_ON(k->entry.next != LIST_POISON1); + + list_add_tail(&k->entry, &q->list); + q->qlen++; +} + +int kqueue_init(struct kqueue *q) +{ + INIT_LIST_HEAD(&q->list); + q->qlen = 0; + + return 0; +} + +/* + * Called from ->enqueue() callback when reference counter for given + * origin (socket, inode...) has been increased. + */ +int kevent_storage_enqueue(struct kevent_storage *st, struct kevent *k) +{ + unsigned long flags; + + k->st = st; + spin_lock_irqsave(&st->lock, flags); + __kqueue_enqueue_kevent(&st->queue, k); + spin_unlock_irqrestore(&st->lock, flags); + + return 0; +} + +/* + * Dequeue kevent from origin's queue. It does not decrese origin's reference counter in any way. + * It is called from ->dequeue() callback. + */ +void kevent_storage_dequeue(struct kevent_storage *st, struct kevent *k) +{ + unsigned long flags; + + spin_lock_irqsave(&st->lock, flags); + /* + * It looks a bit ugly, but there is notthing wrong with this assumption here. + * kevent_storage_dequeue() is only called from ->dequeue() callback, + * which in turn is called from kevent_dequeue() when we want to dequeue + * some kevent from either origin's or user's ready queue. + * But if kevent was in user's ready queue it can not be in storage queue, + * and this will trigger BUG_ON() check in __kqueue_dequeue_kevent(). + * Probably there should be some flag in kevent which states from which + * queue we dequeue this kevent, but I do not think it is too ugly + * to check here if kevent is still in the queue. + */ + if (k->entry.next != LIST_POISON1) + __kqueue_dequeue_kevent(&st->queue, k); + spin_unlock_irqrestore(&st->lock, flags); +} + +/* + * Called each time some activity in origin (socket, inode...) is noticed. + */ +void kevent_storage_ready(struct kevent_storage *st, kevent_callback_t ready_callback, u32 event) +{ + unsigned long flags; + struct kevent *k, *n; + int err, broken; + + spin_lock_irqsave(&st->lock, flags); + list_for_each_entry_safe(k, n, &st->queue.list, entry) { + if (ready_callback) + ready_callback(k); + + spin_lock(&k->lock); + if (!(k->event.event & event)) { + spin_unlock(&k->lock); + continue; + } + + broken = (k->event.ret_flags & KEVENT_RET_BROKEN); + spin_unlock(&k->lock); + + err = k->callback(k); + if (err < 0) + kevent_break(k); + if (err || broken) { + spin_lock(&k->lock); + k->event.ret_flags |= KEVENT_RET_DONE; + spin_unlock(&k->lock); + + spin_lock(&k->user->ready_lock); + __kqueue_dequeue_kevent(&st->queue, k); + __kqueue_enqueue_kevent(&k->user->ready_queue, k); + wake_up(&k->user->wait); + spin_unlock(&k->user->ready_lock); + } + } + spin_unlock_irqrestore(&st->lock, flags); +} + +int kevent_storage_init(__u32 type, __u32 event, void *origin, struct kevent_storage *st) +{ + spin_lock_init(&st->lock); + st->type = type; + st->event = event; + st->origin = origin; + return kqueue_init(&st->queue); +} + +void kevent_storage_fini(struct kevent_storage *st) +{ + kevent_storage_ready(st, kevent_break, KEVENT_MASK_ALL); +} + +struct kevent *kevent_alloc(gfp_t mask) +{ + struct kevent *k; + + if (kevent_cache) + k = kmem_cache_alloc(kevent_cache, mask); + else + k = kzalloc(sizeof(struct kevent), mask); + + return k; +} + +void kevent_free(struct kevent *k) +{ + if (kevent_cache) + kmem_cache_free(kevent_cache, k); + else + kfree(k); +} + +int __init kevent_sys_init(void) +{ + int err = 0; + + kevent_cache = kmem_cache_create("kevent_cache", sizeof(struct kevent), + 0, 0, NULL, NULL); + if (!kevent_cache) + err = -ENOMEM; + + return err; +} + +late_initcall(kevent_sys_init); diff --git a/kernel/kevent/kevent_init.c b/kernel/kevent/kevent_init.c new file mode 100644 index 0000000..d97a323 --- /dev/null +++ b/kernel/kevent/kevent_init.c @@ -0,0 +1,62 @@ +/* + * kevent_init.c + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +int kevent_break(struct kevent *k) +{ + unsigned long flags; + + spin_lock_irqsave(&k->lock, flags); + k->event.ret_flags |= KEVENT_RET_BROKEN; + spin_unlock_irqrestore(&k->lock, flags); + printk("%s: k=%p.\n", __func__, k); + return 0; +} + +#ifndef CONFIG_KEVENT_SOCKET +int kevent_init_socket(struct kevent *k) +{ + kevent_break(k); + return -ENODEV; +} +#endif + +#ifndef CONFIG_KEVENT_INODE +int kevent_init_inode(struct kevent *k) +{ + kevent_break(k); + return -ENODEV; +} +#endif + +#ifndef CONFIG_KEVENT_TIMER +int kevent_init_timer(struct kevent *k) +{ + kevent_break(k); + return -ENODEV; +} +#endif diff --git a/kernel/kevent/kevent_inode.c b/kernel/kevent/kevent_inode.c new file mode 100644 index 0000000..3af0e11 --- /dev/null +++ b/kernel/kevent/kevent_inode.c @@ -0,0 +1,110 @@ +/* + * kevent_inode.c + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int kevent_inode_enqueue(struct kevent *k) +{ + struct file *file; + struct inode *inode; + int err, fput_needed; + + file = fget_light(k->event.id.raw[0], &fput_needed); + if (!file) + return -ENODEV; + + err = -EINVAL; + if (!file->f_dentry || !file->f_dentry->d_inode) + goto err_out_fput; + + inode = igrab(file->f_dentry->d_inode); + if (!inode) + goto err_out_fput; + + err = kevent_storage_enqueue(&inode->st, k); + if (err) + goto err_out_iput; + + fput_light(file, fput_needed); + return 0; + +err_out_iput: + iput(inode); +err_out_fput: + fput_light(file, fput_needed); + return err; +} + +static int kevent_inode_dequeue(struct kevent *k) +{ + struct inode *inode = k->st->origin; + + kevent_storage_dequeue(k->st, k); + iput(inode); + + return 0; +} + +static int kevent_inode_callback(struct kevent *k) +{ + return 1; +} + +int kevent_init_inode(struct kevent *k) +{ + k->enqueue = &kevent_inode_enqueue; + k->dequeue = &kevent_inode_dequeue; + k->callback = &kevent_inode_callback; + return 0; +} + +void kevent_inode_notify_parent(struct dentry *dentry, u32 event) +{ + struct dentry *parent; + struct inode *inode; + + spin_lock(&dentry->d_lock); + parent = dentry->d_parent; + inode = parent->d_inode; + + dget(parent); + spin_unlock(&dentry->d_lock); + kevent_inode_notify(inode, KEVENT_INODE_REMOVE); + dput(parent); +} + +void kevent_inode_remove(struct inode *inode) +{ + kevent_storage_fini(&inode->st); +} + +void kevent_inode_notify(struct inode *inode, u32 event) +{ + kevent_storage_ready(&inode->st, NULL, event); +} diff --git a/kernel/kevent/kevent_timer.c b/kernel/kevent/kevent_timer.c new file mode 100644 index 0000000..17dbd46 --- /dev/null +++ b/kernel/kevent/kevent_timer.c @@ -0,0 +1,112 @@ +/* + * kevent_timer.c + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void kevent_timer_func(unsigned long data) +{ + struct kevent *k = (struct kevent *)data; + struct timer_list *t = k->st->origin; + + kevent_storage_ready(k->st, NULL, KEVENT_MASK_ALL); + mod_timer(t, jiffies + msecs_to_jiffies(k->event.id.raw[0])); +} + +static int kevent_timer_enqueue(struct kevent *k) +{ + struct timer_list *t; + struct kevent_storage *st; + int err; + + t = kmalloc(sizeof(struct timer_list) + sizeof(struct kevent_storage), GFP_KERNEL); + if (!t) + return -ENOMEM; + + init_timer(t); + t->function = kevent_timer_func; + t->expires = jiffies + msecs_to_jiffies(k->event.id.raw[0]); + t->data = (unsigned long)k; + + st = (struct kevent_storage *)(t+1); + err = kevent_storage_init(k->event.type, k->event.event, t, st); + if (err) + goto err_out_free; + + err = kevent_storage_enqueue(st, k); + if (err) + goto err_out_st_fini; + + add_timer(t); + + return 0; + +err_out_st_fini: + kevent_storage_fini(st); +err_out_free: + kfree(t); + + return err; +} + +static int kevent_timer_dequeue(struct kevent *k) +{ + struct kevent_storage *st = k->st; + struct timer_list *t = st->origin; + + if (!t) + return -ENODEV; + + del_timer_sync(t); + + kevent_storage_dequeue(st, k); + + kfree(t); + + return 0; +} + +static int kevent_timer_callback(struct kevent *k) +{ + struct kevent_storage *st = k->st; + struct timer_list *t = st->origin; + + if (!t) + return -ENODEV; + + k->event.ret_data[0] = (__u32)jiffies; + k->event.ret_data[1] = (__u32)((unsigned long)t & 0xffffffff); + return 1; +} + +int kevent_init_timer(struct kevent *k) +{ + k->enqueue = &kevent_timer_enqueue; + k->dequeue = &kevent_timer_dequeue; + k->callback = &kevent_timer_callback; + return 0; +} diff --git a/kernel/kevent/kevent_user.c b/kernel/kevent/kevent_user.c new file mode 100644 index 0000000..f8890d9 --- /dev/null +++ b/kernel/kevent/kevent_user.c @@ -0,0 +1,340 @@ +/* + * kevent_user.c + * + * 2006 Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct class *kevent_user_class; +static char kevent_name[] = "kevent"; +static int kevent_user_major; + +static int kevent_user_open(struct inode *, struct file *); +static int kevent_user_release(struct inode *, struct file *); +static int kevent_user_ioctl(struct inode *, struct file *, unsigned int, unsigned long); + +static struct file_operations kevent_user_fops = { + .open = kevent_user_open, + .release = kevent_user_release, + .ioctl = kevent_user_ioctl, + .owner = THIS_MODULE, +}; + +static int kevent_user_open(struct inode *inode, struct file *file) +{ + struct kevent_user *u; + + u = kzalloc(sizeof(struct kevent_user), GFP_KERNEL); + if (!u) + return -ENOMEM; + + kqueue_init(&u->queue); + spin_lock_init(&u->lock); + + kqueue_init(&u->ready_queue); + spin_lock_init(&u->ready_lock); + + init_MUTEX(&u->ctl_mutex); + init_MUTEX(&u->wait_mutex); + init_waitqueue_head(&u->wait); + u->max_ready_num = 0; + + atomic_set(&u->refcnt, 1); + + file->private_data = u; + + return 0; +} + +static inline void kevent_user_get(struct kevent_user *u) +{ + atomic_inc(&u->refcnt); +} + +static inline void kevent_user_put(struct kevent_user *u) +{ + if (atomic_dec_and_test(&u->refcnt)) + kfree(u); +} + +/* + * Remove kevent from user->queue, dequeue it from storage and decrease user's reference counter, + * since this kevent does not exist anymore. That is why it is freed here. + */ +static void kevent_finish_user(struct kevent *k, int lock) +{ + struct kevent_user *u = k->user; + unsigned long flags; + + if (lock) { + spin_lock_irqsave(&u->lock, flags); + list_del(&k->kevent_entry); + u->queue.qlen--; + spin_unlock_irqrestore(&u->lock, flags); + } else { + list_del(&k->kevent_entry); + u->queue.qlen--; + } + kevent_dequeue(k); + kevent_user_put(u); + kevent_free(k); +} + +static struct kevent *kqueue_dequeue_ready(struct kevent_user *u) +{ + unsigned long flags; + struct kevent *k; + + spin_lock_irqsave(&u->ready_lock, flags); + k = __kqueue_dequeue_one(&u->ready_queue); + spin_unlock_irqrestore(&u->ready_lock, flags); + + return k; +} + +/* + * No new entry can be added or removed from any list at this point. + * It is not permitted to call ->ioctl() and ->release() in parallel. + */ +static int kevent_user_release(struct inode *inode, struct file *file) +{ + struct kevent_user *u = file->private_data; + struct kevent *k, *n; + unsigned long flags; + + spin_lock_irqsave(&u->lock, flags); + list_for_each_entry_safe(k, n, &u->queue.list, kevent_entry) + kevent_finish_user(k, 0); + spin_unlock_irqrestore(&u->lock, flags); + + kevent_user_put(u); + file->private_data = NULL; + + return 0; +} + +/* + * Copy all ukevents from userspace, allocate kevent for each one and add them + * into appropriate kevent_storages, e.g. sockets, inodes and so on... + * If something goes wrong, all events will be dequeued and negative error will be returned. + * On success zero is returned and ctl->num will be a number of finished events, either completed + * or failed. Array of finished events (struct ukevent) will be placed behind kevent_user_control structure. + * User must run through that array and check ret_flags field of each ukevent structure + * to determine if it is fired or failed event. + */ +static int kevent_user_ctl(struct kevent_user *u, struct kevent_user_control *ctl, unsigned long arg) +{ + int err = 0, cerr = 0, num = 0, knum = 0, i; + struct kevent *k, *n; + void __user *orig, *ctl_addr; + unsigned long flags; + + if (down_interruptible(&u->ctl_mutex)) + return -ERESTARTSYS; + + orig = (void __user *)arg; + ctl_addr = (void __user *)(arg - sizeof(struct kevent_user_control)); + + for (i=0; inum; ++i) { + k = kevent_alloc(GFP_KERNEL); + if (!k) { + err = -ENOMEM; + break; + } + + if (copy_from_user(&k->event, (const void __user *)arg, sizeof(struct ukevent))) { + kevent_free(k); + err = -EINVAL; + break; + } + + arg += sizeof(struct ukevent); + + err = kevent_init(k); + if (err) { + kevent_free(k); + break; + } + k->user = u; + + err = kevent_enqueue(k); + if (err) { + if (copy_to_user(orig, &k->event, sizeof(struct ukevent))) + cerr = -EINVAL; + + orig += sizeof(struct ukevent); + kevent_free(k); + num++; + } else { + spin_lock_irqsave(&u->lock, flags); + list_add_tail(&k->kevent_entry, &u->queue.list); + u->queue.qlen++; + kevent_user_get(u); + spin_unlock_irqrestore(&u->lock, flags); + knum++; + } + } + + if (err) + goto err_out_remove; + + ctl->num = num; + if (copy_to_user(ctl_addr, ctl, sizeof(struct kevent_user_control))) + cerr = -EINVAL; + + if (cerr) + err = cerr; + +err_out_remove: + if (err && knum) { + num = 0; + /* + * Dequeue the rest of successfully added kevents. + */ + spin_lock_irqsave(&u->lock, flags); + list_for_each_entry_reverse_safe(k, n, &u->queue.list, kevent_entry) { + kevent_finish_user(k, 0); + + if (++num == knum) + break; + } + spin_unlock_irqrestore(&u->lock, flags); + } + up(&u->ctl_mutex); + + return err; +} + +/* + * Waits until at least ctl->ready_num events are ready or timeout and returns + * number of ready events (in case of timeout) or number of requested events. + */ +static int kevent_user_wait(struct kevent_user *u, struct kevent_user_control *ctl, unsigned long arg) +{ + struct kevent *k; + int cerr = 0, num = 0; + void __user *ptr = (void __user *)(arg + sizeof(struct kevent_user_control)); + + if (down_interruptible(&u->wait_mutex)) + return -ERESTARTSYS; + + wait_event_interruptible_timeout(u->wait, + u->ready_queue.qlen >= ctl->ready_num, msecs_to_jiffies(ctl->timeout)); + while (num < ctl->num && (k = kqueue_dequeue_ready(u))) { + if (copy_to_user(ptr + num*sizeof(struct ukevent), &k->event, sizeof(struct ukevent))) + cerr = -EINVAL; + /* + * If it is one-shot kevent, it has been removed already from + * origin's queue, so we can easily free it here. + */ + if (k->event.req_flags & KEVENT_REQ_ONESHOT) + kevent_finish_user(k, 1); + else + kevent_requeue(k); + ++num; + } + + ctl->num = num; + if (copy_to_user((void __user *)arg, ctl, sizeof(struct kevent_user_control))) + cerr = -EINVAL; + + up(&u->wait_mutex); + + return (cerr)?cerr:num; +} + +static int kevent_user_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int err = -ENODEV; + struct kevent_user_control ctl; + struct kevent_user *u = file->private_data; + + if (copy_from_user(&ctl, (const void __user *)arg, sizeof(struct kevent_user_control))) + return -EINVAL; + + switch (cmd) { + case KEVENT_USER_CTL: + err = kevent_user_ctl(u, &ctl, arg+sizeof(struct kevent_user_control)); + break; + case KEVENT_USER_WAIT: + err = kevent_user_wait(u, &ctl, arg); + default: + break; + } + + return err; +} + +static int __devinit kevent_user_init(void) +{ + struct class_device *dev; + int err = 0; + + kevent_user_major = register_chrdev(0, kevent_name, &kevent_user_fops); + if (kevent_user_major < 0) { + printk(KERN_ERR "Failed to register \"%s\" char device: err=%d.\n", kevent_name, kevent_user_major); + return -ENODEV; + } + + kevent_user_class = class_create(THIS_MODULE, "kevent"); + if (IS_ERR(kevent_user_class)) { + printk(KERN_ERR "Failed to register \"%s\" class: err=%ld.\n", kevent_name, PTR_ERR(kevent_user_class)); + err = PTR_ERR(kevent_user_class); + goto err_out_unregister; + } + + dev = class_device_create(kevent_user_class, NULL, MKDEV(kevent_user_major, 0), NULL, kevent_name); + if (IS_ERR(dev)) { + printk(KERN_ERR "Failed to create %d.%d class device in \"%s\" class: err=%ld.\n", + kevent_user_major, 0, kevent_name, PTR_ERR(dev)); + err = PTR_ERR(dev); + goto err_out_class_destroy; + } + + printk("KEVENT subsystem: chardev helper: major=%d.\n", kevent_user_major); + + return 0; + +err_out_class_destroy: + class_destroy(kevent_user_class); +err_out_unregister: + unregister_chrdev(kevent_user_major, kevent_name); + + return err; +} + +static void __devexit kevent_user_fini(void) +{ + class_device_destroy(kevent_user_class, MKDEV(kevent_user_major, 0)); + class_destroy(kevent_user_class); + unregister_chrdev(kevent_user_major, kevent_name); +} + +module_init(kevent_user_init); +module_exit(kevent_user_fini);