This is the core implementation of the kmon interface.
Signed-off-by: Davide Libenzi <[EMAIL PROTECTED]> - Davide Index: linux-2.6.21.scdev/include/linux/kmon.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21.scdev/include/linux/kmon.h 2007-05-06 14:10:51.000000000 -0700 @@ -0,0 +1,77 @@ +/* + * include/linux/kmon.h + * + * Copyright (C) 2007 Davide Libenzi <[EMAIL PROTECTED]> + * + */ + +#ifndef _LINUX_KMON_H +#define _LINUX_KMON_H + +#define KMON_START _IO('K', 1) +#define KMON_STOP _IO('K', 2) +#define KMON_BUFFSIZE _IOR('K', 3, long) + + +#ifdef __KERNEL__ + +struct kmon_ring { + unsigned long head, tail; + unsigned long size; + void *data; +}; + +struct kmon_buffer { + void *data; + unsigned long size; +}; + +struct kmon_notifier { + struct list_head lnk; + void (*write)(struct kmon_notifier *, const struct kmon_buffer *, int); +}; + +int kmon_ring_write(struct kmon_ring *rng, const struct kmon_buffer *kbuf, int n); +unsigned long kmon_ring_read(struct kmon_ring *rng, struct kmon_buffer *kbuf); +void kmon_ring_commit_read(struct kmon_ring *rng, unsigned long size); +int kmon_add_data(const void *data, unsigned long size); +void kmon_notify_add(struct kmon_notifier *knfy); +void kmon_notify_remove(struct kmon_notifier *knfy); +int kmon_setup(void); +void kmon_cleanup(void); + +unsigned long kmon_ring_avail(const struct kmon_ring *rng) +{ + return (rng->head - rng->tail) & (rng->size - 1); +} + +static inline void kmon_put16(unsigned char *buf, u16 v) +{ + *buf++ = (unsigned char) v; + *buf = (unsigned char) (v >> 8); +} + +static inline void kmon_put32(unsigned char *buf, u32 v) +{ + *buf++ = (unsigned char) v; + *buf++ = (unsigned char) (v >> 8); + *buf++ = (unsigned char) (v >> 16); + *buf = (unsigned char) (v >> 24); +} + +static inline void kmon_put64(unsigned char *buf, u64 v) +{ + *buf++ = (unsigned char) v; + *buf++ = (unsigned char) (v >> 8); + *buf++ = (unsigned char) (v >> 16); + *buf++ = (unsigned char) (v >> 24); + *buf++ = (unsigned char) (v >> 32); + *buf++ = (unsigned char) (v >> 40); + *buf++ = (unsigned char) (v >> 48); + *buf = (unsigned char) (v >> 56); +} + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_KMON_H */ + Index: linux-2.6.21.scdev/drivers/char/kmon-dev.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21.scdev/drivers/char/kmon-dev.c 2007-05-06 14:10:51.000000000 -0700 @@ -0,0 +1,268 @@ +/* + * drivers/char/kmon-dev.c + * + * Copyright (C) 2007 Davide Libenzi <[EMAIL PROTECTED]> + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/file.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/log2.h> +#include <linux/kmon.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define KMON_BUFSIZE_DEFAULT (1024 * 1024) +#define KMON "kmon v0.17" + +#define KMON_FL_OVERFLOW (1 << 0) + +struct kmon_ctx { + struct kmon_notifier knfy; + struct mutex mtx; + wait_queue_head_t wqh; + struct kmon_ring rng; + unsigned long flags; +}; + +static int kmon_alloc_ring(struct kmon_ring *rng, unsigned long size) +{ + size = roundup_pow_of_two(size); + rng->data = vmalloc(size); + if (rng->data == NULL) + return -ENOMEM; + rng->head = rng->tail = 0; + rng->size = size; + + return 0; +} + +static void kmon_nfy_write(struct kmon_notifier *knfy, const struct kmon_buffer *kbuf, + int n) +{ + struct kmon_ctx *ctx = container_of(knfy, struct kmon_ctx, knfy); + int error; + + mutex_lock(&ctx->mtx); + if (likely((ctx->flags & KMON_FL_OVERFLOW) == 0)) { + error = kmon_ring_write(&ctx->rng, kbuf, n); + if (unlikely(error)) + ctx->flags |= KMON_FL_OVERFLOW; + } + if (waitqueue_active(&ctx->wqh)) + wake_up_locked(&ctx->wqh); + mutex_unlock(&ctx->mtx); +} + +static int kmon_open(struct inode *inode, struct file *file) +{ + int error; + struct kmon_ctx *ctx; + + ctx = (struct kmon_ctx *) kzalloc(sizeof(struct kmon_ctx), GFP_KERNEL); + if (ctx == NULL) + return -ENOMEM; + error = kmon_alloc_ring(&ctx->rng, KMON_BUFSIZE_DEFAULT); + if (error) { + kfree(ctx); + return error; + } + init_waitqueue_head(&ctx->wqh); + mutex_init(&ctx->mtx); + + file->private_data = ctx; + + return 0; +} + +static int kmon_release(struct inode *inode, struct file *file) +{ + struct kmon_ctx *ctx = file->private_data; + + if (ctx->knfy.write != NULL) + kmon_notify_remove(&ctx->knfy); + vfree(ctx->rng.data); + mutex_destroy(&ctx->mtx); + kfree(ctx); + + return 0; +} + +static unsigned int kmon_poll(struct file *file, poll_table *wait) +{ + struct kmon_ctx *ctx = file->private_data; + unsigned int events = 0; + + poll_wait(file, &ctx->wqh, wait); + + mutex_lock(&ctx->mtx); + if (ctx->flags & KMON_FL_OVERFLOW) + events |= POLLERR; + if (ctx->rng.head != ctx->rng.tail) + events |= POLLIN; + mutex_unlock(&ctx->mtx); + + return events; +} + +static ssize_t kmon_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct kmon_ctx *ctx = file->private_data; + ssize_t res, i; + unsigned long size, bcnt; + DECLARE_WAITQUEUE(wait, current); + struct kmon_buffer kbuf[2]; + + mutex_lock(&ctx->mtx); + res = -EAGAIN; + size = kmon_ring_read(&ctx->rng, kbuf); + if (size == 0 && !(file->f_flags & O_NONBLOCK)) { + __add_wait_queue(&ctx->wqh, &wait); + for (res = 0;;) { + set_current_state(TASK_INTERRUPTIBLE); + size = kmon_ring_read(&ctx->rng, kbuf); + if (size != 0) { + res = 0; + break; + } + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + mutex_unlock(&ctx->mtx); + schedule(); + mutex_lock(&ctx->mtx); + } + __remove_wait_queue(&ctx->wqh, &wait); + __set_current_state(TASK_RUNNING); + } + if (size != 0) { + res = -EFAULT; + for (i = 0, size = 0; size < (unsigned long) count && + i < 2; i++) { + bcnt = min(kbuf[i].size, (unsigned long) count - size); + if (bcnt != 0 && + copy_to_user(buf + size, kbuf[i].data, bcnt)) + goto out_unlock; + size += bcnt; + } + kmon_ring_commit_read(&ctx->rng, size); + res = (ssize_t) size; + } +out_unlock: + mutex_unlock(&ctx->mtx); + + return res; +} + +static int kmon_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct kmon_ctx *ctx = file->private_data; + int error = -EINVAL; + unsigned long size; + + switch (cmd) { + case KMON_START: + mutex_lock(&ctx->mtx); + error = -ENOBUFS; + if (ctx->rng.data != NULL) { + if (ctx->knfy.write == NULL) { + ctx->rng.head = ctx->rng.tail = 0; + ctx->flags &= ~KMON_FL_OVERFLOW; + ctx->knfy.write = kmon_nfy_write; + kmon_notify_add(&ctx->knfy); + error = 0; + } else + error = -EALREADY; + } + mutex_unlock(&ctx->mtx); + break; + + case KMON_STOP: + mutex_lock(&ctx->mtx); + if (ctx->knfy.write != NULL) { + kmon_notify_remove(&ctx->knfy); + ctx->knfy.write = NULL; + error = 0; + } else + error = -EALREADY; + mutex_unlock(&ctx->mtx); + break; + + case KMON_BUFFSIZE: + if (copy_from_user(&size, (void __user *) arg, sizeof(size))) + return -EFAULT; + mutex_lock(&ctx->mtx); + if (ctx->knfy.write == NULL) { + vfree(ctx->rng.data); + ctx->rng.data = NULL; + error = kmon_alloc_ring(&ctx->rng, size); + } else + error = -EBUSY; + mutex_unlock(&ctx->mtx); + break; + } + + return error; +} + +static const struct file_operations kmon_fops = { + .poll = kmon_poll, + .read = kmon_read, + .ioctl = kmon_ioctl, + .open = kmon_open, + .release = kmon_release, +}; + +static struct miscdevice kmon_dev = { + MISC_DYNAMIC_MINOR, + "kmon", + &kmon_fops +}; + +static int __init kmon_init(void) +{ + int error; + + error = kmon_setup(); + if (error) + return error; + + error = misc_register(&kmon_dev); + if (error) { + printk(KERN_ERR KMON " - unable to register misc device.\n"); + goto out_kmon_cleanup; + } + + return 0; + +out_kmon_cleanup: + kmon_cleanup(); + return error; +} +module_init(kmon_init); + +static void __exit kmon_exit(void) +{ + misc_deregister(&kmon_dev); + kmon_cleanup(); +} +module_exit(kmon_exit); + +MODULE_AUTHOR("Davide Libenzi"); +MODULE_LICENSE("GPL"); + Index: linux-2.6.21.scdev/kernel/kmon.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21.scdev/kernel/kmon.c 2007-05-06 14:10:51.000000000 -0700 @@ -0,0 +1,310 @@ +/* + * kernel/kmon.c + * + * Copyright (C) 2007 Davide Libenzi <[EMAIL PROTECTED]> + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/file.h> +#include <linux/poll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/rcupdate.h> +#include <linux/workqueue.h> +#include <linux/log2.h> +#include <linux/cpu.h> +#include <linux/ktime.h> +#include <linux/kmon.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define DEF_CPUBUF_SIZE (1024 * 16) +#define DEF_SYNCBUF_FREQ (HZ / 20) +#define CPUBUF_CPU(p) ((int) ((p) - cpubuf)) + +struct kmon_cpubuf { + struct delayed_work work; + struct kmon_ring rng; + u64 base_time; +}; + +static unsigned long cpubuf_size = DEF_CPUBUF_SIZE; +static unsigned long flush_freq = DEF_SYNCBUF_FREQ; +static struct kmon_cpubuf cpubuf[NR_CPUS] __cacheline_aligned; +static struct list_head kmon_nfylist = LIST_HEAD_INIT(kmon_nfylist); +static struct mutex kmon_mtx; +static struct mutex setup_mtx; +static long usecnt; + +int kmon_ring_write(struct kmon_ring *rng, const struct kmon_buffer *kbuf, int n) +{ + int i; + unsigned long head, rspace, dwrt; + + head = rng->head; + rspace = (rng->size - 1) - ((head - rng->tail) & (rng->size - 1)); + for (i = 0; i < n; i++, kbuf++) { + if (rspace < kbuf->size) + return -EAGAIN; + dwrt = min(kbuf->size, rng->size - head); + if (likely(dwrt)) + memcpy(rng->data + head, kbuf->data, dwrt); + if (kbuf->size > dwrt) + memcpy(rng->data, kbuf->data + dwrt, kbuf->size - dwrt); + rspace -= kbuf->size; + head = (head + kbuf->size) & (rng->size - 1); + } + smp_wmb(); + rng->head = head; + + return 0; +} +EXPORT_SYMBOL(kmon_ring_write); + +unsigned long kmon_ring_read(struct kmon_ring *rng, struct kmon_buffer *kbuf) +{ + unsigned long size; + + size = kmon_ring_avail(rng); + kbuf[0].size = min(size, rng->size - rng->tail); + kbuf[0].data = rng->data + rng->tail; + kbuf[1].size = size - kbuf[0].size; + kbuf[1].data = rng->data; + + return size; +} +EXPORT_SYMBOL(kmon_ring_read); + +void kmon_ring_commit_read(struct kmon_ring *rng, unsigned long size) +{ + rng->tail = (rng->tail + size) & (rng->size - 1); +} +EXPORT_SYMBOL(kmon_ring_commit_read); + +static unsigned int kmon_prepare_buf(unsigned char *buf, int cpu, u64 time, + unsigned long size) +{ + buf[0] = (unsigned char) cpu; + kmon_put64(buf + 1, time); + kmon_put16(buf + 9, (u16) size); + return 11; +} + +int kmon_add_data(const void *data, unsigned long size) +{ + int cpu, error = -EAGAIN; + u64 ctime; + struct kmon_cpubuf *cpub; + unsigned char buf[16]; + struct kmon_buffer kbuf[2]; + + rcu_read_lock(); + cpu = smp_processor_id(); + cpub = &cpubuf[cpu]; + if (cpub->rng.data != NULL) { + ctime = ktime_to_ns(ktime_get()); + kbuf[0].data = buf; + kbuf[0].size = kmon_prepare_buf(buf, cpu, ctime, size); + kbuf[1].data = (void *) data; + kbuf[1].size = size; + error = kmon_ring_write(&cpub->rng, kbuf, 2); + } + rcu_read_unlock(); + + return error; +} +EXPORT_SYMBOL(kmon_add_data); + +void kmon_notify_add(struct kmon_notifier *knfy) +{ + mutex_lock(&kmon_mtx); + list_add_tail(&knfy->lnk, &kmon_nfylist); + mutex_unlock(&kmon_mtx); +} +EXPORT_SYMBOL(kmon_notify_add); + +void kmon_notify_remove(struct kmon_notifier *knfy) +{ + mutex_lock(&kmon_mtx); + list_del_init(&knfy->lnk); + mutex_unlock(&kmon_mtx); +} +EXPORT_SYMBOL(kmon_notify_remove); + +static void kmon_sync_buffer(struct work_struct *work) +{ + unsigned long size; + struct kmon_cpubuf *cpub; + struct kmon_notifier *knfy; + struct kmon_buffer kbuf[2]; + + cpub = container_of(work, struct kmon_cpubuf, work.work); + mutex_lock(&kmon_mtx); + if (likely(cpub->rng.data != NULL)) { + size = kmon_ring_read(&cpub->rng, kbuf); + if (likely(size)) { + list_for_each_entry(knfy, &kmon_nfylist, lnk) { + (*knfy->write)(knfy, kbuf, 2); + } + kmon_ring_commit_read(&cpub->rng, size); + } + schedule_delayed_work(&cpub->work, flush_freq); + } + mutex_unlock(&kmon_mtx); +} + +static int kmon_cpu_prepare(int cpu) +{ + unsigned long size; + void *data; + struct kmon_cpubuf *cpub; + + size = roundup_pow_of_two(cpubuf_size); + cpub = &cpubuf[cpu]; + mutex_lock(&kmon_mtx); + if (cpub->rng.data == NULL) { + data = vmalloc_node(size, cpu_to_node(cpu)); + if (data == NULL) { + mutex_unlock(&kmon_mtx); + return -ENOMEM; + } + cpub->rng.head = cpub->rng.tail = 0; + cpub->rng.size = size; + rcu_assign_pointer(cpub->rng.data, data); + INIT_DELAYED_WORK(&cpub->work, kmon_sync_buffer); + schedule_delayed_work_on(cpu, &cpub->work, flush_freq + cpu); + } + mutex_unlock(&kmon_mtx); + + return 0; +} + +static void kmon_cpu_cleanup(int cpu) +{ + void *data = NULL; + struct kmon_cpubuf *cpub; + + cpub = &cpubuf[cpu]; + mutex_lock(&kmon_mtx); + if (cpub->rng.data != NULL) { + data = cpub->rng.data; + cpub->rng.data = NULL; + } + mutex_unlock(&kmon_mtx); + + if (data != NULL) { + cancel_delayed_work(&cpub->work); + synchronize_rcu(); + vfree(data); + } +} + +static int kmon_cpu_hotplug(struct notifier_block *notifier, unsigned long val, + void *v) +{ + int cpu = (long) v; + + switch (val) { + case CPU_UP_PREPARE: + kmon_cpu_prepare(cpu); + break; + case CPU_UP_CANCELED: + case CPU_DEAD: + kmon_cpu_cleanup(cpu); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block kmon_cpu_notifier = { + .notifier_call = kmon_cpu_hotplug, +}; + +static void kmon_free_cpubufs(void) +{ + int cpu; + + for_each_online_cpu(cpu) + kmon_cpu_cleanup(cpu); +} + +static int kmon_alloc_cpubufs(void) +{ + int cpu, error; + + for_each_online_cpu(cpu) { + error = kmon_cpu_prepare(cpu); + if (error) { + kmon_free_cpubufs(); + return error; + } + } + + return 0; +} + +int kmon_setup(void) +{ + int error; + + mutex_lock(&setup_mtx); + if (usecnt > 0) + goto setup_done; + + error = register_cpu_notifier(&kmon_cpu_notifier); + if (error) + goto out_unlock; + + error = kmon_alloc_cpubufs(); + if (error) + goto out_unreg_cpuntfy; + +setup_done: + usecnt++; + mutex_unlock(&setup_mtx); + + return 0; + +out_unreg_cpuntfy: + unregister_cpu_notifier(&kmon_cpu_notifier); + kmon_free_cpubufs(); +out_unlock: + mutex_unlock(&setup_mtx); + return error; +} +EXPORT_SYMBOL(kmon_setup); + +void kmon_cleanup(void) +{ + mutex_lock(&setup_mtx); + usecnt--; + if (usecnt == 0) { + unregister_cpu_notifier(&kmon_cpu_notifier); + kmon_free_cpubufs(); + } + mutex_unlock(&setup_mtx); +} +EXPORT_SYMBOL(kmon_cleanup); + +static int __init kmon_init(void) +{ + mutex_init(&kmon_mtx); + mutex_init(&setup_mtx); + + return 0; +} +fs_initcall(kmon_init); + Index: linux-2.6.21.scdev/kernel/kmon-stats.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21.scdev/kernel/kmon-stats.c 2007-05-06 14:10:51.000000000 -0700 @@ -0,0 +1,62 @@ +/* + * kernel/kmon-stats.c + * + * Copyright (C) 2007 Davide Libenzi <[EMAIL PROTECTED]> + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/file.h> +#include <linux/poll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/kmon.h> +#include <linux/kmon-stats.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +void kmon_st_task_enqueue(struct task_struct *tsk) +{ + unsigned char buf[8]; + + buf[0] = KMON_DT_TSKQUEUE; + kmon_put16(buf + 1, (u16) tsk->pid); + kmon_add_data(buf, 3); +} + +void kmon_st_task_dequeue(struct task_struct *tsk) +{ + unsigned char buf[8]; + + buf[0] = KMON_DT_TSKDEQUEUE; + kmon_put16(buf + 1, (u16) tsk->pid); + kmon_add_data(buf, 3); +} + +void kmon_st_task_switch(struct task_struct *prev, struct task_struct *next) +{ + unsigned char buf[8]; + + buf[0] = KMON_DT_TSKSWITCH; + kmon_put16(buf + 1, (u16) prev->pid); + kmon_put16(buf + 3, (u16) next->pid); + kmon_add_data(buf, 5); +} + +void kmon_st_task_wakeup(struct task_struct *from, struct task_struct *to) +{ + unsigned char buf[8]; + + buf[0] = KMON_DT_TSKWAKEUP; + kmon_put16(buf + 1, from ? (u16) from->pid: 0); + kmon_put16(buf + 3, (u16) to->pid); + kmon_add_data(buf, 5); +} + Index: linux-2.6.21.scdev/include/linux/kmon-stats.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21.scdev/include/linux/kmon-stats.h 2007-05-06 14:10:51.000000000 -0700 @@ -0,0 +1,37 @@ +/* + * include/linux/kmon-stats.h + * + * Copyright (C) 2007 Davide Libenzi <[EMAIL PROTECTED]> + * + */ + +#ifndef _LINUX_KMON_STATS_H +#define _LINUX_KMON_STATS_H + +#define KMON_DT_TSKQUEUE 1 +#define KMON_DT_TSKDEQUEUE 2 +#define KMON_DT_TSKSWITCH 3 +#define KMON_DT_TSKWAKEUP 4 + +#ifdef __KERNEL__ + +#ifdef CONFIG_KMON + +void kmon_st_task_enqueue(struct task_struct *tsk); +void kmon_st_task_dequeue(struct task_struct *tsk); +void kmon_st_task_switch(struct task_struct *prev, struct task_struct *next); +void kmon_st_task_wakeup(struct task_struct *from, struct task_struct *to); + +#else /* CONFIG_KMON */ + +#define kmon_st_task_enqueue(p) do { } while (0) +#define kmon_st_task_dequeue(p) do { } while (0) +#define kmon_st_task_switch(p, n) do { } while (0) +#define kmon_st_task_wakeup(f, t) do { } while (0) + +#endif /* CONFIG_KMON */ + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_KMON_STATS_H */ + - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/