| /* |
| * This file is part of the Chelsio T4 Ethernet driver for Linux. |
| * |
| * Copyright (C) 2003-2009 Chelsio Communications. All rights reserved. |
| * |
| * 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 LICENSE file included in this |
| * release for licensing terms and conditions. |
| */ |
| |
| /* |
| * Routines to allocate and free T4 trace buffers. |
| * |
| * Authors: |
| * Felix Marti <felix@chelsio.com> |
| * |
| * The code suffers from a trace buffer count increment race, which might |
| * lead to entries being overwritten. I don't really care about this, |
| * because the trace buffer is a simple debug/perfomance tuning aid. |
| * |
| * Trace buffers are created in /proc, which needs to be fixed. |
| */ |
| |
| #include "trace.h" |
| |
| #ifdef T4_TRACE |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/log2.h> |
| #include <linux/slab.h> |
| |
| /* |
| * SEQ OPS |
| */ |
| static void *t4_trace_seq_start(struct seq_file *seq, loff_t *pos) |
| { |
| struct trace_buf *tb = seq->private; |
| struct trace_entry *e = NULL; |
| unsigned int start, count; |
| |
| if (tb->idx > tb->capacity) { |
| start = tb->idx & (tb->capacity - 1); |
| count = tb->capacity; |
| } else { |
| start = 0; |
| count = tb->idx; |
| } |
| |
| if (*pos < count) |
| e = &tb->ep[(start + *pos) & (tb->capacity - 1)]; |
| |
| return e; |
| } |
| |
| static void *t4_trace_seq_next(struct seq_file *seq, void *v, loff_t *pos) |
| { |
| struct trace_buf *tb = seq->private; |
| struct trace_entry *e = v; |
| unsigned int count = min(tb->idx, tb->capacity); |
| |
| if (++*pos < count) { |
| e++; |
| if (e >= &tb->ep[tb->capacity]) |
| e = tb->ep; |
| } else |
| e = NULL; |
| |
| return e; |
| } |
| |
| static void t4_trace_seq_stop(struct seq_file *seq, void *v) |
| { |
| } |
| |
| static int t4_trace_seq_show(struct seq_file *seq, void *v) |
| { |
| struct trace_entry *ep = v; |
| |
| seq_printf(seq, "%016llx ", (unsigned long long) ep->tsc); |
| seq_printf(seq, ep->fmt, ep->param[0], ep->param[1], ep->param[2], |
| ep->param[3], ep->param[4], ep->param[5]); |
| seq_printf(seq, "\n"); |
| return 0; |
| } |
| |
| static const struct seq_operations t4_trace_seq_ops = { |
| .start = t4_trace_seq_start, |
| .next = t4_trace_seq_next, |
| .stop = t4_trace_seq_stop, |
| .show = t4_trace_seq_show |
| }; |
| |
| /* |
| * FILE OPS |
| */ |
| static int t4_trace_seq_open(struct inode *inode, struct file *file) |
| { |
| int rc = seq_open(file, &t4_trace_seq_ops); |
| |
| if (!rc) { |
| struct seq_file *seq = file->private_data; |
| |
| seq->private = inode->i_private; |
| } |
| |
| return rc; |
| } |
| |
| static const struct file_operations t4_trace_seq_fops = { |
| .owner = THIS_MODULE, |
| .open = t4_trace_seq_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release |
| }; |
| |
| /* |
| * TRACEBUFFER API |
| */ |
| struct trace_buf *t4_trace_alloc(struct dentry *root, const char *name, |
| unsigned int capacity) |
| { |
| struct trace_buf *tb; |
| unsigned int size; |
| |
| if (!name || !is_power_of_2(capacity)) |
| return NULL; |
| |
| size = sizeof(*tb) + sizeof(struct trace_entry) * capacity; |
| tb = kmalloc(size, GFP_KERNEL); |
| if (!tb) |
| return NULL; |
| |
| memset(tb, 0, size); |
| tb->capacity = capacity; |
| tb->debugfs_dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, root, |
| tb, &t4_trace_seq_fops); |
| if (!tb->debugfs_dentry) { |
| kfree(tb); |
| return NULL; |
| } |
| |
| return tb; |
| } |
| |
| void t4_trace_free(struct trace_buf *tb) |
| { |
| if (tb) { |
| if (tb->debugfs_dentry) |
| debugfs_remove(tb->debugfs_dentry); |
| kfree(tb); |
| } |
| } |
| EXPORT_SYMBOL(t4_trace_alloc); |
| EXPORT_SYMBOL(t4_trace_free); |
| #endif /* T4_TRACE */ |