/*
* 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);