Commit | Line | Data |
---|---|---|
bb9f8692 ZY |
1 | /* |
2 | * Intel Wireless Multicomm 3200 WiFi driver | |
3 | * | |
4 | * Copyright (C) 2009 Intel Corporation <ilw@linux.intel.com> | |
5 | * Samuel Ortiz <samuel.ortiz@intel.com> | |
6 | * Zhu Yi <yi.zhu@intel.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License version | |
10 | * 2 as published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
20 | * 02110-1301, USA. | |
21 | * | |
22 | */ | |
23 | ||
24 | #include <linux/kernel.h> | |
25 | #include <linux/bitops.h> | |
26 | #include <linux/debugfs.h> | |
27 | ||
28 | #include "iwm.h" | |
29 | #include "bus.h" | |
30 | #include "rx.h" | |
31 | #include "debug.h" | |
32 | ||
33 | static struct { | |
34 | u8 id; | |
35 | char *name; | |
36 | } iwm_debug_module[__IWM_DM_NR] = { | |
37 | {IWM_DM_BOOT, "boot"}, | |
38 | {IWM_DM_FW, "fw"}, | |
39 | {IWM_DM_SDIO, "sdio"}, | |
40 | {IWM_DM_NTF, "ntf"}, | |
41 | {IWM_DM_RX, "rx"}, | |
42 | {IWM_DM_TX, "tx"}, | |
43 | {IWM_DM_MLME, "mlme"}, | |
44 | {IWM_DM_CMD, "cmd"}, | |
45 | {IWM_DM_WEXT, "wext"}, | |
46 | }; | |
47 | ||
48 | #define add_dbg_module(dbg, name, id, initlevel) \ | |
49 | do { \ | |
50 | struct dentry *d; \ | |
51 | dbg.dbg_module[id] = (initlevel); \ | |
52 | d = debugfs_create_x8(name, 0600, dbg.dbgdir, \ | |
53 | &(dbg.dbg_module[id])); \ | |
54 | if (!IS_ERR(d)) \ | |
55 | dbg.dbg_module_dentries[id] = d; \ | |
56 | } while (0) | |
57 | ||
58 | static int iwm_debugfs_u32_read(void *data, u64 *val) | |
59 | { | |
60 | struct iwm_priv *iwm = data; | |
61 | ||
62 | *val = iwm->dbg.dbg_level; | |
63 | return 0; | |
64 | } | |
65 | ||
66 | static int iwm_debugfs_dbg_level_write(void *data, u64 val) | |
67 | { | |
68 | struct iwm_priv *iwm = data; | |
69 | int i; | |
70 | ||
71 | iwm->dbg.dbg_level = val; | |
72 | ||
73 | for (i = 0; i < __IWM_DM_NR; i++) | |
74 | iwm->dbg.dbg_module[i] = val; | |
75 | ||
76 | return 0; | |
77 | } | |
78 | DEFINE_SIMPLE_ATTRIBUTE(fops_iwm_dbg_level, | |
79 | iwm_debugfs_u32_read, iwm_debugfs_dbg_level_write, | |
80 | "%llu\n"); | |
81 | ||
82 | static int iwm_debugfs_dbg_modules_write(void *data, u64 val) | |
83 | { | |
84 | struct iwm_priv *iwm = data; | |
85 | int i, bit; | |
86 | ||
87 | iwm->dbg.dbg_modules = val; | |
88 | ||
89 | for (i = 0; i < __IWM_DM_NR; i++) | |
90 | iwm->dbg.dbg_module[i] = 0; | |
91 | ||
92 | for_each_bit(bit, &iwm->dbg.dbg_modules, __IWM_DM_NR) | |
93 | iwm->dbg.dbg_module[bit] = iwm->dbg.dbg_level; | |
94 | ||
95 | return 0; | |
96 | } | |
97 | DEFINE_SIMPLE_ATTRIBUTE(fops_iwm_dbg_modules, | |
98 | iwm_debugfs_u32_read, iwm_debugfs_dbg_modules_write, | |
99 | "%llu\n"); | |
100 | ||
101 | static int iwm_txrx_open(struct inode *inode, struct file *filp) | |
102 | { | |
103 | filp->private_data = inode->i_private; | |
104 | return 0; | |
105 | } | |
106 | ||
107 | ||
108 | static ssize_t iwm_debugfs_txq_read(struct file *filp, char __user *buffer, | |
109 | size_t count, loff_t *ppos) | |
110 | { | |
111 | struct iwm_priv *iwm = filp->private_data; | |
112 | char *buf; | |
113 | int i, buf_len = 4096; | |
114 | size_t len = 0; | |
115 | ssize_t ret; | |
116 | ||
117 | if (*ppos != 0) | |
118 | return 0; | |
119 | if (count < sizeof(buf)) | |
120 | return -ENOSPC; | |
121 | ||
122 | buf = kzalloc(buf_len, GFP_KERNEL); | |
123 | if (!buf) | |
124 | return -ENOMEM; | |
125 | ||
126 | for (i = 0; i < IWM_TX_QUEUES; i++) { | |
127 | struct iwm_tx_queue *txq = &iwm->txq[i]; | |
128 | struct sk_buff *skb; | |
129 | int j; | |
130 | unsigned long flags; | |
131 | ||
132 | spin_lock_irqsave(&txq->queue.lock, flags); | |
133 | ||
134 | skb = (struct sk_buff *)&txq->queue; | |
135 | ||
136 | len += snprintf(buf + len, buf_len - len, "TXQ #%d\n", i); | |
137 | len += snprintf(buf + len, buf_len - len, "\tStopped: %d\n", | |
138 | __netif_subqueue_stopped(iwm_to_ndev(iwm), | |
139 | txq->id)); | |
140 | len += snprintf(buf + len, buf_len - len, "\tConcat count:%d\n", | |
141 | txq->concat_count); | |
142 | len += snprintf(buf + len, buf_len - len, "\tQueue len: %d\n", | |
143 | skb_queue_len(&txq->queue)); | |
144 | for (j = 0; j < skb_queue_len(&txq->queue); j++) { | |
145 | struct iwm_tx_info *tx_info; | |
146 | ||
147 | skb = skb->next; | |
148 | tx_info = skb_to_tx_info(skb); | |
149 | ||
150 | len += snprintf(buf + len, buf_len - len, | |
151 | "\tSKB #%d\n", j); | |
152 | len += snprintf(buf + len, buf_len - len, | |
153 | "\t\tsta: %d\n", tx_info->sta); | |
154 | len += snprintf(buf + len, buf_len - len, | |
155 | "\t\tcolor: %d\n", tx_info->color); | |
156 | len += snprintf(buf + len, buf_len - len, | |
157 | "\t\ttid: %d\n", tx_info->tid); | |
158 | } | |
159 | ||
160 | spin_unlock_irqrestore(&txq->queue.lock, flags); | |
161 | } | |
162 | ||
163 | ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len); | |
164 | kfree(buf); | |
165 | ||
166 | return ret; | |
167 | } | |
168 | ||
169 | static ssize_t iwm_debugfs_tx_credit_read(struct file *filp, | |
170 | char __user *buffer, | |
171 | size_t count, loff_t *ppos) | |
172 | { | |
173 | struct iwm_priv *iwm = filp->private_data; | |
174 | struct iwm_tx_credit *credit = &iwm->tx_credit; | |
175 | char *buf; | |
176 | int i, buf_len = 4096; | |
177 | size_t len = 0; | |
178 | ssize_t ret; | |
179 | ||
180 | if (*ppos != 0) | |
181 | return 0; | |
182 | if (count < sizeof(buf)) | |
183 | return -ENOSPC; | |
184 | ||
185 | buf = kzalloc(buf_len, GFP_KERNEL); | |
186 | if (!buf) | |
187 | return -ENOMEM; | |
188 | ||
189 | len += snprintf(buf + len, buf_len - len, | |
190 | "NR pools: %d\n", credit->pool_nr); | |
191 | len += snprintf(buf + len, buf_len - len, | |
192 | "pools map: 0x%lx\n", credit->full_pools_map); | |
193 | ||
194 | len += snprintf(buf + len, buf_len - len, "\n### POOLS ###\n"); | |
195 | for (i = 0; i < IWM_MACS_OUT_GROUPS; i++) { | |
196 | len += snprintf(buf + len, buf_len - len, | |
197 | "pools entry #%d\n", i); | |
198 | len += snprintf(buf + len, buf_len - len, | |
199 | "\tid: %d\n", | |
200 | credit->pools[i].id); | |
201 | len += snprintf(buf + len, buf_len - len, | |
202 | "\tsid: %d\n", | |
203 | credit->pools[i].sid); | |
204 | len += snprintf(buf + len, buf_len - len, | |
205 | "\tmin_pages: %d\n", | |
206 | credit->pools[i].min_pages); | |
207 | len += snprintf(buf + len, buf_len - len, | |
208 | "\tmax_pages: %d\n", | |
209 | credit->pools[i].max_pages); | |
210 | len += snprintf(buf + len, buf_len - len, | |
211 | "\talloc_pages: %d\n", | |
212 | credit->pools[i].alloc_pages); | |
213 | len += snprintf(buf + len, buf_len - len, | |
214 | "\tfreed_pages: %d\n", | |
215 | credit->pools[i].total_freed_pages); | |
216 | } | |
217 | ||
218 | len += snprintf(buf + len, buf_len - len, "\n### SPOOLS ###\n"); | |
219 | for (i = 0; i < IWM_MACS_OUT_SGROUPS; i++) { | |
220 | len += snprintf(buf + len, buf_len - len, | |
221 | "spools entry #%d\n", i); | |
222 | len += snprintf(buf + len, buf_len - len, | |
223 | "\tid: %d\n", | |
224 | credit->spools[i].id); | |
225 | len += snprintf(buf + len, buf_len - len, | |
226 | "\tmax_pages: %d\n", | |
227 | credit->spools[i].max_pages); | |
228 | len += snprintf(buf + len, buf_len - len, | |
229 | "\talloc_pages: %d\n", | |
230 | credit->spools[i].alloc_pages); | |
231 | ||
232 | } | |
233 | ||
234 | ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len); | |
235 | kfree(buf); | |
236 | ||
237 | return ret; | |
238 | } | |
239 | ||
240 | static ssize_t iwm_debugfs_rx_ticket_read(struct file *filp, | |
241 | char __user *buffer, | |
242 | size_t count, loff_t *ppos) | |
243 | { | |
244 | struct iwm_priv *iwm = filp->private_data; | |
245 | struct iwm_rx_ticket_node *ticket, *next; | |
246 | char *buf; | |
247 | int buf_len = 4096, i; | |
248 | size_t len = 0; | |
249 | ssize_t ret; | |
250 | ||
251 | if (*ppos != 0) | |
252 | return 0; | |
253 | if (count < sizeof(buf)) | |
254 | return -ENOSPC; | |
255 | ||
256 | buf = kzalloc(buf_len, GFP_KERNEL); | |
257 | if (!buf) | |
258 | return -ENOMEM; | |
259 | ||
260 | list_for_each_entry_safe(ticket, next, &iwm->rx_tickets, node) { | |
261 | len += snprintf(buf + len, buf_len - len, "Ticket #%d\n", | |
262 | ticket->ticket->id); | |
263 | len += snprintf(buf + len, buf_len - len, "\taction: 0x%x\n", | |
264 | ticket->ticket->action); | |
265 | len += snprintf(buf + len, buf_len - len, "\tflags: 0x%x\n", | |
266 | ticket->ticket->flags); | |
267 | } | |
268 | ||
269 | for (i = 0; i < IWM_RX_ID_HASH; i++) { | |
270 | struct iwm_rx_packet *packet, *nxt; | |
271 | struct list_head *pkt_list = &iwm->rx_packets[i]; | |
272 | if (!list_empty(pkt_list)) { | |
273 | len += snprintf(buf + len, buf_len - len, | |
274 | "Packet hash #%d\n", i); | |
275 | list_for_each_entry_safe(packet, nxt, pkt_list, node) { | |
276 | len += snprintf(buf + len, buf_len - len, | |
277 | "\tPacket id: %d\n", | |
278 | packet->id); | |
279 | len += snprintf(buf + len, buf_len - len, | |
280 | "\tPacket length: %lu\n", | |
281 | packet->pkt_size); | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len); | |
287 | kfree(buf); | |
288 | ||
289 | return ret; | |
290 | } | |
291 | ||
292 | ||
293 | static const struct file_operations iwm_debugfs_txq_fops = { | |
294 | .owner = THIS_MODULE, | |
295 | .open = iwm_txrx_open, | |
296 | .read = iwm_debugfs_txq_read, | |
297 | }; | |
298 | ||
299 | static const struct file_operations iwm_debugfs_tx_credit_fops = { | |
300 | .owner = THIS_MODULE, | |
301 | .open = iwm_txrx_open, | |
302 | .read = iwm_debugfs_tx_credit_read, | |
303 | }; | |
304 | ||
305 | static const struct file_operations iwm_debugfs_rx_ticket_fops = { | |
306 | .owner = THIS_MODULE, | |
307 | .open = iwm_txrx_open, | |
308 | .read = iwm_debugfs_rx_ticket_read, | |
309 | }; | |
310 | ||
311 | int iwm_debugfs_init(struct iwm_priv *iwm) | |
312 | { | |
313 | int i, result; | |
314 | char devdir[16]; | |
315 | ||
316 | iwm->dbg.rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); | |
317 | result = PTR_ERR(iwm->dbg.rootdir); | |
318 | if (!result || IS_ERR(iwm->dbg.rootdir)) { | |
319 | if (result == -ENODEV) { | |
320 | IWM_ERR(iwm, "DebugFS (CONFIG_DEBUG_FS) not " | |
321 | "enabled in kernel config\n"); | |
322 | result = 0; /* No debugfs support */ | |
323 | } | |
324 | IWM_ERR(iwm, "Couldn't create rootdir: %d\n", result); | |
325 | goto error; | |
326 | } | |
327 | ||
328 | snprintf(devdir, sizeof(devdir), "%s", wiphy_name(iwm_to_wiphy(iwm))); | |
329 | ||
330 | iwm->dbg.devdir = debugfs_create_dir(devdir, iwm->dbg.rootdir); | |
331 | result = PTR_ERR(iwm->dbg.devdir); | |
332 | if (IS_ERR(iwm->dbg.devdir) && (result != -ENODEV)) { | |
333 | IWM_ERR(iwm, "Couldn't create devdir: %d\n", result); | |
334 | goto error; | |
335 | } | |
336 | ||
337 | iwm->dbg.dbgdir = debugfs_create_dir("debug", iwm->dbg.devdir); | |
338 | result = PTR_ERR(iwm->dbg.dbgdir); | |
339 | if (IS_ERR(iwm->dbg.dbgdir) && (result != -ENODEV)) { | |
340 | IWM_ERR(iwm, "Couldn't create dbgdir: %d\n", result); | |
341 | goto error; | |
342 | } | |
343 | ||
344 | iwm->dbg.rxdir = debugfs_create_dir("rx", iwm->dbg.devdir); | |
345 | result = PTR_ERR(iwm->dbg.rxdir); | |
346 | if (IS_ERR(iwm->dbg.rxdir) && (result != -ENODEV)) { | |
347 | IWM_ERR(iwm, "Couldn't create rx dir: %d\n", result); | |
348 | goto error; | |
349 | } | |
350 | ||
351 | iwm->dbg.txdir = debugfs_create_dir("tx", iwm->dbg.devdir); | |
352 | result = PTR_ERR(iwm->dbg.txdir); | |
353 | if (IS_ERR(iwm->dbg.txdir) && (result != -ENODEV)) { | |
354 | IWM_ERR(iwm, "Couldn't create tx dir: %d\n", result); | |
355 | goto error; | |
356 | } | |
357 | ||
358 | iwm->dbg.busdir = debugfs_create_dir("bus", iwm->dbg.devdir); | |
359 | result = PTR_ERR(iwm->dbg.busdir); | |
360 | if (IS_ERR(iwm->dbg.busdir) && (result != -ENODEV)) { | |
361 | IWM_ERR(iwm, "Couldn't create bus dir: %d\n", result); | |
362 | goto error; | |
363 | } | |
364 | ||
365 | if (iwm->bus_ops->debugfs_init) { | |
366 | result = iwm->bus_ops->debugfs_init(iwm, iwm->dbg.busdir); | |
367 | if (result < 0) { | |
368 | IWM_ERR(iwm, "Couldn't create bus entry: %d\n", result); | |
369 | goto error; | |
370 | } | |
371 | } | |
372 | ||
373 | ||
374 | iwm->dbg.dbg_level = IWM_DL_NONE; | |
375 | iwm->dbg.dbg_level_dentry = | |
376 | debugfs_create_file("level", 0200, iwm->dbg.dbgdir, iwm, | |
377 | &fops_iwm_dbg_level); | |
378 | result = PTR_ERR(iwm->dbg.dbg_level_dentry); | |
379 | if (IS_ERR(iwm->dbg.dbg_level_dentry) && (result != -ENODEV)) { | |
380 | IWM_ERR(iwm, "Couldn't create dbg_level: %d\n", result); | |
381 | goto error; | |
382 | } | |
383 | ||
384 | ||
385 | iwm->dbg.dbg_modules = IWM_DM_DEFAULT; | |
386 | iwm->dbg.dbg_modules_dentry = | |
387 | debugfs_create_file("modules", 0200, iwm->dbg.dbgdir, iwm, | |
388 | &fops_iwm_dbg_modules); | |
389 | result = PTR_ERR(iwm->dbg.dbg_modules_dentry); | |
390 | if (IS_ERR(iwm->dbg.dbg_modules_dentry) && (result != -ENODEV)) { | |
391 | IWM_ERR(iwm, "Couldn't create dbg_modules: %d\n", result); | |
392 | goto error; | |
393 | } | |
394 | ||
395 | for (i = 0; i < __IWM_DM_NR; i++) | |
396 | add_dbg_module(iwm->dbg, iwm_debug_module[i].name, | |
397 | iwm_debug_module[i].id, IWM_DL_DEFAULT); | |
398 | ||
399 | iwm->dbg.txq_dentry = debugfs_create_file("queues", 0200, | |
400 | iwm->dbg.txdir, iwm, | |
401 | &iwm_debugfs_txq_fops); | |
402 | result = PTR_ERR(iwm->dbg.txq_dentry); | |
403 | if (IS_ERR(iwm->dbg.txq_dentry) && (result != -ENODEV)) { | |
404 | IWM_ERR(iwm, "Couldn't create tx queue: %d\n", result); | |
405 | goto error; | |
406 | } | |
407 | ||
408 | iwm->dbg.tx_credit_dentry = debugfs_create_file("credits", 0200, | |
409 | iwm->dbg.txdir, iwm, | |
410 | &iwm_debugfs_tx_credit_fops); | |
411 | result = PTR_ERR(iwm->dbg.tx_credit_dentry); | |
412 | if (IS_ERR(iwm->dbg.tx_credit_dentry) && (result != -ENODEV)) { | |
413 | IWM_ERR(iwm, "Couldn't create tx credit: %d\n", result); | |
414 | goto error; | |
415 | } | |
416 | ||
417 | iwm->dbg.rx_ticket_dentry = debugfs_create_file("tickets", 0200, | |
418 | iwm->dbg.rxdir, iwm, | |
419 | &iwm_debugfs_rx_ticket_fops); | |
420 | result = PTR_ERR(iwm->dbg.rx_ticket_dentry); | |
421 | if (IS_ERR(iwm->dbg.rx_ticket_dentry) && (result != -ENODEV)) { | |
422 | IWM_ERR(iwm, "Couldn't create rx ticket: %d\n", result); | |
423 | goto error; | |
424 | } | |
425 | ||
426 | return 0; | |
427 | ||
428 | error: | |
429 | return result; | |
430 | } | |
431 | ||
432 | void iwm_debugfs_exit(struct iwm_priv *iwm) | |
433 | { | |
434 | int i; | |
435 | ||
436 | for (i = 0; i < __IWM_DM_NR; i++) | |
437 | debugfs_remove(iwm->dbg.dbg_module_dentries[i]); | |
438 | ||
439 | debugfs_remove(iwm->dbg.dbg_modules_dentry); | |
440 | debugfs_remove(iwm->dbg.dbg_level_dentry); | |
441 | debugfs_remove(iwm->dbg.txq_dentry); | |
442 | debugfs_remove(iwm->dbg.tx_credit_dentry); | |
443 | debugfs_remove(iwm->dbg.rx_ticket_dentry); | |
444 | if (iwm->bus_ops->debugfs_exit) | |
445 | iwm->bus_ops->debugfs_exit(iwm); | |
446 | ||
447 | debugfs_remove(iwm->dbg.busdir); | |
448 | debugfs_remove(iwm->dbg.dbgdir); | |
449 | debugfs_remove(iwm->dbg.txdir); | |
450 | debugfs_remove(iwm->dbg.rxdir); | |
451 | debugfs_remove(iwm->dbg.devdir); | |
452 | debugfs_remove(iwm->dbg.rootdir); | |
453 | } |