/* * lib/stopwatch.c * Creates /proc/stopwatch/ and associated functions * * This file is part of the Ubicom32 Linux Kernel Port. * * The Ubicom32 Linux Kernel Port 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. * * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, * see * */ #include #include #include #include #define __STOPWATCH_USE__ #include #define STOPWATCH_MAX_WATCHES 10 static DEFINE_SPINLOCK(stopwatch_init_lock); static DEFINE_SPINLOCK(stopwatch_lock); static struct proc_dir_entry *stopwatch_root; #define STOPWATCH_FLAG_INUSE 0x01 #define STOPWTACH_FLAG_CREATED 0x02 static struct stopwatch { int flags; const char *name; int count; stopwatch_show_t show; } stopwatches[STOPWATCH_MAX_WATCHES]; /* * stopwatch_find() * Find the stop watch. */ static struct stopwatch *stopwatch_find(const char *name) { int idx; unsigned long flags; spin_lock_irqsave(&stopwatch_lock, flags); for (idx = 0; idx < STOPWATCH_MAX_WATCHES; idx++) { /* * Trying to find a match. */ struct stopwatch *sw = &stopwatches[idx]; if (!sw->name) { continue; } if (strcmp(sw->name, name)) { continue; } spin_unlock_irqrestore(&stopwatch_lock, flags); return sw; } spin_unlock_irqrestore(&stopwatch_lock, flags); return NULL; } /* * stopwatch_alloc() * Allocate an entry, check for duplicates */ static struct stopwatch *stopwatch_alloc(const char *name) { int idx; unsigned long flags; struct stopwatch *free = NULL; spin_lock_irqsave(&stopwatch_lock, flags); for (idx = 0; idx < STOPWATCH_MAX_WATCHES; idx++) { struct stopwatch *sw = &stopwatches[idx]; /* * Looking for an empty slot? */ if (!(sw->flags & STOPWATCH_FLAG_INUSE)) { free = sw; continue; } /* * Trying to find a match. */ if (strcmp(sw->name, name) == 0) { printk(KERN_WARNING "[%s]: duplicate found: %d\n", __func__, idx); spin_unlock_irqrestore(&stopwatch_lock, flags); return NULL; } } /* * If we have a free slot, use it. */ if (free) { free->name = name; free->flags |= STOPWATCH_FLAG_INUSE; } spin_unlock_irqrestore(&stopwatch_lock, flags); return free; } /* * stopwatch_seq_show() * Internal show (redirects to the callers show). */ static int stopwatch_seq_show(struct seq_file *f, void *v) { struct stopwatch *sw = f->private; if (!sw) { return 0; } return sw->show(f, v); } /* * stopwatch_seq_start() * Internal seq start (uses configured count). */ static void *stopwatch_seq_start(struct seq_file *f, loff_t *pos) { struct stopwatch *sw = f->private; if (!sw) { return NULL; } return (*pos < sw->count) ? pos : NULL; } /* * stopwatch_seq_next() * Internal seq next (uses configured count). */ static void *stopwatch_seq_next(struct seq_file *f, void *v, loff_t *pos) { struct stopwatch *sw = f->private; if (!sw) { return NULL; } (*pos)++; if (*pos >= sw->count) { return NULL; } return pos; } /* * stopwatch_seq_start() * Empty stop function. */ static void stopwatch_seq_stop(struct seq_file *f, void *v) { /* Nothing to do */ } /* * Provide our own internal sequence operations. */ static const struct seq_operations stopwatch_seq_ops = { .start = stopwatch_seq_start, .next = stopwatch_seq_next, .stop = stopwatch_seq_stop, .show = stopwatch_seq_show, }; /* * stopwatch_open() * Bind the file to the proper sequence operations */ static int stopwatch_open(struct inode *inode, struct file *f) { struct seq_file *m; int ret; struct stopwatch *sw = PDE_DATA(inode); if (!sw) { printk(KERN_WARNING "[%s]: unable to find: %s\n", __func__, f->f_path.dentry->d_name.name); return -ENOENT; } ret = seq_open(f, &stopwatch_seq_ops); if (ret < 0) { printk(KERN_WARNING "[%s]: unable to create sequence file: " "%s\n", __func__, f->f_path.dentry->d_name.name); return ret; } /* * Transfer stopwatch pointer form inode to file. */ m = f->private_data; m->private = sw; return 0; } /* * List of file operations. */ static const struct file_operations stopwatch_fops = { .open = stopwatch_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; /* * stopwatch_init() * Initialize the stop watch service. */ static int stopwatch_init(void) { /* * Already inited, do nothing? */ spin_lock(&stopwatch_init_lock); if (stopwatch_root) { spin_unlock(&stopwatch_init_lock); return 0; } /* * create /proc/stopwatch */ stopwatch_root = proc_mkdir("stopwatch", NULL); if (!stopwatch_root) { spin_unlock(&stopwatch_init_lock); printk(KERN_WARNING "[%s]: unable to create /proc/stopwatch\n", __func__); return -ENOMEM; } spin_unlock(&stopwatch_init_lock); return 0; } module_init(stopwatch_init); /* * stopwtch_show() * Print the stop watch values to the sequence file. */ void stopwatch_show(struct stopwatch_instance *si, struct seq_file *p, int precision) { unsigned long flags; unsigned long long min, avg, max; unsigned long count; if (si->count == 0) { seq_printf(p, " \t%u\t%u\t%u", 0, 0, 0); return; } spin_lock_irqsave(&si->lock, flags); min = si->min; avg = si->avg; max = si->max; count = si->count; si->min = 0; si->avg = 0; si->max = 0; si->count = 0; spin_unlock_irqrestore(&si->lock, flags); /* Calculate the actual average */ do_div(avg, count); if (precision == STOPWATCH_MICRO) { do_div(min, 1000); do_div(avg, 1000); do_div(max, 1000); } seq_printf(p, " \t%llu\t%llu\t%llu", min, avg, max); } EXPORT_SYMBOL(stopwatch_show); /* * stopwatch_unregister() * Remove the registered stopwatch. */ void stopwatch_unregister(const char *name) { unsigned long flags; struct stopwatch *sw = stopwatch_find(name); if (!sw) { printk(KERN_WARNING "[%s]: unable to find: %s\n", __func__, name); return; } remove_proc_entry(name, stopwatch_root); /* * Remove an entry. */ spin_lock_irqsave(&stopwatch_lock, flags); sw->name = NULL; sw->flags = 0; spin_unlock_irqrestore(&stopwatch_lock, flags); } EXPORT_SYMBOL(stopwatch_unregister); /* * stopwatch_register() * Register a new stop watch counter group. */ unsigned int stopwatch_register(const char *name, int count, stopwatch_show_t show) { unsigned long flags; struct stopwatch *sw; /* * If we try to register before the stopwatch init, just do * the init first. */ if (!stopwatch_root) { stopwatch_init(); } /* * Check for duplicate name use. */ sw = stopwatch_alloc(name); if (!sw) { printk(KERN_WARNING "[%s]: unable to allocate empty slot\n", __func__); return -ENOMEM; } /* * Create a node in the stop watch directory. */ proc_create_data(name, 0, stopwatch_root, &stopwatch_fops, (void *)sw); spin_lock_irqsave(&stopwatch_lock, flags); sw->count = count; sw->show = show; sw->flags |= STOPWTACH_FLAG_CREATED; spin_unlock_irqrestore(&stopwatch_lock, flags); return 0; } EXPORT_SYMBOL(stopwatch_register);