Commit | Line | Data |
---|---|---|
c71228ca IPG |
1 | /* |
2 | * Intel Wireless WiMAX Connection 2400m | |
3 | * Debugfs interfaces to manipulate driver and device information | |
4 | * | |
5 | * | |
6 | * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com> | |
7 | * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License version | |
11 | * 2 as published by the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
21 | * 02110-1301, USA. | |
22 | */ | |
23 | ||
24 | #include <linux/debugfs.h> | |
25 | #include <linux/netdevice.h> | |
26 | #include <linux/etherdevice.h> | |
27 | #include <linux/spinlock.h> | |
28 | #include <linux/device.h> | |
ee40fa06 | 29 | #include <linux/export.h> |
c71228ca IPG |
30 | #include "i2400m.h" |
31 | ||
32 | ||
33 | #define D_SUBMODULE debugfs | |
34 | #include "debug-levels.h" | |
35 | ||
36 | static | |
37 | int debugfs_netdev_queue_stopped_get(void *data, u64 *val) | |
38 | { | |
39 | struct i2400m *i2400m = data; | |
40 | *val = netif_queue_stopped(i2400m->wimax_dev.net_dev); | |
41 | return 0; | |
42 | } | |
43 | DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped, | |
44 | debugfs_netdev_queue_stopped_get, | |
45 | NULL, "%llu\n"); | |
46 | ||
47 | ||
48 | static | |
49 | struct dentry *debugfs_create_netdev_queue_stopped( | |
50 | const char *name, struct dentry *parent, struct i2400m *i2400m) | |
51 | { | |
52 | return debugfs_create_file(name, 0400, parent, i2400m, | |
53 | &fops_netdev_queue_stopped); | |
54 | } | |
55 | ||
c71228ca IPG |
56 | /* |
57 | * We don't allow partial reads of this file, as then the reader would | |
58 | * get weirdly confused data as it is updated. | |
59 | * | |
60 | * So or you read it all or nothing; if you try to read with an offset | |
61 | * != 0, we consider you are done reading. | |
62 | */ | |
63 | static | |
64 | ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer, | |
65 | size_t count, loff_t *ppos) | |
66 | { | |
67 | struct i2400m *i2400m = filp->private_data; | |
68 | char buf[128]; | |
69 | unsigned long flags; | |
70 | ||
71 | if (*ppos != 0) | |
72 | return 0; | |
73 | if (count < sizeof(buf)) | |
74 | return -ENOSPC; | |
75 | spin_lock_irqsave(&i2400m->rx_lock, flags); | |
76 | snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n", | |
77 | i2400m->rx_pl_num, i2400m->rx_pl_min, | |
78 | i2400m->rx_pl_max, i2400m->rx_num, | |
79 | i2400m->rx_size_acc, | |
80 | i2400m->rx_size_min, i2400m->rx_size_max); | |
81 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
82 | return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); | |
83 | } | |
84 | ||
85 | ||
86 | /* Any write clears the stats */ | |
87 | static | |
88 | ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer, | |
89 | size_t count, loff_t *ppos) | |
90 | { | |
91 | struct i2400m *i2400m = filp->private_data; | |
92 | unsigned long flags; | |
93 | ||
94 | spin_lock_irqsave(&i2400m->rx_lock, flags); | |
95 | i2400m->rx_pl_num = 0; | |
96 | i2400m->rx_pl_max = 0; | |
97 | i2400m->rx_pl_min = UINT_MAX; | |
98 | i2400m->rx_num = 0; | |
99 | i2400m->rx_size_acc = 0; | |
100 | i2400m->rx_size_min = UINT_MAX; | |
101 | i2400m->rx_size_max = 0; | |
102 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
103 | return count; | |
104 | } | |
105 | ||
106 | static | |
107 | const struct file_operations i2400m_rx_stats_fops = { | |
108 | .owner = THIS_MODULE, | |
234e3405 | 109 | .open = simple_open, |
c71228ca IPG |
110 | .read = i2400m_rx_stats_read, |
111 | .write = i2400m_rx_stats_write, | |
6038f373 | 112 | .llseek = default_llseek, |
c71228ca IPG |
113 | }; |
114 | ||
115 | ||
116 | /* See i2400m_rx_stats_read() */ | |
117 | static | |
118 | ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer, | |
119 | size_t count, loff_t *ppos) | |
120 | { | |
121 | struct i2400m *i2400m = filp->private_data; | |
122 | char buf[128]; | |
123 | unsigned long flags; | |
124 | ||
125 | if (*ppos != 0) | |
126 | return 0; | |
127 | if (count < sizeof(buf)) | |
128 | return -ENOSPC; | |
129 | spin_lock_irqsave(&i2400m->tx_lock, flags); | |
130 | snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n", | |
131 | i2400m->tx_pl_num, i2400m->tx_pl_min, | |
132 | i2400m->tx_pl_max, i2400m->tx_num, | |
133 | i2400m->tx_size_acc, | |
134 | i2400m->tx_size_min, i2400m->tx_size_max); | |
135 | spin_unlock_irqrestore(&i2400m->tx_lock, flags); | |
136 | return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); | |
137 | } | |
138 | ||
139 | /* Any write clears the stats */ | |
140 | static | |
141 | ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer, | |
142 | size_t count, loff_t *ppos) | |
143 | { | |
144 | struct i2400m *i2400m = filp->private_data; | |
145 | unsigned long flags; | |
146 | ||
147 | spin_lock_irqsave(&i2400m->tx_lock, flags); | |
148 | i2400m->tx_pl_num = 0; | |
149 | i2400m->tx_pl_max = 0; | |
150 | i2400m->tx_pl_min = UINT_MAX; | |
151 | i2400m->tx_num = 0; | |
152 | i2400m->tx_size_acc = 0; | |
153 | i2400m->tx_size_min = UINT_MAX; | |
154 | i2400m->tx_size_max = 0; | |
155 | spin_unlock_irqrestore(&i2400m->tx_lock, flags); | |
156 | return count; | |
157 | } | |
158 | ||
159 | static | |
160 | const struct file_operations i2400m_tx_stats_fops = { | |
161 | .owner = THIS_MODULE, | |
234e3405 | 162 | .open = simple_open, |
c71228ca IPG |
163 | .read = i2400m_tx_stats_read, |
164 | .write = i2400m_tx_stats_write, | |
6038f373 | 165 | .llseek = default_llseek, |
c71228ca IPG |
166 | }; |
167 | ||
168 | ||
169 | /* Write 1 to ask the device to go into suspend */ | |
170 | static | |
171 | int debugfs_i2400m_suspend_set(void *data, u64 val) | |
172 | { | |
173 | int result; | |
174 | struct i2400m *i2400m = data; | |
175 | result = i2400m_cmd_enter_powersave(i2400m); | |
176 | if (result >= 0) | |
177 | result = 0; | |
178 | return result; | |
179 | } | |
180 | DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend, | |
181 | NULL, debugfs_i2400m_suspend_set, | |
182 | "%llu\n"); | |
183 | ||
184 | static | |
185 | struct dentry *debugfs_create_i2400m_suspend( | |
186 | const char *name, struct dentry *parent, struct i2400m *i2400m) | |
187 | { | |
188 | return debugfs_create_file(name, 0200, parent, i2400m, | |
189 | &fops_i2400m_suspend); | |
190 | } | |
191 | ||
192 | ||
193 | /* | |
194 | * Reset the device | |
195 | * | |
196 | * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus | |
197 | * reset (as defined by enum i2400m_reset_type). | |
198 | */ | |
199 | static | |
200 | int debugfs_i2400m_reset_set(void *data, u64 val) | |
201 | { | |
202 | int result; | |
203 | struct i2400m *i2400m = data; | |
204 | enum i2400m_reset_type rt = val; | |
205 | switch(rt) { | |
206 | case I2400M_RT_WARM: | |
207 | case I2400M_RT_COLD: | |
208 | case I2400M_RT_BUS: | |
c931ceeb | 209 | result = i2400m_reset(i2400m, rt); |
c71228ca IPG |
210 | if (result >= 0) |
211 | result = 0; | |
01872c64 | 212 | break; |
c71228ca IPG |
213 | default: |
214 | result = -EINVAL; | |
215 | } | |
216 | return result; | |
217 | } | |
218 | DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset, | |
219 | NULL, debugfs_i2400m_reset_set, | |
220 | "%llu\n"); | |
221 | ||
222 | static | |
223 | struct dentry *debugfs_create_i2400m_reset( | |
224 | const char *name, struct dentry *parent, struct i2400m *i2400m) | |
225 | { | |
226 | return debugfs_create_file(name, 0200, parent, i2400m, | |
227 | &fops_i2400m_reset); | |
228 | } | |
229 | ||
c71228ca IPG |
230 | |
231 | #define __debugfs_register(prefix, name, parent) \ | |
232 | do { \ | |
233 | result = d_level_register_debugfs(prefix, name, parent); \ | |
234 | if (result < 0) \ | |
235 | goto error; \ | |
236 | } while (0) | |
237 | ||
238 | ||
239 | int i2400m_debugfs_add(struct i2400m *i2400m) | |
240 | { | |
241 | int result; | |
242 | struct device *dev = i2400m_dev(i2400m); | |
243 | struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry; | |
244 | struct dentry *fd; | |
245 | ||
246 | dentry = debugfs_create_dir("i2400m", dentry); | |
247 | result = PTR_ERR(dentry); | |
248 | if (IS_ERR(dentry)) { | |
249 | if (result == -ENODEV) | |
250 | result = 0; /* No debugfs support */ | |
251 | goto error; | |
252 | } | |
253 | i2400m->debugfs_dentry = dentry; | |
254 | __debugfs_register("dl_", control, dentry); | |
255 | __debugfs_register("dl_", driver, dentry); | |
256 | __debugfs_register("dl_", debugfs, dentry); | |
257 | __debugfs_register("dl_", fw, dentry); | |
258 | __debugfs_register("dl_", netdev, dentry); | |
259 | __debugfs_register("dl_", rfkill, dentry); | |
260 | __debugfs_register("dl_", rx, dentry); | |
261 | __debugfs_register("dl_", tx, dentry); | |
262 | ||
263 | fd = debugfs_create_size_t("tx_in", 0400, dentry, | |
264 | &i2400m->tx_in); | |
265 | result = PTR_ERR(fd); | |
266 | if (IS_ERR(fd) && result != -ENODEV) { | |
267 | dev_err(dev, "Can't create debugfs entry " | |
268 | "tx_in: %d\n", result); | |
269 | goto error; | |
270 | } | |
271 | ||
272 | fd = debugfs_create_size_t("tx_out", 0400, dentry, | |
273 | &i2400m->tx_out); | |
274 | result = PTR_ERR(fd); | |
275 | if (IS_ERR(fd) && result != -ENODEV) { | |
276 | dev_err(dev, "Can't create debugfs entry " | |
277 | "tx_out: %d\n", result); | |
278 | goto error; | |
279 | } | |
280 | ||
281 | fd = debugfs_create_u32("state", 0600, dentry, | |
282 | &i2400m->state); | |
283 | result = PTR_ERR(fd); | |
284 | if (IS_ERR(fd) && result != -ENODEV) { | |
285 | dev_err(dev, "Can't create debugfs entry " | |
286 | "state: %d\n", result); | |
287 | goto error; | |
288 | } | |
289 | ||
290 | /* | |
291 | * Trace received messages from user space | |
292 | * | |
293 | * In order to tap the bidirectional message stream in the | |
294 | * 'msg' pipe, user space can read from the 'msg' pipe; | |
295 | * however, due to limitations in libnl, we can't know what | |
296 | * the different applications are sending down to the kernel. | |
297 | * | |
298 | * So we have this hack where the driver will echo any message | |
299 | * received on the msg pipe from user space [through a call to | |
300 | * wimax_dev->op_msg_from_user() into | |
301 | * i2400m_op_msg_from_user()] into the 'trace' pipe that this | |
302 | * driver creates. | |
303 | * | |
304 | * So then, reading from both the 'trace' and 'msg' pipes in | |
305 | * user space will provide a full dump of the traffic. | |
306 | * | |
307 | * Write 1 to activate, 0 to clear. | |
308 | * | |
309 | * It is not really very atomic, but it is also not too | |
310 | * critical. | |
311 | */ | |
312 | fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry, | |
313 | &i2400m->trace_msg_from_user); | |
314 | result = PTR_ERR(fd); | |
315 | if (IS_ERR(fd) && result != -ENODEV) { | |
316 | dev_err(dev, "Can't create debugfs entry " | |
317 | "trace_msg_from_user: %d\n", result); | |
318 | goto error; | |
319 | } | |
320 | ||
321 | fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped", | |
322 | dentry, i2400m); | |
323 | result = PTR_ERR(fd); | |
324 | if (IS_ERR(fd) && result != -ENODEV) { | |
325 | dev_err(dev, "Can't create debugfs entry " | |
326 | "netdev_queue_stopped: %d\n", result); | |
327 | goto error; | |
328 | } | |
329 | ||
330 | fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m, | |
331 | &i2400m_rx_stats_fops); | |
332 | result = PTR_ERR(fd); | |
333 | if (IS_ERR(fd) && result != -ENODEV) { | |
334 | dev_err(dev, "Can't create debugfs entry " | |
335 | "rx_stats: %d\n", result); | |
336 | goto error; | |
337 | } | |
338 | ||
339 | fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m, | |
340 | &i2400m_tx_stats_fops); | |
341 | result = PTR_ERR(fd); | |
342 | if (IS_ERR(fd) && result != -ENODEV) { | |
343 | dev_err(dev, "Can't create debugfs entry " | |
344 | "tx_stats: %d\n", result); | |
345 | goto error; | |
346 | } | |
347 | ||
348 | fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m); | |
349 | result = PTR_ERR(fd); | |
350 | if (IS_ERR(fd) && result != -ENODEV) { | |
351 | dev_err(dev, "Can't create debugfs entry suspend: %d\n", | |
352 | result); | |
353 | goto error; | |
354 | } | |
355 | ||
356 | fd = debugfs_create_i2400m_reset("reset", dentry, i2400m); | |
357 | result = PTR_ERR(fd); | |
358 | if (IS_ERR(fd) && result != -ENODEV) { | |
359 | dev_err(dev, "Can't create debugfs entry reset: %d\n", result); | |
360 | goto error; | |
361 | } | |
362 | ||
363 | result = 0; | |
364 | error: | |
365 | return result; | |
366 | } | |
367 | ||
368 | void i2400m_debugfs_rm(struct i2400m *i2400m) | |
369 | { | |
370 | debugfs_remove_recursive(i2400m->debugfs_dentry); | |
371 | } |