summaryrefslogtreecommitdiff
path: root/kernel/trace/tracelevel.c
blob: 9f8b8eedbb588bd5adb7af9f7fb035d609faf5c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * kernel/trace/tracelevel.c
 *
 * Copyright (c) 2011, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include <linux/ftrace_event.h>
#include <linux/list.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/tracelevel.h>
#include <linux/vmalloc.h>

#include "trace.h"

#define TAG KERN_ERR "tracelevel: "

struct tracelevel_record {
	struct list_head list;
	char *name;
	int level;
};

static LIST_HEAD(tracelevel_list);

static bool started;
static unsigned int tracelevel_level = TRACELEVEL_DEFAULT;

static DEFINE_MUTEX(tracelevel_record_lock);

/* tracelevel_set_event sets a single event if set = 1, or
 * clears an event if set = 0.
 */
static int tracelevel_set_event(struct tracelevel_record *evt, bool set)
{
	if (trace_set_clr_event(NULL, evt->name, set) < 0) {
		printk(TAG "failed to set event %s\n", evt->name);
		return -EINVAL;
	}
	return 0;
}

/* Registers an event. If possible, it also sets it.
 * If not, we'll set it in tracelevel_init.
 */
int __tracelevel_register(char *name, unsigned int level)
{
	struct tracelevel_record *evt = (struct tracelevel_record *)
		vmalloc(sizeof(struct tracelevel_record));
	if (!evt) {
		printk(TAG "failed to allocate tracelevel_record for %s\n",
			name);
		return -ENOMEM;
	}

	evt->name = name;
	evt->level = level;

	mutex_lock(&tracelevel_record_lock);
	list_add(&evt->list, &tracelevel_list);
	mutex_unlock(&tracelevel_record_lock);

	if (level >= tracelevel_level && started)
		tracelevel_set_event(evt, 1);
	return 0;
}

/* tracelevel_set_level sets the global level, clears events
 * lower than that level, and enables events greater or equal.
 */
int tracelevel_set_level(int level)
{
	struct tracelevel_record *evt = NULL;

	if (level < 0 || level > TRACELEVEL_MAX)
		return -EINVAL;
	tracelevel_level = level;

	mutex_lock(&tracelevel_record_lock);
	list_for_each_entry(evt, &tracelevel_list, list) {
		if (evt->level >= level)
			tracelevel_set_event(evt, 1);
		else
			tracelevel_set_event(evt, 0);
	}
	mutex_unlock(&tracelevel_record_lock);
	return 0;
}

static int param_set_level(const char *val, const struct kernel_param *kp)
{
	int level, ret;
	ret = strict_strtol(val, 0, &level);
	if (ret < 0)
		return ret;
	return tracelevel_set_level(level);
}

static int param_get_level(char *buffer, const struct kernel_param *kp)
{
	return param_get_int(buffer, kp);
}

static struct kernel_param_ops tracelevel_level_ops = {
	.set = param_set_level,
	.get = param_get_level
};

module_param_cb(level, &tracelevel_level_ops, &tracelevel_level, 0644);

/* Turn on the tracing that has been registered thus far. */
static int __init tracelevel_init(void)
{
	int ret;
	started = true;

	/* Ring buffer is initialize to 1 page until the user sets a tracer.
	 * Since we're doing this manually, we need to ask for expanded buffer.
	 */
	ret = tracing_update_buffers();
	if (ret < 0)
		return ret;

	return tracelevel_set_level(tracelevel_level);
}

/* Tracing mechanism is set up during fs_initcall. */
fs_initcall_sync(tracelevel_init);