summaryrefslogtreecommitdiff
path: root/drivers/net/wimax/i2400m/debugfs.c
blob: 1c640b41ea4c943de4fb6e3a95cdffda7d106c3a (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Intel Wireless WiMAX Connection 2400m
 * Debugfs interfaces to manipulate driver and device information
 *
 * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
 * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
 */

#include <linux/debugfs.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/export.h>
#include "i2400m.h"


#define D_SUBMODULE debugfs
#include "debug-levels.h"

static
int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
{
	struct i2400m *i2400m = data;
	*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
	return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_netdev_queue_stopped,
			debugfs_netdev_queue_stopped_get,
			NULL, "%llu\n");

/*
 * We don't allow partial reads of this file, as then the reader would
 * get weirdly confused data as it is updated.
 *
 * So or you read it all or nothing; if you try to read with an offset
 * != 0, we consider you are done reading.
 */
static
ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
			     size_t count, loff_t *ppos)
{
	struct i2400m *i2400m = filp->private_data;
	char buf[128];
	unsigned long flags;

	if (*ppos != 0)
		return 0;
	if (count < sizeof(buf))
		return -ENOSPC;
	spin_lock_irqsave(&i2400m->rx_lock, flags);
	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
		 i2400m->rx_pl_num, i2400m->rx_pl_min,
		 i2400m->rx_pl_max, i2400m->rx_num,
		 i2400m->rx_size_acc,
		 i2400m->rx_size_min, i2400m->rx_size_max);
	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
}


/* Any write clears the stats */
static
ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
			      size_t count, loff_t *ppos)
{
	struct i2400m *i2400m = filp->private_data;
	unsigned long flags;

	spin_lock_irqsave(&i2400m->rx_lock, flags);
	i2400m->rx_pl_num = 0;
	i2400m->rx_pl_max = 0;
	i2400m->rx_pl_min = UINT_MAX;
	i2400m->rx_num = 0;
	i2400m->rx_size_acc = 0;
	i2400m->rx_size_min = UINT_MAX;
	i2400m->rx_size_max = 0;
	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
	return count;
}

static
const struct file_operations i2400m_rx_stats_fops = {
	.owner =	THIS_MODULE,
	.open =		simple_open,
	.read =		i2400m_rx_stats_read,
	.write =	i2400m_rx_stats_write,
	.llseek =	default_llseek,
};


/* See i2400m_rx_stats_read() */
static
ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
			     size_t count, loff_t *ppos)
{
	struct i2400m *i2400m = filp->private_data;
	char buf[128];
	unsigned long flags;

	if (*ppos != 0)
		return 0;
	if (count < sizeof(buf))
		return -ENOSPC;
	spin_lock_irqsave(&i2400m->tx_lock, flags);
	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
		 i2400m->tx_pl_num, i2400m->tx_pl_min,
		 i2400m->tx_pl_max, i2400m->tx_num,
		 i2400m->tx_size_acc,
		 i2400m->tx_size_min, i2400m->tx_size_max);
	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
}

/* Any write clears the stats */
static
ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
			      size_t count, loff_t *ppos)
{
	struct i2400m *i2400m = filp->private_data;
	unsigned long flags;

	spin_lock_irqsave(&i2400m->tx_lock, flags);
	i2400m->tx_pl_num = 0;
	i2400m->tx_pl_max = 0;
	i2400m->tx_pl_min = UINT_MAX;
	i2400m->tx_num = 0;
	i2400m->tx_size_acc = 0;
	i2400m->tx_size_min = UINT_MAX;
	i2400m->tx_size_max = 0;
	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
	return count;
}

static
const struct file_operations i2400m_tx_stats_fops = {
	.owner =	THIS_MODULE,
	.open =		simple_open,
	.read =		i2400m_tx_stats_read,
	.write =	i2400m_tx_stats_write,
	.llseek =	default_llseek,
};


/* Write 1 to ask the device to go into suspend */
static
int debugfs_i2400m_suspend_set(void *data, u64 val)
{
	int result;
	struct i2400m *i2400m = data;
	result = i2400m_cmd_enter_powersave(i2400m);
	if (result >= 0)
		result = 0;
	return result;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_i2400m_suspend,
			NULL, debugfs_i2400m_suspend_set,
			"%llu\n");

/*
 * Reset the device
 *
 * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
 * reset (as defined by enum i2400m_reset_type).
 */
static
int debugfs_i2400m_reset_set(void *data, u64 val)
{
	int result;
	struct i2400m *i2400m = data;
	enum i2400m_reset_type rt = val;
	switch(rt) {
	case I2400M_RT_WARM:
	case I2400M_RT_COLD:
	case I2400M_RT_BUS:
		result = i2400m_reset(i2400m, rt);
		if (result >= 0)
			result = 0;
		break;
	default:
		result = -EINVAL;
	}
	return result;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_i2400m_reset,
			NULL, debugfs_i2400m_reset_set,
			"%llu\n");

void i2400m_debugfs_add(struct i2400m *i2400m)
{
	struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;

	dentry = debugfs_create_dir("i2400m", dentry);
	i2400m->debugfs_dentry = dentry;

	d_level_register_debugfs("dl_", control, dentry);
	d_level_register_debugfs("dl_", driver, dentry);
	d_level_register_debugfs("dl_", debugfs, dentry);
	d_level_register_debugfs("dl_", fw, dentry);
	d_level_register_debugfs("dl_", netdev, dentry);
	d_level_register_debugfs("dl_", rfkill, dentry);
	d_level_register_debugfs("dl_", rx, dentry);
	d_level_register_debugfs("dl_", tx, dentry);

	debugfs_create_size_t("tx_in", 0400, dentry, &i2400m->tx_in);
	debugfs_create_size_t("tx_out", 0400, dentry, &i2400m->tx_out);
	debugfs_create_u32("state", 0600, dentry, &i2400m->state);

	/*
	 * Trace received messages from user space
	 *
	 * In order to tap the bidirectional message stream in the
	 * 'msg' pipe, user space can read from the 'msg' pipe;
	 * however, due to limitations in libnl, we can't know what
	 * the different applications are sending down to the kernel.
	 *
	 * So we have this hack where the driver will echo any message
	 * received on the msg pipe from user space [through a call to
	 * wimax_dev->op_msg_from_user() into
	 * i2400m_op_msg_from_user()] into the 'trace' pipe that this
	 * driver creates.
	 *
	 * So then, reading from both the 'trace' and 'msg' pipes in
	 * user space will provide a full dump of the traffic.
	 *
	 * Write 1 to activate, 0 to clear.
	 *
	 * It is not really very atomic, but it is also not too
	 * critical.
	 */
	debugfs_create_u8("trace_msg_from_user", 0600, dentry,
			  &i2400m->trace_msg_from_user);

	debugfs_create_file("netdev_queue_stopped", 0400, dentry, i2400m,
			    &fops_netdev_queue_stopped);

	debugfs_create_file("rx_stats", 0600, dentry, i2400m,
			    &i2400m_rx_stats_fops);

	debugfs_create_file("tx_stats", 0600, dentry, i2400m,
			    &i2400m_tx_stats_fops);

	debugfs_create_file("suspend", 0200, dentry, i2400m,
			    &fops_i2400m_suspend);

	debugfs_create_file("reset", 0200, dentry, i2400m, &fops_i2400m_reset);
}

void i2400m_debugfs_rm(struct i2400m *i2400m)
{
	debugfs_remove_recursive(i2400m->debugfs_dentry);
}