1 | // SPDX-License-Identifier: ISC |
2 | /* |
3 | * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. |
4 | * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/debugfs.h> |
9 | #include <linux/seq_file.h> |
10 | #include <linux/pci.h> |
11 | #include <linux/rtnetlink.h> |
12 | #include <linux/power_supply.h> |
13 | #include "wil6210.h" |
14 | #include "wmi.h" |
15 | #include "txrx.h" |
16 | #include "pmc.h" |
17 | |
18 | /* Nasty hack. Better have per device instances */ |
19 | static u32 mem_addr; |
20 | static u32 dbg_txdesc_index; |
21 | static u32 dbg_ring_index; /* 24+ for Rx, 0..23 for Tx */ |
22 | static u32 dbg_status_msg_index; |
23 | /* 0..wil->num_rx_status_rings-1 for Rx, wil->tx_sring_idx for Tx */ |
24 | static u32 dbg_sring_index; |
25 | |
26 | enum dbg_off_type { |
27 | doff_u32 = 0, |
28 | doff_x32 = 1, |
29 | doff_ulong = 2, |
30 | doff_io32 = 3, |
31 | doff_u8 = 4 |
32 | }; |
33 | |
34 | /* offset to "wil" */ |
35 | struct dbg_off { |
36 | const char *name; |
37 | umode_t mode; |
38 | ulong off; |
39 | enum dbg_off_type type; |
40 | }; |
41 | |
42 | static void wil_print_desc_edma(struct seq_file *s, struct wil6210_priv *wil, |
43 | struct wil_ring *ring, |
44 | char _s, char _h, int idx) |
45 | { |
46 | u8 num_of_descs; |
47 | bool has_skb = false; |
48 | |
49 | if (ring->is_rx) { |
50 | struct wil_rx_enhanced_desc *rx_d = |
51 | (struct wil_rx_enhanced_desc *) |
52 | &ring->va[idx].rx.enhanced; |
53 | u16 buff_id = le16_to_cpu(rx_d->mac.buff_id); |
54 | |
55 | if (wil->rx_buff_mgmt.buff_arr && |
56 | wil_val_in_range(val: buff_id, min: 0, max: wil->rx_buff_mgmt.size)) |
57 | has_skb = wil->rx_buff_mgmt.buff_arr[buff_id].skb; |
58 | seq_printf(m: s, fmt: "%c" , (has_skb) ? _h : _s); |
59 | } else { |
60 | struct wil_tx_enhanced_desc *d = |
61 | (struct wil_tx_enhanced_desc *) |
62 | &ring->va[idx].tx.enhanced; |
63 | |
64 | num_of_descs = (u8)d->mac.d[2]; |
65 | has_skb = ring->ctx && ring->ctx[idx].skb; |
66 | if (num_of_descs >= 1) |
67 | seq_printf(m: s, fmt: "%c" , has_skb ? _h : _s); |
68 | else |
69 | /* num_of_descs == 0, it's a frag in a list of descs */ |
70 | seq_printf(m: s, fmt: "%c" , has_skb ? 'h' : _s); |
71 | } |
72 | } |
73 | |
74 | static void wil_print_ring(struct seq_file *s, struct wil6210_priv *wil, |
75 | const char *name, struct wil_ring *ring, |
76 | char _s, char _h) |
77 | { |
78 | void __iomem *x; |
79 | u32 v; |
80 | |
81 | seq_printf(m: s, fmt: "RING %s = {\n" , name); |
82 | seq_printf(m: s, fmt: " pa = %pad\n" , &ring->pa); |
83 | seq_printf(m: s, fmt: " va = 0x%p\n" , ring->va); |
84 | seq_printf(m: s, fmt: " size = %d\n" , ring->size); |
85 | if (wil->use_enhanced_dma_hw && ring->is_rx) |
86 | seq_printf(m: s, fmt: " swtail = %u\n" , *ring->edma_rx_swtail.va); |
87 | else |
88 | seq_printf(m: s, fmt: " swtail = %d\n" , ring->swtail); |
89 | seq_printf(m: s, fmt: " swhead = %d\n" , ring->swhead); |
90 | if (wil->use_enhanced_dma_hw) { |
91 | int ring_id = ring->is_rx ? |
92 | WIL_RX_DESC_RING_ID : ring - wil->ring_tx; |
93 | /* SUBQ_CONS is a table of 32 entries, one for each Q pair. |
94 | * lower 16bits are for even ring_id and upper 16bits are for |
95 | * odd ring_id |
96 | */ |
97 | x = wmi_addr(wil, RGF_DMA_SCM_SUBQ_CONS + 4 * (ring_id / 2)); |
98 | v = readl_relaxed(x); |
99 | |
100 | v = (ring_id % 2 ? (v >> 16) : (v & 0xffff)); |
101 | seq_printf(m: s, fmt: " hwhead = %u\n" , v); |
102 | } |
103 | seq_printf(m: s, fmt: " hwtail = [0x%08x] -> " , ring->hwtail); |
104 | x = wmi_addr(wil, ptr: ring->hwtail); |
105 | if (x) { |
106 | v = readl(addr: x); |
107 | seq_printf(m: s, fmt: "0x%08x = %d\n" , v, v); |
108 | } else { |
109 | seq_puts(m: s, s: "???\n" ); |
110 | } |
111 | |
112 | if (ring->va && (ring->size <= (1 << WIL_RING_SIZE_ORDER_MAX))) { |
113 | uint i; |
114 | |
115 | for (i = 0; i < ring->size; i++) { |
116 | if ((i % 128) == 0 && i != 0) |
117 | seq_puts(m: s, s: "\n" ); |
118 | if (wil->use_enhanced_dma_hw) { |
119 | wil_print_desc_edma(s, wil, ring, _s, _h, idx: i); |
120 | } else { |
121 | volatile struct vring_tx_desc *d = |
122 | &ring->va[i].tx.legacy; |
123 | seq_printf(m: s, fmt: "%c" , (d->dma.status & BIT(0)) ? |
124 | _s : (ring->ctx[i].skb ? _h : 'h')); |
125 | } |
126 | } |
127 | seq_puts(m: s, s: "\n" ); |
128 | } |
129 | seq_puts(m: s, s: "}\n" ); |
130 | } |
131 | |
132 | static int ring_show(struct seq_file *s, void *data) |
133 | { |
134 | uint i; |
135 | struct wil6210_priv *wil = s->private; |
136 | |
137 | wil_print_ring(s, wil, name: "rx" , ring: &wil->ring_rx, s: 'S', h: '_'); |
138 | |
139 | for (i = 0; i < ARRAY_SIZE(wil->ring_tx); i++) { |
140 | struct wil_ring *ring = &wil->ring_tx[i]; |
141 | struct wil_ring_tx_data *txdata = &wil->ring_tx_data[i]; |
142 | |
143 | if (ring->va) { |
144 | int cid = wil->ring2cid_tid[i][0]; |
145 | int tid = wil->ring2cid_tid[i][1]; |
146 | u32 swhead = ring->swhead; |
147 | u32 swtail = ring->swtail; |
148 | int used = (ring->size + swhead - swtail) |
149 | % ring->size; |
150 | int avail = ring->size - used - 1; |
151 | char name[10]; |
152 | char sidle[10]; |
153 | /* performance monitoring */ |
154 | cycles_t now = get_cycles(); |
155 | uint64_t idle = txdata->idle * 100; |
156 | uint64_t total = now - txdata->begin; |
157 | |
158 | if (total != 0) { |
159 | do_div(idle, total); |
160 | snprintf(buf: sidle, size: sizeof(sidle), fmt: "%3d%%" , |
161 | (int)idle); |
162 | } else { |
163 | snprintf(buf: sidle, size: sizeof(sidle), fmt: "N/A" ); |
164 | } |
165 | txdata->begin = now; |
166 | txdata->idle = 0ULL; |
167 | |
168 | snprintf(buf: name, size: sizeof(name), fmt: "tx_%2d" , i); |
169 | |
170 | if (cid < wil->max_assoc_sta) |
171 | seq_printf(m: s, |
172 | fmt: "\n%pM CID %d TID %d 1x%s BACK([%u] %u TU A%s) [%3d|%3d] idle %s\n" , |
173 | wil->sta[cid].addr, cid, tid, |
174 | txdata->dot1x_open ? "+" : "-" , |
175 | txdata->agg_wsize, |
176 | txdata->agg_timeout, |
177 | txdata->agg_amsdu ? "+" : "-" , |
178 | used, avail, sidle); |
179 | else |
180 | seq_printf(m: s, |
181 | fmt: "\nBroadcast 1x%s [%3d|%3d] idle %s\n" , |
182 | txdata->dot1x_open ? "+" : "-" , |
183 | used, avail, sidle); |
184 | |
185 | wil_print_ring(s, wil, name, ring, s: '_', h: 'H'); |
186 | } |
187 | } |
188 | |
189 | return 0; |
190 | } |
191 | DEFINE_SHOW_ATTRIBUTE(ring); |
192 | |
193 | static void wil_print_sring(struct seq_file *s, struct wil6210_priv *wil, |
194 | struct wil_status_ring *sring) |
195 | { |
196 | void __iomem *x; |
197 | int sring_idx = sring - wil->srings; |
198 | u32 v; |
199 | |
200 | seq_printf(m: s, fmt: "Status Ring %s [ %d ] = {\n" , |
201 | sring->is_rx ? "RX" : "TX" , sring_idx); |
202 | seq_printf(m: s, fmt: " pa = %pad\n" , &sring->pa); |
203 | seq_printf(m: s, fmt: " va = 0x%pK\n" , sring->va); |
204 | seq_printf(m: s, fmt: " size = %d\n" , sring->size); |
205 | seq_printf(m: s, fmt: " elem_size = %zu\n" , sring->elem_size); |
206 | seq_printf(m: s, fmt: " swhead = %d\n" , sring->swhead); |
207 | if (wil->use_enhanced_dma_hw) { |
208 | /* COMPQ_PROD is a table of 32 entries, one for each Q pair. |
209 | * lower 16bits are for even ring_id and upper 16bits are for |
210 | * odd ring_id |
211 | */ |
212 | x = wmi_addr(wil, RGF_DMA_SCM_COMPQ_PROD + 4 * (sring_idx / 2)); |
213 | v = readl_relaxed(x); |
214 | |
215 | v = (sring_idx % 2 ? (v >> 16) : (v & 0xffff)); |
216 | seq_printf(m: s, fmt: " hwhead = %u\n" , v); |
217 | } |
218 | seq_printf(m: s, fmt: " hwtail = [0x%08x] -> " , sring->hwtail); |
219 | x = wmi_addr(wil, ptr: sring->hwtail); |
220 | if (x) { |
221 | v = readl_relaxed(x); |
222 | seq_printf(m: s, fmt: "0x%08x = %d\n" , v, v); |
223 | } else { |
224 | seq_puts(m: s, s: "???\n" ); |
225 | } |
226 | seq_printf(m: s, fmt: " desc_rdy_pol = %d\n" , sring->desc_rdy_pol); |
227 | seq_printf(m: s, fmt: " invalid_buff_id_cnt = %d\n" , |
228 | sring->invalid_buff_id_cnt); |
229 | |
230 | if (sring->va && (sring->size <= (1 << WIL_RING_SIZE_ORDER_MAX))) { |
231 | uint i; |
232 | |
233 | for (i = 0; i < sring->size; i++) { |
234 | u32 *sdword_0 = |
235 | (u32 *)(sring->va + (sring->elem_size * i)); |
236 | |
237 | if ((i % 128) == 0 && i != 0) |
238 | seq_puts(m: s, s: "\n" ); |
239 | if (i == sring->swhead) |
240 | seq_printf(m: s, fmt: "%c" , (*sdword_0 & BIT(31)) ? |
241 | 'X' : 'x'); |
242 | else |
243 | seq_printf(m: s, fmt: "%c" , (*sdword_0 & BIT(31)) ? |
244 | '1' : '0'); |
245 | } |
246 | seq_puts(m: s, s: "\n" ); |
247 | } |
248 | seq_puts(m: s, s: "}\n" ); |
249 | } |
250 | |
251 | static int srings_show(struct seq_file *s, void *data) |
252 | { |
253 | struct wil6210_priv *wil = s->private; |
254 | int i = 0; |
255 | |
256 | for (i = 0; i < WIL6210_MAX_STATUS_RINGS; i++) |
257 | if (wil->srings[i].va) |
258 | wil_print_sring(s, wil, sring: &wil->srings[i]); |
259 | |
260 | return 0; |
261 | } |
262 | DEFINE_SHOW_ATTRIBUTE(srings); |
263 | |
264 | static void wil_seq_hexdump(struct seq_file *s, void *p, int len, |
265 | const char *prefix) |
266 | { |
267 | seq_hex_dump(m: s, prefix_str: prefix, prefix_type: DUMP_PREFIX_NONE, rowsize: 16, groupsize: 1, buf: p, len, ascii: false); |
268 | } |
269 | |
270 | static void wil_print_mbox_ring(struct seq_file *s, const char *prefix, |
271 | void __iomem *off) |
272 | { |
273 | struct wil6210_priv *wil = s->private; |
274 | struct wil6210_mbox_ring r; |
275 | int rsize; |
276 | uint i; |
277 | |
278 | wil_halp_vote(wil); |
279 | |
280 | if (wil_mem_access_lock(wil)) { |
281 | wil_halp_unvote(wil); |
282 | return; |
283 | } |
284 | |
285 | wil_memcpy_fromio_32(dst: &r, src: off, count: sizeof(r)); |
286 | wil_mbox_ring_le2cpus(r: &r); |
287 | /* |
288 | * we just read memory block from NIC. This memory may be |
289 | * garbage. Check validity before using it. |
290 | */ |
291 | rsize = r.size / sizeof(struct wil6210_mbox_ring_desc); |
292 | |
293 | seq_printf(m: s, fmt: "ring %s = {\n" , prefix); |
294 | seq_printf(m: s, fmt: " base = 0x%08x\n" , r.base); |
295 | seq_printf(m: s, fmt: " size = 0x%04x bytes -> %d entries\n" , r.size, rsize); |
296 | seq_printf(m: s, fmt: " tail = 0x%08x\n" , r.tail); |
297 | seq_printf(m: s, fmt: " head = 0x%08x\n" , r.head); |
298 | seq_printf(m: s, fmt: " entry size = %d\n" , r.entry_size); |
299 | |
300 | if (r.size % sizeof(struct wil6210_mbox_ring_desc)) { |
301 | seq_printf(m: s, fmt: " ??? size is not multiple of %zd, garbage?\n" , |
302 | sizeof(struct wil6210_mbox_ring_desc)); |
303 | goto out; |
304 | } |
305 | |
306 | if (!wmi_addr(wil, ptr: r.base) || |
307 | !wmi_addr(wil, ptr: r.tail) || |
308 | !wmi_addr(wil, ptr: r.head)) { |
309 | seq_puts(m: s, s: " ??? pointers are garbage?\n" ); |
310 | goto out; |
311 | } |
312 | |
313 | for (i = 0; i < rsize; i++) { |
314 | struct wil6210_mbox_ring_desc d; |
315 | struct wil6210_mbox_hdr hdr; |
316 | size_t delta = i * sizeof(d); |
317 | void __iomem *x = wil->csr + HOSTADDR(r.base) + delta; |
318 | |
319 | wil_memcpy_fromio_32(dst: &d, src: x, count: sizeof(d)); |
320 | |
321 | seq_printf(m: s, fmt: " [%2x] %s %s%s 0x%08x" , i, |
322 | d.sync ? "F" : "E" , |
323 | (r.tail - r.base == delta) ? "t" : " " , |
324 | (r.head - r.base == delta) ? "h" : " " , |
325 | le32_to_cpu(d.addr)); |
326 | if (0 == wmi_read_hdr(wil, ptr: d.addr, hdr: &hdr)) { |
327 | u16 len = le16_to_cpu(hdr.len); |
328 | |
329 | seq_printf(m: s, fmt: " -> %04x %04x %04x %02x\n" , |
330 | le16_to_cpu(hdr.seq), len, |
331 | le16_to_cpu(hdr.type), hdr.flags); |
332 | if (len <= MAX_MBOXITEM_SIZE) { |
333 | unsigned char databuf[MAX_MBOXITEM_SIZE]; |
334 | void __iomem *src = wmi_buffer(wil, ptr: d.addr) + |
335 | sizeof(struct wil6210_mbox_hdr); |
336 | /* |
337 | * No need to check @src for validity - |
338 | * we already validated @d.addr while |
339 | * reading header |
340 | */ |
341 | wil_memcpy_fromio_32(dst: databuf, src, count: len); |
342 | wil_seq_hexdump(s, p: databuf, len, prefix: " : " ); |
343 | } |
344 | } else { |
345 | seq_puts(m: s, s: "\n" ); |
346 | } |
347 | } |
348 | out: |
349 | seq_puts(m: s, s: "}\n" ); |
350 | wil_mem_access_unlock(wil); |
351 | wil_halp_unvote(wil); |
352 | } |
353 | |
354 | static int mbox_show(struct seq_file *s, void *data) |
355 | { |
356 | struct wil6210_priv *wil = s->private; |
357 | int ret; |
358 | |
359 | ret = wil_pm_runtime_get(wil); |
360 | if (ret < 0) |
361 | return ret; |
362 | |
363 | wil_print_mbox_ring(s, prefix: "tx" , off: wil->csr + HOST_MBOX + |
364 | offsetof(struct wil6210_mbox_ctl, tx)); |
365 | wil_print_mbox_ring(s, prefix: "rx" , off: wil->csr + HOST_MBOX + |
366 | offsetof(struct wil6210_mbox_ctl, rx)); |
367 | |
368 | wil_pm_runtime_put(wil); |
369 | |
370 | return 0; |
371 | } |
372 | DEFINE_SHOW_ATTRIBUTE(mbox); |
373 | |
374 | static int wil_debugfs_iomem_x32_set(void *data, u64 val) |
375 | { |
376 | struct wil_debugfs_iomem_data *d = (struct |
377 | wil_debugfs_iomem_data *)data; |
378 | struct wil6210_priv *wil = d->wil; |
379 | int ret; |
380 | |
381 | ret = wil_pm_runtime_get(wil); |
382 | if (ret < 0) |
383 | return ret; |
384 | |
385 | writel_relaxed(val, (void __iomem *)d->offset); |
386 | |
387 | wmb(); /* make sure write propagated to HW */ |
388 | |
389 | wil_pm_runtime_put(wil); |
390 | |
391 | return 0; |
392 | } |
393 | |
394 | static int wil_debugfs_iomem_x32_get(void *data, u64 *val) |
395 | { |
396 | struct wil_debugfs_iomem_data *d = (struct |
397 | wil_debugfs_iomem_data *)data; |
398 | struct wil6210_priv *wil = d->wil; |
399 | int ret; |
400 | |
401 | ret = wil_pm_runtime_get(wil); |
402 | if (ret < 0) |
403 | return ret; |
404 | |
405 | *val = readl(addr: (void __iomem *)d->offset); |
406 | |
407 | wil_pm_runtime_put(wil); |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | DEFINE_DEBUGFS_ATTRIBUTE(fops_iomem_x32, wil_debugfs_iomem_x32_get, |
413 | wil_debugfs_iomem_x32_set, "0x%08llx\n" ); |
414 | |
415 | static void wil_debugfs_create_iomem_x32(const char *name, umode_t mode, |
416 | struct dentry *parent, void *value, |
417 | struct wil6210_priv *wil) |
418 | { |
419 | struct wil_debugfs_iomem_data *data = &wil->dbg_data.data_arr[ |
420 | wil->dbg_data.iomem_data_count]; |
421 | |
422 | data->wil = wil; |
423 | data->offset = value; |
424 | |
425 | debugfs_create_file_unsafe(name, mode, parent, data, fops: &fops_iomem_x32); |
426 | wil->dbg_data.iomem_data_count++; |
427 | } |
428 | |
429 | static int wil_debugfs_ulong_set(void *data, u64 val) |
430 | { |
431 | *(ulong *)data = val; |
432 | return 0; |
433 | } |
434 | |
435 | static int wil_debugfs_ulong_get(void *data, u64 *val) |
436 | { |
437 | *val = *(ulong *)data; |
438 | return 0; |
439 | } |
440 | |
441 | DEFINE_DEBUGFS_ATTRIBUTE(wil_fops_ulong, wil_debugfs_ulong_get, |
442 | wil_debugfs_ulong_set, "0x%llx\n" ); |
443 | |
444 | /** |
445 | * wil6210_debugfs_init_offset - create set of debugfs files |
446 | * @wil: driver's context, used for printing |
447 | * @dbg: directory on the debugfs, where files will be created |
448 | * @base: base address used in address calculation |
449 | * @tbl: table with file descriptions. Should be terminated with empty element. |
450 | * |
451 | * Creates files accordingly to the @tbl. |
452 | */ |
453 | static void wil6210_debugfs_init_offset(struct wil6210_priv *wil, |
454 | struct dentry *dbg, void *base, |
455 | const struct dbg_off * const tbl) |
456 | { |
457 | int i; |
458 | |
459 | for (i = 0; tbl[i].name; i++) { |
460 | switch (tbl[i].type) { |
461 | case doff_u32: |
462 | debugfs_create_u32(name: tbl[i].name, mode: tbl[i].mode, parent: dbg, |
463 | value: base + tbl[i].off); |
464 | break; |
465 | case doff_x32: |
466 | debugfs_create_x32(name: tbl[i].name, mode: tbl[i].mode, parent: dbg, |
467 | value: base + tbl[i].off); |
468 | break; |
469 | case doff_ulong: |
470 | debugfs_create_file_unsafe(name: tbl[i].name, mode: tbl[i].mode, |
471 | parent: dbg, data: base + tbl[i].off, |
472 | fops: &wil_fops_ulong); |
473 | break; |
474 | case doff_io32: |
475 | wil_debugfs_create_iomem_x32(name: tbl[i].name, mode: tbl[i].mode, |
476 | parent: dbg, value: base + tbl[i].off, |
477 | wil); |
478 | break; |
479 | case doff_u8: |
480 | debugfs_create_u8(name: tbl[i].name, mode: tbl[i].mode, parent: dbg, |
481 | value: base + tbl[i].off); |
482 | break; |
483 | } |
484 | } |
485 | } |
486 | |
487 | static const struct dbg_off isr_off[] = { |
488 | {"ICC" , 0644, offsetof(struct RGF_ICR, ICC), doff_io32}, |
489 | {"ICR" , 0644, offsetof(struct RGF_ICR, ICR), doff_io32}, |
490 | {"ICM" , 0644, offsetof(struct RGF_ICR, ICM), doff_io32}, |
491 | {"ICS" , 0244, offsetof(struct RGF_ICR, ICS), doff_io32}, |
492 | {"IMV" , 0644, offsetof(struct RGF_ICR, IMV), doff_io32}, |
493 | {"IMS" , 0244, offsetof(struct RGF_ICR, IMS), doff_io32}, |
494 | {"IMC" , 0244, offsetof(struct RGF_ICR, IMC), doff_io32}, |
495 | {}, |
496 | }; |
497 | |
498 | static void wil6210_debugfs_create_ISR(struct wil6210_priv *wil, |
499 | const char *name, struct dentry *parent, |
500 | u32 off) |
501 | { |
502 | struct dentry *d = debugfs_create_dir(name, parent); |
503 | |
504 | wil6210_debugfs_init_offset(wil, dbg: d, base: (void * __force)wil->csr + off, |
505 | tbl: isr_off); |
506 | } |
507 | |
508 | static const struct dbg_off pseudo_isr_off[] = { |
509 | {"CAUSE" , 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE), doff_io32}, |
510 | {"MASK_SW" , 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW), doff_io32}, |
511 | {"MASK_FW" , 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW), doff_io32}, |
512 | {}, |
513 | }; |
514 | |
515 | static void wil6210_debugfs_create_pseudo_ISR(struct wil6210_priv *wil, |
516 | struct dentry *parent) |
517 | { |
518 | struct dentry *d = debugfs_create_dir(name: "PSEUDO_ISR" , parent); |
519 | |
520 | wil6210_debugfs_init_offset(wil, dbg: d, base: (void * __force)wil->csr, |
521 | tbl: pseudo_isr_off); |
522 | } |
523 | |
524 | static const struct dbg_off lgc_itr_cnt_off[] = { |
525 | {"TRSH" , 0644, HOSTADDR(RGF_DMA_ITR_CNT_TRSH), doff_io32}, |
526 | {"DATA" , 0644, HOSTADDR(RGF_DMA_ITR_CNT_DATA), doff_io32}, |
527 | {"CTL" , 0644, HOSTADDR(RGF_DMA_ITR_CNT_CRL), doff_io32}, |
528 | {}, |
529 | }; |
530 | |
531 | static const struct dbg_off tx_itr_cnt_off[] = { |
532 | {"TRSH" , 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_TRSH), |
533 | doff_io32}, |
534 | {"DATA" , 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_DATA), |
535 | doff_io32}, |
536 | {"CTL" , 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_CTL), |
537 | doff_io32}, |
538 | {"IDL_TRSH" , 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_TRSH), |
539 | doff_io32}, |
540 | {"IDL_DATA" , 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_DATA), |
541 | doff_io32}, |
542 | {"IDL_CTL" , 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_CTL), |
543 | doff_io32}, |
544 | {}, |
545 | }; |
546 | |
547 | static const struct dbg_off rx_itr_cnt_off[] = { |
548 | {"TRSH" , 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_TRSH), |
549 | doff_io32}, |
550 | {"DATA" , 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_DATA), |
551 | doff_io32}, |
552 | {"CTL" , 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_CTL), |
553 | doff_io32}, |
554 | {"IDL_TRSH" , 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_TRSH), |
555 | doff_io32}, |
556 | {"IDL_DATA" , 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_DATA), |
557 | doff_io32}, |
558 | {"IDL_CTL" , 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_CTL), |
559 | doff_io32}, |
560 | {}, |
561 | }; |
562 | |
563 | static int wil6210_debugfs_create_ITR_CNT(struct wil6210_priv *wil, |
564 | struct dentry *parent) |
565 | { |
566 | struct dentry *d, *dtx, *drx; |
567 | |
568 | d = debugfs_create_dir(name: "ITR_CNT" , parent); |
569 | |
570 | dtx = debugfs_create_dir(name: "TX" , parent: d); |
571 | drx = debugfs_create_dir(name: "RX" , parent: d); |
572 | |
573 | wil6210_debugfs_init_offset(wil, dbg: d, base: (void * __force)wil->csr, |
574 | tbl: lgc_itr_cnt_off); |
575 | |
576 | wil6210_debugfs_init_offset(wil, dbg: dtx, base: (void * __force)wil->csr, |
577 | tbl: tx_itr_cnt_off); |
578 | |
579 | wil6210_debugfs_init_offset(wil, dbg: drx, base: (void * __force)wil->csr, |
580 | tbl: rx_itr_cnt_off); |
581 | return 0; |
582 | } |
583 | |
584 | static int memread_show(struct seq_file *s, void *data) |
585 | { |
586 | struct wil6210_priv *wil = s->private; |
587 | void __iomem *a; |
588 | int ret; |
589 | |
590 | ret = wil_pm_runtime_get(wil); |
591 | if (ret < 0) |
592 | return ret; |
593 | |
594 | ret = wil_mem_access_lock(wil); |
595 | if (ret) { |
596 | wil_pm_runtime_put(wil); |
597 | return ret; |
598 | } |
599 | |
600 | a = wmi_buffer(wil, cpu_to_le32(mem_addr)); |
601 | |
602 | if (a) |
603 | seq_printf(m: s, fmt: "[0x%08x] = 0x%08x\n" , mem_addr, readl(addr: a)); |
604 | else |
605 | seq_printf(m: s, fmt: "[0x%08x] = INVALID\n" , mem_addr); |
606 | |
607 | wil_mem_access_unlock(wil); |
608 | wil_pm_runtime_put(wil); |
609 | |
610 | return 0; |
611 | } |
612 | DEFINE_SHOW_ATTRIBUTE(memread); |
613 | |
614 | static ssize_t wil_read_file_ioblob(struct file *file, char __user *user_buf, |
615 | size_t count, loff_t *ppos) |
616 | { |
617 | enum { max_count = 4096 }; |
618 | struct wil_blob_wrapper *wil_blob = file->private_data; |
619 | struct wil6210_priv *wil = wil_blob->wil; |
620 | loff_t aligned_pos, pos = *ppos; |
621 | size_t available = wil_blob->blob.size; |
622 | void *buf; |
623 | size_t unaligned_bytes, aligned_count, ret; |
624 | int rc; |
625 | |
626 | if (pos < 0) |
627 | return -EINVAL; |
628 | |
629 | if (pos >= available || !count) |
630 | return 0; |
631 | |
632 | if (count > available - pos) |
633 | count = available - pos; |
634 | if (count > max_count) |
635 | count = max_count; |
636 | |
637 | /* set pos to 4 bytes aligned */ |
638 | unaligned_bytes = pos % 4; |
639 | aligned_pos = pos - unaligned_bytes; |
640 | aligned_count = count + unaligned_bytes; |
641 | |
642 | buf = kmalloc(size: aligned_count, GFP_KERNEL); |
643 | if (!buf) |
644 | return -ENOMEM; |
645 | |
646 | rc = wil_pm_runtime_get(wil); |
647 | if (rc < 0) { |
648 | kfree(objp: buf); |
649 | return rc; |
650 | } |
651 | |
652 | rc = wil_mem_access_lock(wil); |
653 | if (rc) { |
654 | kfree(objp: buf); |
655 | wil_pm_runtime_put(wil); |
656 | return rc; |
657 | } |
658 | |
659 | wil_memcpy_fromio_32(dst: buf, src: (const void __iomem *) |
660 | wil_blob->blob.data + aligned_pos, count: aligned_count); |
661 | |
662 | ret = copy_to_user(to: user_buf, from: buf + unaligned_bytes, n: count); |
663 | |
664 | wil_mem_access_unlock(wil); |
665 | wil_pm_runtime_put(wil); |
666 | |
667 | kfree(objp: buf); |
668 | if (ret == count) |
669 | return -EFAULT; |
670 | |
671 | count -= ret; |
672 | *ppos = pos + count; |
673 | |
674 | return count; |
675 | } |
676 | |
677 | static const struct file_operations fops_ioblob = { |
678 | .read = wil_read_file_ioblob, |
679 | .open = simple_open, |
680 | .llseek = default_llseek, |
681 | }; |
682 | |
683 | static |
684 | struct dentry *wil_debugfs_create_ioblob(const char *name, |
685 | umode_t mode, |
686 | struct dentry *parent, |
687 | struct wil_blob_wrapper *wil_blob) |
688 | { |
689 | return debugfs_create_file(name, mode, parent, data: wil_blob, fops: &fops_ioblob); |
690 | } |
691 | |
692 | /*---write channel 1..4 to rxon for it, 0 to rxoff---*/ |
693 | static ssize_t wil_write_file_rxon(struct file *file, const char __user *buf, |
694 | size_t len, loff_t *ppos) |
695 | { |
696 | struct wil6210_priv *wil = file->private_data; |
697 | int rc; |
698 | long channel; |
699 | bool on; |
700 | |
701 | char *kbuf = memdup_user_nul(buf, len); |
702 | |
703 | if (IS_ERR(ptr: kbuf)) |
704 | return PTR_ERR(ptr: kbuf); |
705 | rc = kstrtol(s: kbuf, base: 0, res: &channel); |
706 | kfree(objp: kbuf); |
707 | if (rc) |
708 | return rc; |
709 | |
710 | if ((channel < 0) || (channel > 4)) { |
711 | wil_err(wil, "Invalid channel %ld\n" , channel); |
712 | return -EINVAL; |
713 | } |
714 | on = !!channel; |
715 | |
716 | if (on) { |
717 | rc = wmi_set_channel(wil, channel: (int)channel); |
718 | if (rc) |
719 | return rc; |
720 | } |
721 | |
722 | rc = wmi_rxon(wil, on); |
723 | if (rc) |
724 | return rc; |
725 | |
726 | return len; |
727 | } |
728 | |
729 | static const struct file_operations fops_rxon = { |
730 | .write = wil_write_file_rxon, |
731 | .open = simple_open, |
732 | }; |
733 | |
734 | static ssize_t wil_write_file_rbufcap(struct file *file, |
735 | const char __user *buf, |
736 | size_t count, loff_t *ppos) |
737 | { |
738 | struct wil6210_priv *wil = file->private_data; |
739 | int val; |
740 | int rc; |
741 | |
742 | rc = kstrtoint_from_user(s: buf, count, base: 0, res: &val); |
743 | if (rc) { |
744 | wil_err(wil, "Invalid argument\n" ); |
745 | return rc; |
746 | } |
747 | /* input value: negative to disable, 0 to use system default, |
748 | * 1..ring size to set descriptor threshold |
749 | */ |
750 | wil_info(wil, "%s RBUFCAP, descriptors threshold - %d\n" , |
751 | val < 0 ? "Disabling" : "Enabling" , val); |
752 | |
753 | if (!wil->ring_rx.va || val > wil->ring_rx.size) { |
754 | wil_err(wil, "Invalid descriptors threshold, %d\n" , val); |
755 | return -EINVAL; |
756 | } |
757 | |
758 | rc = wmi_rbufcap_cfg(wil, enable: val < 0 ? 0 : 1, threshold: val < 0 ? 0 : val); |
759 | if (rc) { |
760 | wil_err(wil, "RBUFCAP config failed: %d\n" , rc); |
761 | return rc; |
762 | } |
763 | |
764 | return count; |
765 | } |
766 | |
767 | static const struct file_operations fops_rbufcap = { |
768 | .write = wil_write_file_rbufcap, |
769 | .open = simple_open, |
770 | }; |
771 | |
772 | /* block ack control, write: |
773 | * - "add <ringid> <agg_size> <timeout>" to trigger ADDBA |
774 | * - "del_tx <ringid> <reason>" to trigger DELBA for Tx side |
775 | * - "del_rx <CID> <TID> <reason>" to trigger DELBA for Rx side |
776 | */ |
777 | static ssize_t wil_write_back(struct file *file, const char __user *buf, |
778 | size_t len, loff_t *ppos) |
779 | { |
780 | struct wil6210_priv *wil = file->private_data; |
781 | int rc; |
782 | char *kbuf = kmalloc(size: len + 1, GFP_KERNEL); |
783 | char cmd[9]; |
784 | int p1, p2, p3; |
785 | |
786 | if (!kbuf) |
787 | return -ENOMEM; |
788 | |
789 | rc = simple_write_to_buffer(to: kbuf, available: len, ppos, from: buf, count: len); |
790 | if (rc != len) { |
791 | kfree(objp: kbuf); |
792 | return rc >= 0 ? -EIO : rc; |
793 | } |
794 | |
795 | kbuf[len] = '\0'; |
796 | rc = sscanf(kbuf, "%8s %d %d %d" , cmd, &p1, &p2, &p3); |
797 | kfree(objp: kbuf); |
798 | |
799 | if (rc < 0) |
800 | return rc; |
801 | if (rc < 2) |
802 | return -EINVAL; |
803 | |
804 | if ((strcmp(cmd, "add" ) == 0) || |
805 | (strcmp(cmd, "del_tx" ) == 0)) { |
806 | struct wil_ring_tx_data *txdata; |
807 | |
808 | if (p1 < 0 || p1 >= WIL6210_MAX_TX_RINGS) { |
809 | wil_err(wil, "BACK: invalid ring id %d\n" , p1); |
810 | return -EINVAL; |
811 | } |
812 | txdata = &wil->ring_tx_data[p1]; |
813 | if (strcmp(cmd, "add" ) == 0) { |
814 | if (rc < 3) { |
815 | wil_err(wil, "BACK: add require at least 2 params\n" ); |
816 | return -EINVAL; |
817 | } |
818 | if (rc < 4) |
819 | p3 = 0; |
820 | wmi_addba(wil, mid: txdata->mid, ringid: p1, size: p2, timeout: p3); |
821 | } else { |
822 | if (rc < 3) |
823 | p2 = WLAN_REASON_QSTA_LEAVE_QBSS; |
824 | wmi_delba_tx(wil, mid: txdata->mid, ringid: p1, reason: p2); |
825 | } |
826 | } else if (strcmp(cmd, "del_rx" ) == 0) { |
827 | struct wil_sta_info *sta; |
828 | |
829 | if (rc < 3) { |
830 | wil_err(wil, |
831 | "BACK: del_rx require at least 2 params\n" ); |
832 | return -EINVAL; |
833 | } |
834 | if (p1 < 0 || p1 >= wil->max_assoc_sta) { |
835 | wil_err(wil, "BACK: invalid CID %d\n" , p1); |
836 | return -EINVAL; |
837 | } |
838 | if (rc < 4) |
839 | p3 = WLAN_REASON_QSTA_LEAVE_QBSS; |
840 | sta = &wil->sta[p1]; |
841 | wmi_delba_rx(wil, mid: sta->mid, cid: p1, tid: p2, reason: p3); |
842 | } else { |
843 | wil_err(wil, "BACK: Unrecognized command \"%s\"\n" , cmd); |
844 | return -EINVAL; |
845 | } |
846 | |
847 | return len; |
848 | } |
849 | |
850 | static ssize_t wil_read_back(struct file *file, char __user *user_buf, |
851 | size_t count, loff_t *ppos) |
852 | { |
853 | static const char text[] = "block ack control, write:\n" |
854 | " - \"add <ringid> <agg_size> <timeout>\" to trigger ADDBA\n" |
855 | "If missing, <timeout> defaults to 0\n" |
856 | " - \"del_tx <ringid> <reason>\" to trigger DELBA for Tx side\n" |
857 | " - \"del_rx <CID> <TID> <reason>\" to trigger DELBA for Rx side\n" |
858 | "If missing, <reason> set to \"STA_LEAVING\" (36)\n" ; |
859 | |
860 | return simple_read_from_buffer(to: user_buf, count, ppos, from: text, |
861 | available: sizeof(text)); |
862 | } |
863 | |
864 | static const struct file_operations fops_back = { |
865 | .read = wil_read_back, |
866 | .write = wil_write_back, |
867 | .open = simple_open, |
868 | }; |
869 | |
870 | /* pmc control, write: |
871 | * - "alloc <num descriptors> <descriptor_size>" to allocate PMC |
872 | * - "free" to release memory allocated for PMC |
873 | */ |
874 | static ssize_t wil_write_pmccfg(struct file *file, const char __user *buf, |
875 | size_t len, loff_t *ppos) |
876 | { |
877 | struct wil6210_priv *wil = file->private_data; |
878 | int rc; |
879 | char *kbuf = kmalloc(size: len + 1, GFP_KERNEL); |
880 | char cmd[9]; |
881 | int num_descs, desc_size; |
882 | |
883 | if (!kbuf) |
884 | return -ENOMEM; |
885 | |
886 | rc = simple_write_to_buffer(to: kbuf, available: len, ppos, from: buf, count: len); |
887 | if (rc != len) { |
888 | kfree(objp: kbuf); |
889 | return rc >= 0 ? -EIO : rc; |
890 | } |
891 | |
892 | kbuf[len] = '\0'; |
893 | rc = sscanf(kbuf, "%8s %d %d" , cmd, &num_descs, &desc_size); |
894 | kfree(objp: kbuf); |
895 | |
896 | if (rc < 0) |
897 | return rc; |
898 | |
899 | if (rc < 1) { |
900 | wil_err(wil, "pmccfg: no params given\n" ); |
901 | return -EINVAL; |
902 | } |
903 | |
904 | if (0 == strcmp(cmd, "alloc" )) { |
905 | if (rc != 3) { |
906 | wil_err(wil, "pmccfg: alloc requires 2 params\n" ); |
907 | return -EINVAL; |
908 | } |
909 | wil_pmc_alloc(wil, num_descriptors: num_descs, descriptor_size: desc_size); |
910 | } else if (0 == strcmp(cmd, "free" )) { |
911 | if (rc != 1) { |
912 | wil_err(wil, "pmccfg: free does not have any params\n" ); |
913 | return -EINVAL; |
914 | } |
915 | wil_pmc_free(wil, send_pmc_cmd: true); |
916 | } else { |
917 | wil_err(wil, "pmccfg: Unrecognized command \"%s\"\n" , cmd); |
918 | return -EINVAL; |
919 | } |
920 | |
921 | return len; |
922 | } |
923 | |
924 | static ssize_t wil_read_pmccfg(struct file *file, char __user *user_buf, |
925 | size_t count, loff_t *ppos) |
926 | { |
927 | struct wil6210_priv *wil = file->private_data; |
928 | char text[256]; |
929 | char help[] = "pmc control, write:\n" |
930 | " - \"alloc <num descriptors> <descriptor_size>\" to allocate pmc\n" |
931 | " - \"free\" to free memory allocated for pmc\n" ; |
932 | |
933 | snprintf(buf: text, size: sizeof(text), fmt: "Last command status: %d\n\n%s" , |
934 | wil_pmc_last_cmd_status(wil), help); |
935 | |
936 | return simple_read_from_buffer(to: user_buf, count, ppos, from: text, |
937 | strlen(text) + 1); |
938 | } |
939 | |
940 | static const struct file_operations fops_pmccfg = { |
941 | .read = wil_read_pmccfg, |
942 | .write = wil_write_pmccfg, |
943 | .open = simple_open, |
944 | }; |
945 | |
946 | static const struct file_operations fops_pmcdata = { |
947 | .open = simple_open, |
948 | .read = wil_pmc_read, |
949 | .llseek = wil_pmc_llseek, |
950 | }; |
951 | |
952 | static int wil_pmcring_seq_open(struct inode *inode, struct file *file) |
953 | { |
954 | return single_open(file, wil_pmcring_read, inode->i_private); |
955 | } |
956 | |
957 | static const struct file_operations fops_pmcring = { |
958 | .open = wil_pmcring_seq_open, |
959 | .release = single_release, |
960 | .read = seq_read, |
961 | .llseek = seq_lseek, |
962 | }; |
963 | |
964 | /*---tx_mgmt---*/ |
965 | /* Write mgmt frame to this file to send it */ |
966 | static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, |
967 | size_t len, loff_t *ppos) |
968 | { |
969 | struct wil6210_priv *wil = file->private_data; |
970 | struct wiphy *wiphy = wil_to_wiphy(wil); |
971 | struct wireless_dev *wdev = wil->main_ndev->ieee80211_ptr; |
972 | struct cfg80211_mgmt_tx_params params; |
973 | int rc; |
974 | void *frame; |
975 | |
976 | memset(¶ms, 0, sizeof(params)); |
977 | |
978 | if (!len) |
979 | return -EINVAL; |
980 | |
981 | frame = memdup_user(buf, len); |
982 | if (IS_ERR(ptr: frame)) |
983 | return PTR_ERR(ptr: frame); |
984 | |
985 | params.buf = frame; |
986 | params.len = len; |
987 | |
988 | rc = wil_cfg80211_mgmt_tx(wiphy, wdev, params: ¶ms, NULL); |
989 | |
990 | kfree(objp: frame); |
991 | wil_info(wil, "-> %d\n" , rc); |
992 | |
993 | return len; |
994 | } |
995 | |
996 | static const struct file_operations fops_txmgmt = { |
997 | .write = wil_write_file_txmgmt, |
998 | .open = simple_open, |
999 | }; |
1000 | |
1001 | /* Write WMI command (w/o mbox header) to this file to send it |
1002 | * WMI starts from wil6210_mbox_hdr_wmi header |
1003 | */ |
1004 | static ssize_t wil_write_file_wmi(struct file *file, const char __user *buf, |
1005 | size_t len, loff_t *ppos) |
1006 | { |
1007 | struct wil6210_priv *wil = file->private_data; |
1008 | struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev); |
1009 | struct wmi_cmd_hdr *wmi; |
1010 | void *cmd; |
1011 | int cmdlen = len - sizeof(struct wmi_cmd_hdr); |
1012 | u16 cmdid; |
1013 | int rc1; |
1014 | |
1015 | if (cmdlen < 0 || *ppos != 0) |
1016 | return -EINVAL; |
1017 | |
1018 | wmi = memdup_user(buf, len); |
1019 | if (IS_ERR(ptr: wmi)) |
1020 | return PTR_ERR(ptr: wmi); |
1021 | |
1022 | cmd = (cmdlen > 0) ? &wmi[1] : NULL; |
1023 | cmdid = le16_to_cpu(wmi->command_id); |
1024 | |
1025 | rc1 = wmi_send(wil, cmdid, mid: vif->mid, buf: cmd, len: cmdlen); |
1026 | kfree(objp: wmi); |
1027 | |
1028 | wil_info(wil, "0x%04x[%d] -> %d\n" , cmdid, cmdlen, rc1); |
1029 | |
1030 | return len; |
1031 | } |
1032 | |
1033 | static const struct file_operations fops_wmi = { |
1034 | .write = wil_write_file_wmi, |
1035 | .open = simple_open, |
1036 | }; |
1037 | |
1038 | static void wil_seq_print_skb(struct seq_file *s, struct sk_buff *skb) |
1039 | { |
1040 | int i = 0; |
1041 | int len = skb_headlen(skb); |
1042 | void *p = skb->data; |
1043 | int nr_frags = skb_shinfo(skb)->nr_frags; |
1044 | |
1045 | seq_printf(m: s, fmt: " len = %d\n" , len); |
1046 | wil_seq_hexdump(s, p, len, prefix: " : " ); |
1047 | |
1048 | if (nr_frags) { |
1049 | seq_printf(m: s, fmt: " nr_frags = %d\n" , nr_frags); |
1050 | for (i = 0; i < nr_frags; i++) { |
1051 | const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; |
1052 | |
1053 | len = skb_frag_size(frag); |
1054 | p = skb_frag_address_safe(frag); |
1055 | seq_printf(m: s, fmt: " [%2d] : len = %d\n" , i, len); |
1056 | wil_seq_hexdump(s, p, len, prefix: " : " ); |
1057 | } |
1058 | } |
1059 | } |
1060 | |
1061 | /*---------Tx/Rx descriptor------------*/ |
1062 | static int txdesc_show(struct seq_file *s, void *data) |
1063 | { |
1064 | struct wil6210_priv *wil = s->private; |
1065 | struct wil_ring *ring; |
1066 | bool tx; |
1067 | int ring_idx = dbg_ring_index; |
1068 | int txdesc_idx = dbg_txdesc_index; |
1069 | volatile struct vring_tx_desc *d; |
1070 | volatile u32 *u; |
1071 | struct sk_buff *skb; |
1072 | |
1073 | if (wil->use_enhanced_dma_hw) { |
1074 | /* RX ring index == 0 */ |
1075 | if (ring_idx >= WIL6210_MAX_TX_RINGS) { |
1076 | seq_printf(m: s, fmt: "invalid ring index %d\n" , ring_idx); |
1077 | return 0; |
1078 | } |
1079 | tx = ring_idx > 0; /* desc ring 0 is reserved for RX */ |
1080 | } else { |
1081 | /* RX ring index == WIL6210_MAX_TX_RINGS */ |
1082 | if (ring_idx > WIL6210_MAX_TX_RINGS) { |
1083 | seq_printf(m: s, fmt: "invalid ring index %d\n" , ring_idx); |
1084 | return 0; |
1085 | } |
1086 | tx = (ring_idx < WIL6210_MAX_TX_RINGS); |
1087 | } |
1088 | |
1089 | ring = tx ? &wil->ring_tx[ring_idx] : &wil->ring_rx; |
1090 | |
1091 | if (!ring->va) { |
1092 | if (tx) |
1093 | seq_printf(m: s, fmt: "No Tx[%2d] RING\n" , ring_idx); |
1094 | else |
1095 | seq_puts(m: s, s: "No Rx RING\n" ); |
1096 | return 0; |
1097 | } |
1098 | |
1099 | if (txdesc_idx >= ring->size) { |
1100 | if (tx) |
1101 | seq_printf(m: s, fmt: "[%2d] TxDesc index (%d) >= size (%d)\n" , |
1102 | ring_idx, txdesc_idx, ring->size); |
1103 | else |
1104 | seq_printf(m: s, fmt: "RxDesc index (%d) >= size (%d)\n" , |
1105 | txdesc_idx, ring->size); |
1106 | return 0; |
1107 | } |
1108 | |
1109 | /* use struct vring_tx_desc for Rx as well, |
1110 | * only field used, .dma.length, is the same |
1111 | */ |
1112 | d = &ring->va[txdesc_idx].tx.legacy; |
1113 | u = (volatile u32 *)d; |
1114 | skb = NULL; |
1115 | |
1116 | if (wil->use_enhanced_dma_hw) { |
1117 | if (tx) { |
1118 | skb = ring->ctx ? ring->ctx[txdesc_idx].skb : NULL; |
1119 | } else if (wil->rx_buff_mgmt.buff_arr) { |
1120 | struct wil_rx_enhanced_desc *rx_d = |
1121 | (struct wil_rx_enhanced_desc *) |
1122 | &ring->va[txdesc_idx].rx.enhanced; |
1123 | u16 buff_id = le16_to_cpu(rx_d->mac.buff_id); |
1124 | |
1125 | if (!wil_val_in_range(val: buff_id, min: 0, |
1126 | max: wil->rx_buff_mgmt.size)) |
1127 | seq_printf(m: s, fmt: "invalid buff_id %d\n" , buff_id); |
1128 | else |
1129 | skb = wil->rx_buff_mgmt.buff_arr[buff_id].skb; |
1130 | } |
1131 | } else { |
1132 | skb = ring->ctx[txdesc_idx].skb; |
1133 | } |
1134 | if (tx) |
1135 | seq_printf(m: s, fmt: "Tx[%2d][%3d] = {\n" , ring_idx, |
1136 | txdesc_idx); |
1137 | else |
1138 | seq_printf(m: s, fmt: "Rx[%3d] = {\n" , txdesc_idx); |
1139 | seq_printf(m: s, fmt: " MAC = 0x%08x 0x%08x 0x%08x 0x%08x\n" , |
1140 | u[0], u[1], u[2], u[3]); |
1141 | seq_printf(m: s, fmt: " DMA = 0x%08x 0x%08x 0x%08x 0x%08x\n" , |
1142 | u[4], u[5], u[6], u[7]); |
1143 | seq_printf(m: s, fmt: " SKB = 0x%p\n" , skb); |
1144 | |
1145 | if (skb) { |
1146 | skb_get(skb); |
1147 | wil_seq_print_skb(s, skb); |
1148 | kfree_skb(skb); |
1149 | } |
1150 | seq_puts(m: s, s: "}\n" ); |
1151 | |
1152 | return 0; |
1153 | } |
1154 | DEFINE_SHOW_ATTRIBUTE(txdesc); |
1155 | |
1156 | /*---------Tx/Rx status message------------*/ |
1157 | static int status_msg_show(struct seq_file *s, void *data) |
1158 | { |
1159 | struct wil6210_priv *wil = s->private; |
1160 | int sring_idx = dbg_sring_index; |
1161 | struct wil_status_ring *sring; |
1162 | bool tx; |
1163 | u32 status_msg_idx = dbg_status_msg_index; |
1164 | u32 *u; |
1165 | |
1166 | if (sring_idx >= WIL6210_MAX_STATUS_RINGS) { |
1167 | seq_printf(m: s, fmt: "invalid status ring index %d\n" , sring_idx); |
1168 | return 0; |
1169 | } |
1170 | |
1171 | sring = &wil->srings[sring_idx]; |
1172 | tx = !sring->is_rx; |
1173 | |
1174 | if (!sring->va) { |
1175 | seq_printf(m: s, fmt: "No %cX status ring\n" , tx ? 'T' : 'R'); |
1176 | return 0; |
1177 | } |
1178 | |
1179 | if (status_msg_idx >= sring->size) { |
1180 | seq_printf(m: s, fmt: "%cxDesc index (%d) >= size (%d)\n" , |
1181 | tx ? 'T' : 'R', status_msg_idx, sring->size); |
1182 | return 0; |
1183 | } |
1184 | |
1185 | u = sring->va + (sring->elem_size * status_msg_idx); |
1186 | |
1187 | seq_printf(m: s, fmt: "%cx[%d][%3d] = {\n" , |
1188 | tx ? 'T' : 'R', sring_idx, status_msg_idx); |
1189 | |
1190 | seq_printf(m: s, fmt: " 0x%08x 0x%08x 0x%08x 0x%08x\n" , |
1191 | u[0], u[1], u[2], u[3]); |
1192 | if (!tx && !wil->use_compressed_rx_status) |
1193 | seq_printf(m: s, fmt: " 0x%08x 0x%08x 0x%08x 0x%08x\n" , |
1194 | u[4], u[5], u[6], u[7]); |
1195 | |
1196 | seq_puts(m: s, s: "}\n" ); |
1197 | |
1198 | return 0; |
1199 | } |
1200 | DEFINE_SHOW_ATTRIBUTE(status_msg); |
1201 | |
1202 | static int wil_print_rx_buff(struct seq_file *s, struct list_head *lh) |
1203 | { |
1204 | struct wil_rx_buff *it; |
1205 | int i = 0; |
1206 | |
1207 | list_for_each_entry(it, lh, list) { |
1208 | if ((i % 16) == 0 && i != 0) |
1209 | seq_puts(m: s, s: "\n " ); |
1210 | seq_printf(m: s, fmt: "[%4d] " , it->id); |
1211 | i++; |
1212 | } |
1213 | seq_printf(m: s, fmt: "\nNumber of buffers: %u\n" , i); |
1214 | |
1215 | return i; |
1216 | } |
1217 | |
1218 | static int rx_buff_mgmt_show(struct seq_file *s, void *data) |
1219 | { |
1220 | struct wil6210_priv *wil = s->private; |
1221 | struct wil_rx_buff_mgmt *rbm = &wil->rx_buff_mgmt; |
1222 | int num_active; |
1223 | int num_free; |
1224 | |
1225 | if (!rbm->buff_arr) |
1226 | return -EINVAL; |
1227 | |
1228 | seq_printf(m: s, fmt: " size = %zu\n" , rbm->size); |
1229 | seq_printf(m: s, fmt: " free_list_empty_cnt = %lu\n" , |
1230 | rbm->free_list_empty_cnt); |
1231 | |
1232 | /* Print active list */ |
1233 | seq_puts(m: s, s: " Active list:\n" ); |
1234 | num_active = wil_print_rx_buff(s, lh: &rbm->active); |
1235 | seq_puts(m: s, s: "\n Free list:\n" ); |
1236 | num_free = wil_print_rx_buff(s, lh: &rbm->free); |
1237 | |
1238 | seq_printf(m: s, fmt: " Total number of buffers: %u\n" , |
1239 | num_active + num_free); |
1240 | |
1241 | return 0; |
1242 | } |
1243 | DEFINE_SHOW_ATTRIBUTE(rx_buff_mgmt); |
1244 | |
1245 | /*---------beamforming------------*/ |
1246 | static char *wil_bfstatus_str(u32 status) |
1247 | { |
1248 | switch (status) { |
1249 | case 0: |
1250 | return "Failed" ; |
1251 | case 1: |
1252 | return "OK" ; |
1253 | case 2: |
1254 | return "Retrying" ; |
1255 | default: |
1256 | return "??" ; |
1257 | } |
1258 | } |
1259 | |
1260 | static bool is_all_zeros(void * const x_, size_t sz) |
1261 | { |
1262 | /* if reply is all-0, ignore this CID */ |
1263 | u32 *x = x_; |
1264 | int n; |
1265 | |
1266 | for (n = 0; n < sz / sizeof(*x); n++) |
1267 | if (x[n]) |
1268 | return false; |
1269 | |
1270 | return true; |
1271 | } |
1272 | |
1273 | static int bf_show(struct seq_file *s, void *data) |
1274 | { |
1275 | int rc; |
1276 | int i; |
1277 | struct wil6210_priv *wil = s->private; |
1278 | struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev); |
1279 | struct wmi_notify_req_cmd cmd = { |
1280 | .interval_usec = 0, |
1281 | }; |
1282 | struct { |
1283 | struct wmi_cmd_hdr wmi; |
1284 | struct wmi_notify_req_done_event evt; |
1285 | } __packed reply; |
1286 | |
1287 | memset(&reply, 0, sizeof(reply)); |
1288 | |
1289 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1290 | u32 status; |
1291 | u8 bf_mcs; |
1292 | |
1293 | cmd.cid = i; |
1294 | rc = wmi_call(wil, cmdid: WMI_NOTIFY_REQ_CMDID, mid: vif->mid, |
1295 | buf: &cmd, len: sizeof(cmd), |
1296 | reply_id: WMI_NOTIFY_REQ_DONE_EVENTID, reply: &reply, |
1297 | reply_size: sizeof(reply), WIL_WMI_CALL_GENERAL_TO_MS); |
1298 | /* if reply is all-0, ignore this CID */ |
1299 | if (rc || is_all_zeros(x_: &reply.evt, sz: sizeof(reply.evt))) |
1300 | continue; |
1301 | |
1302 | status = le32_to_cpu(reply.evt.status); |
1303 | bf_mcs = le16_to_cpu(reply.evt.bf_mcs); |
1304 | seq_printf(m: s, fmt: "CID %d {\n" |
1305 | " TSF = 0x%016llx\n" |
1306 | " TxMCS = %s TxTpt = %4d\n" |
1307 | " SQI = %4d\n" |
1308 | " RSSI = %4d\n" |
1309 | " Status = 0x%08x %s\n" |
1310 | " Sectors(rx:tx) my %2d:%2d peer %2d:%2d\n" |
1311 | " Goodput(rx:tx) %4d:%4d\n" |
1312 | "}\n" , |
1313 | i, |
1314 | le64_to_cpu(reply.evt.tsf), |
1315 | WIL_EXTENDED_MCS_CHECK(bf_mcs), |
1316 | le32_to_cpu(reply.evt.tx_tpt), |
1317 | reply.evt.sqi, |
1318 | reply.evt.rssi, |
1319 | status, wil_bfstatus_str(status), |
1320 | le16_to_cpu(reply.evt.my_rx_sector), |
1321 | le16_to_cpu(reply.evt.my_tx_sector), |
1322 | le16_to_cpu(reply.evt.other_rx_sector), |
1323 | le16_to_cpu(reply.evt.other_tx_sector), |
1324 | le32_to_cpu(reply.evt.rx_goodput), |
1325 | le32_to_cpu(reply.evt.tx_goodput)); |
1326 | } |
1327 | return 0; |
1328 | } |
1329 | DEFINE_SHOW_ATTRIBUTE(bf); |
1330 | |
1331 | /*---------temp------------*/ |
1332 | static void print_temp(struct seq_file *s, const char *prefix, s32 t) |
1333 | { |
1334 | switch (t) { |
1335 | case 0: |
1336 | case WMI_INVALID_TEMPERATURE: |
1337 | seq_printf(m: s, fmt: "%s N/A\n" , prefix); |
1338 | break; |
1339 | default: |
1340 | seq_printf(m: s, fmt: "%s %s%d.%03d\n" , prefix, (t < 0 ? "-" : "" ), |
1341 | abs(t / 1000), abs(t % 1000)); |
1342 | break; |
1343 | } |
1344 | } |
1345 | |
1346 | static int temp_show(struct seq_file *s, void *data) |
1347 | { |
1348 | struct wil6210_priv *wil = s->private; |
1349 | int rc, i; |
1350 | |
1351 | if (test_bit(WMI_FW_CAPABILITY_TEMPERATURE_ALL_RF, |
1352 | wil->fw_capabilities)) { |
1353 | struct wmi_temp_sense_all_done_event sense_all_evt; |
1354 | |
1355 | wil_dbg_misc(wil, |
1356 | "WMI_FW_CAPABILITY_TEMPERATURE_ALL_RF is supported" ); |
1357 | rc = wmi_get_all_temperatures(wil, sense_all_evt: &sense_all_evt); |
1358 | if (rc) { |
1359 | seq_puts(m: s, s: "Failed\n" ); |
1360 | return 0; |
1361 | } |
1362 | print_temp(s, prefix: "T_mac =" , |
1363 | le32_to_cpu(sense_all_evt.baseband_t1000)); |
1364 | seq_printf(m: s, fmt: "Connected RFs [0x%08x]\n" , |
1365 | sense_all_evt.rf_bitmap); |
1366 | for (i = 0; i < WMI_MAX_XIF_PORTS_NUM; i++) { |
1367 | seq_printf(m: s, fmt: "RF[%d] = " , i); |
1368 | print_temp(s, prefix: "" , |
1369 | le32_to_cpu(sense_all_evt.rf_t1000[i])); |
1370 | } |
1371 | } else { |
1372 | s32 t_m, t_r; |
1373 | |
1374 | wil_dbg_misc(wil, |
1375 | "WMI_FW_CAPABILITY_TEMPERATURE_ALL_RF is not supported" ); |
1376 | rc = wmi_get_temperature(wil, t_m: &t_m, t_r: &t_r); |
1377 | if (rc) { |
1378 | seq_puts(m: s, s: "Failed\n" ); |
1379 | return 0; |
1380 | } |
1381 | print_temp(s, prefix: "T_mac =" , t: t_m); |
1382 | print_temp(s, prefix: "T_radio =" , t: t_r); |
1383 | } |
1384 | return 0; |
1385 | } |
1386 | DEFINE_SHOW_ATTRIBUTE(temp); |
1387 | |
1388 | /*---------link------------*/ |
1389 | static int link_show(struct seq_file *s, void *data) |
1390 | { |
1391 | struct wil6210_priv *wil = s->private; |
1392 | struct station_info *sinfo; |
1393 | int i, rc = 0; |
1394 | |
1395 | sinfo = kzalloc(size: sizeof(*sinfo), GFP_KERNEL); |
1396 | if (!sinfo) |
1397 | return -ENOMEM; |
1398 | |
1399 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1400 | struct wil_sta_info *p = &wil->sta[i]; |
1401 | char *status = "unknown" ; |
1402 | struct wil6210_vif *vif; |
1403 | u8 mid; |
1404 | |
1405 | switch (p->status) { |
1406 | case wil_sta_unused: |
1407 | status = "unused " ; |
1408 | break; |
1409 | case wil_sta_conn_pending: |
1410 | status = "pending " ; |
1411 | break; |
1412 | case wil_sta_connected: |
1413 | status = "connected" ; |
1414 | break; |
1415 | } |
1416 | mid = (p->status != wil_sta_unused) ? p->mid : U8_MAX; |
1417 | seq_printf(m: s, fmt: "[%d][MID %d] %pM %s\n" , |
1418 | i, mid, p->addr, status); |
1419 | |
1420 | if (p->status != wil_sta_connected) |
1421 | continue; |
1422 | |
1423 | vif = (mid < GET_MAX_VIFS(wil)) ? wil->vifs[mid] : NULL; |
1424 | if (vif) { |
1425 | rc = wil_cid_fill_sinfo(vif, cid: i, sinfo); |
1426 | if (rc) |
1427 | goto out; |
1428 | |
1429 | seq_printf(m: s, fmt: " Tx_mcs = %s\n" , |
1430 | WIL_EXTENDED_MCS_CHECK(sinfo->txrate.mcs)); |
1431 | seq_printf(m: s, fmt: " Rx_mcs = %s\n" , |
1432 | WIL_EXTENDED_MCS_CHECK(sinfo->rxrate.mcs)); |
1433 | seq_printf(m: s, fmt: " SQ = %d\n" , sinfo->signal); |
1434 | } else { |
1435 | seq_puts(m: s, s: " INVALID MID\n" ); |
1436 | } |
1437 | } |
1438 | |
1439 | out: |
1440 | kfree(objp: sinfo); |
1441 | return rc; |
1442 | } |
1443 | DEFINE_SHOW_ATTRIBUTE(link); |
1444 | |
1445 | /*---------info------------*/ |
1446 | static int info_show(struct seq_file *s, void *data) |
1447 | { |
1448 | struct wil6210_priv *wil = s->private; |
1449 | struct net_device *ndev = wil->main_ndev; |
1450 | int is_ac = power_supply_is_system_supplied(); |
1451 | int rx = atomic_xchg(v: &wil->isr_count_rx, new: 0); |
1452 | int tx = atomic_xchg(v: &wil->isr_count_tx, new: 0); |
1453 | static ulong rxf_old, txf_old; |
1454 | ulong rxf = ndev->stats.rx_packets; |
1455 | ulong txf = ndev->stats.tx_packets; |
1456 | unsigned int i; |
1457 | |
1458 | /* >0 : AC; 0 : battery; <0 : error */ |
1459 | seq_printf(m: s, fmt: "AC powered : %d\n" , is_ac); |
1460 | seq_printf(m: s, fmt: "Rx irqs:packets : %8d : %8ld\n" , rx, rxf - rxf_old); |
1461 | seq_printf(m: s, fmt: "Tx irqs:packets : %8d : %8ld\n" , tx, txf - txf_old); |
1462 | rxf_old = rxf; |
1463 | txf_old = txf; |
1464 | |
1465 | #define CHECK_QSTATE(x) (state & BIT(__QUEUE_STATE_ ## x)) ? \ |
1466 | " " __stringify(x) : "" |
1467 | |
1468 | for (i = 0; i < ndev->num_tx_queues; i++) { |
1469 | struct netdev_queue *txq = netdev_get_tx_queue(dev: ndev, index: i); |
1470 | unsigned long state = txq->state; |
1471 | |
1472 | seq_printf(m: s, fmt: "Tx queue[%i] state : 0x%lx%s%s%s\n" , i, state, |
1473 | CHECK_QSTATE(DRV_XOFF), |
1474 | CHECK_QSTATE(STACK_XOFF), |
1475 | CHECK_QSTATE(FROZEN) |
1476 | ); |
1477 | } |
1478 | #undef CHECK_QSTATE |
1479 | return 0; |
1480 | } |
1481 | DEFINE_SHOW_ATTRIBUTE(info); |
1482 | |
1483 | /*---------recovery------------*/ |
1484 | /* mode = [manual|auto] |
1485 | * state = [idle|pending|running] |
1486 | */ |
1487 | static ssize_t wil_read_file_recovery(struct file *file, char __user *user_buf, |
1488 | size_t count, loff_t *ppos) |
1489 | { |
1490 | struct wil6210_priv *wil = file->private_data; |
1491 | char buf[80]; |
1492 | int n; |
1493 | static const char * const sstate[] = {"idle" , "pending" , "running" }; |
1494 | |
1495 | n = snprintf(buf, size: sizeof(buf), fmt: "mode = %s\nstate = %s\n" , |
1496 | no_fw_recovery ? "manual" : "auto" , |
1497 | sstate[wil->recovery_state]); |
1498 | |
1499 | n = min_t(int, n, sizeof(buf)); |
1500 | |
1501 | return simple_read_from_buffer(to: user_buf, count, ppos, |
1502 | from: buf, available: n); |
1503 | } |
1504 | |
1505 | static ssize_t wil_write_file_recovery(struct file *file, |
1506 | const char __user *buf_, |
1507 | size_t count, loff_t *ppos) |
1508 | { |
1509 | struct wil6210_priv *wil = file->private_data; |
1510 | static const char run_command[] = "run" ; |
1511 | char buf[sizeof(run_command) + 1]; /* to detect "runx" */ |
1512 | ssize_t rc; |
1513 | |
1514 | if (wil->recovery_state != fw_recovery_pending) { |
1515 | wil_err(wil, "No recovery pending\n" ); |
1516 | return -EINVAL; |
1517 | } |
1518 | |
1519 | if (*ppos != 0) { |
1520 | wil_err(wil, "Offset [%d]\n" , (int)*ppos); |
1521 | return -EINVAL; |
1522 | } |
1523 | |
1524 | if (count > sizeof(buf)) { |
1525 | wil_err(wil, "Input too long, len = %d\n" , (int)count); |
1526 | return -EINVAL; |
1527 | } |
1528 | |
1529 | rc = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: buf_, count); |
1530 | if (rc < 0) |
1531 | return rc; |
1532 | |
1533 | buf[rc] = '\0'; |
1534 | if (0 == strcmp(buf, run_command)) |
1535 | wil_set_recovery_state(wil, state: fw_recovery_running); |
1536 | else |
1537 | wil_err(wil, "Bad recovery command \"%s\"\n" , buf); |
1538 | |
1539 | return rc; |
1540 | } |
1541 | |
1542 | static const struct file_operations fops_recovery = { |
1543 | .read = wil_read_file_recovery, |
1544 | .write = wil_write_file_recovery, |
1545 | .open = simple_open, |
1546 | }; |
1547 | |
1548 | /*---------Station matrix------------*/ |
1549 | static void wil_print_rxtid(struct seq_file *s, struct wil_tid_ampdu_rx *r) |
1550 | { |
1551 | int i; |
1552 | u16 index = ((r->head_seq_num - r->ssn) & 0xfff) % r->buf_size; |
1553 | unsigned long long drop_dup = r->drop_dup, drop_old = r->drop_old; |
1554 | unsigned long long drop_dup_mcast = r->drop_dup_mcast; |
1555 | |
1556 | seq_printf(m: s, fmt: "([%2d]) 0x%03x [" , r->buf_size, r->head_seq_num); |
1557 | for (i = 0; i < r->buf_size; i++) { |
1558 | if (i == index) |
1559 | seq_printf(m: s, fmt: "%c" , r->reorder_buf[i] ? 'O' : '|'); |
1560 | else |
1561 | seq_printf(m: s, fmt: "%c" , r->reorder_buf[i] ? '*' : '_'); |
1562 | } |
1563 | seq_printf(m: s, |
1564 | fmt: "] total %llu drop %llu (dup %llu + old %llu + dup mcast %llu) last 0x%03x\n" , |
1565 | r->total, drop_dup + drop_old + drop_dup_mcast, drop_dup, |
1566 | drop_old, drop_dup_mcast, r->ssn_last_drop); |
1567 | } |
1568 | |
1569 | static void wil_print_rxtid_crypto(struct seq_file *s, int tid, |
1570 | struct wil_tid_crypto_rx *c) |
1571 | { |
1572 | int i; |
1573 | |
1574 | for (i = 0; i < 4; i++) { |
1575 | struct wil_tid_crypto_rx_single *cc = &c->key_id[i]; |
1576 | |
1577 | if (cc->key_set) |
1578 | goto has_keys; |
1579 | } |
1580 | return; |
1581 | |
1582 | has_keys: |
1583 | if (tid < WIL_STA_TID_NUM) |
1584 | seq_printf(m: s, fmt: " [%2d] PN" , tid); |
1585 | else |
1586 | seq_puts(m: s, s: " [GR] PN" ); |
1587 | |
1588 | for (i = 0; i < 4; i++) { |
1589 | struct wil_tid_crypto_rx_single *cc = &c->key_id[i]; |
1590 | |
1591 | seq_printf(m: s, fmt: " [%i%s]%6phN" , i, cc->key_set ? "+" : "-" , |
1592 | cc->pn); |
1593 | } |
1594 | seq_puts(m: s, s: "\n" ); |
1595 | } |
1596 | |
1597 | static int sta_show(struct seq_file *s, void *data) |
1598 | __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) |
1599 | { |
1600 | struct wil6210_priv *wil = s->private; |
1601 | int i, tid, mcs; |
1602 | |
1603 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1604 | struct wil_sta_info *p = &wil->sta[i]; |
1605 | char *status = "unknown" ; |
1606 | u8 aid = 0; |
1607 | u8 mid; |
1608 | bool sta_connected = false; |
1609 | |
1610 | switch (p->status) { |
1611 | case wil_sta_unused: |
1612 | status = "unused " ; |
1613 | break; |
1614 | case wil_sta_conn_pending: |
1615 | status = "pending " ; |
1616 | break; |
1617 | case wil_sta_connected: |
1618 | status = "connected" ; |
1619 | aid = p->aid; |
1620 | break; |
1621 | } |
1622 | mid = (p->status != wil_sta_unused) ? p->mid : U8_MAX; |
1623 | if (mid < GET_MAX_VIFS(wil)) { |
1624 | struct wil6210_vif *vif = wil->vifs[mid]; |
1625 | |
1626 | if (vif->wdev.iftype == NL80211_IFTYPE_STATION && |
1627 | p->status == wil_sta_connected) |
1628 | sta_connected = true; |
1629 | } |
1630 | /* print roam counter only for connected stations */ |
1631 | if (sta_connected) |
1632 | seq_printf(m: s, fmt: "[%d] %pM connected (roam counter %d) MID %d AID %d\n" , |
1633 | i, p->addr, p->stats.ft_roams, mid, aid); |
1634 | else |
1635 | seq_printf(m: s, fmt: "[%d] %pM %s MID %d AID %d\n" , i, |
1636 | p->addr, status, mid, aid); |
1637 | |
1638 | if (p->status == wil_sta_connected) { |
1639 | spin_lock_bh(lock: &p->tid_rx_lock); |
1640 | for (tid = 0; tid < WIL_STA_TID_NUM; tid++) { |
1641 | struct wil_tid_ampdu_rx *r = p->tid_rx[tid]; |
1642 | struct wil_tid_crypto_rx *c = |
1643 | &p->tid_crypto_rx[tid]; |
1644 | |
1645 | if (r) { |
1646 | seq_printf(m: s, fmt: " [%2d] " , tid); |
1647 | wil_print_rxtid(s, r); |
1648 | } |
1649 | |
1650 | wil_print_rxtid_crypto(s, tid, c); |
1651 | } |
1652 | wil_print_rxtid_crypto(s, WIL_STA_TID_NUM, |
1653 | c: &p->group_crypto_rx); |
1654 | spin_unlock_bh(lock: &p->tid_rx_lock); |
1655 | seq_printf(m: s, |
1656 | fmt: "Rx invalid frame: non-data %lu, short %lu, large %lu, replay %lu\n" , |
1657 | p->stats.rx_non_data_frame, |
1658 | p->stats.rx_short_frame, |
1659 | p->stats.rx_large_frame, |
1660 | p->stats.rx_replay); |
1661 | seq_printf(m: s, |
1662 | fmt: "mic error %lu, key error %lu, amsdu error %lu, csum error %lu\n" , |
1663 | p->stats.rx_mic_error, |
1664 | p->stats.rx_key_error, |
1665 | p->stats.rx_amsdu_error, |
1666 | p->stats.rx_csum_err); |
1667 | |
1668 | seq_puts(m: s, s: "Rx/MCS:" ); |
1669 | for (mcs = 0; mcs < ARRAY_SIZE(p->stats.rx_per_mcs); |
1670 | mcs++) |
1671 | seq_printf(m: s, fmt: " %lld" , |
1672 | p->stats.rx_per_mcs[mcs]); |
1673 | seq_puts(m: s, s: "\n" ); |
1674 | } |
1675 | } |
1676 | |
1677 | return 0; |
1678 | } |
1679 | DEFINE_SHOW_ATTRIBUTE(sta); |
1680 | |
1681 | static int mids_show(struct seq_file *s, void *data) |
1682 | { |
1683 | struct wil6210_priv *wil = s->private; |
1684 | struct wil6210_vif *vif; |
1685 | struct net_device *ndev; |
1686 | int i; |
1687 | |
1688 | mutex_lock(&wil->vif_mutex); |
1689 | for (i = 0; i < GET_MAX_VIFS(wil); i++) { |
1690 | vif = wil->vifs[i]; |
1691 | |
1692 | if (vif) { |
1693 | ndev = vif_to_ndev(vif); |
1694 | seq_printf(m: s, fmt: "[%d] %pM %s\n" , i, ndev->dev_addr, |
1695 | ndev->name); |
1696 | } else { |
1697 | seq_printf(m: s, fmt: "[%d] unused\n" , i); |
1698 | } |
1699 | } |
1700 | mutex_unlock(lock: &wil->vif_mutex); |
1701 | |
1702 | return 0; |
1703 | } |
1704 | DEFINE_SHOW_ATTRIBUTE(mids); |
1705 | |
1706 | static int wil_tx_latency_debugfs_show(struct seq_file *s, void *data) |
1707 | __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) |
1708 | { |
1709 | struct wil6210_priv *wil = s->private; |
1710 | int i, bin; |
1711 | |
1712 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1713 | struct wil_sta_info *p = &wil->sta[i]; |
1714 | char *status = "unknown" ; |
1715 | u8 aid = 0; |
1716 | u8 mid; |
1717 | |
1718 | if (!p->tx_latency_bins) |
1719 | continue; |
1720 | |
1721 | switch (p->status) { |
1722 | case wil_sta_unused: |
1723 | status = "unused " ; |
1724 | break; |
1725 | case wil_sta_conn_pending: |
1726 | status = "pending " ; |
1727 | break; |
1728 | case wil_sta_connected: |
1729 | status = "connected" ; |
1730 | aid = p->aid; |
1731 | break; |
1732 | } |
1733 | mid = (p->status != wil_sta_unused) ? p->mid : U8_MAX; |
1734 | seq_printf(m: s, fmt: "[%d] %pM %s MID %d AID %d\n" , i, p->addr, status, |
1735 | mid, aid); |
1736 | |
1737 | if (p->status == wil_sta_connected) { |
1738 | u64 num_packets = 0; |
1739 | u64 tx_latency_avg = p->stats.tx_latency_total_us; |
1740 | |
1741 | seq_puts(m: s, s: "Tx/Latency bin:" ); |
1742 | for (bin = 0; bin < WIL_NUM_LATENCY_BINS; bin++) { |
1743 | seq_printf(m: s, fmt: " %lld" , |
1744 | p->tx_latency_bins[bin]); |
1745 | num_packets += p->tx_latency_bins[bin]; |
1746 | } |
1747 | seq_puts(m: s, s: "\n" ); |
1748 | if (!num_packets) |
1749 | continue; |
1750 | do_div(tx_latency_avg, num_packets); |
1751 | seq_printf(m: s, fmt: "Tx/Latency min/avg/max (us): %d/%lld/%d" , |
1752 | p->stats.tx_latency_min_us, |
1753 | tx_latency_avg, |
1754 | p->stats.tx_latency_max_us); |
1755 | |
1756 | seq_puts(m: s, s: "\n" ); |
1757 | } |
1758 | } |
1759 | |
1760 | return 0; |
1761 | } |
1762 | |
1763 | static int wil_tx_latency_seq_open(struct inode *inode, struct file *file) |
1764 | { |
1765 | return single_open(file, wil_tx_latency_debugfs_show, |
1766 | inode->i_private); |
1767 | } |
1768 | |
1769 | static ssize_t wil_tx_latency_write(struct file *file, const char __user *buf, |
1770 | size_t len, loff_t *ppos) |
1771 | { |
1772 | struct seq_file *s = file->private_data; |
1773 | struct wil6210_priv *wil = s->private; |
1774 | int val, rc, i; |
1775 | bool enable; |
1776 | |
1777 | rc = kstrtoint_from_user(s: buf, count: len, base: 0, res: &val); |
1778 | if (rc) { |
1779 | wil_err(wil, "Invalid argument\n" ); |
1780 | return rc; |
1781 | } |
1782 | if (val == 1) |
1783 | /* default resolution */ |
1784 | val = 500; |
1785 | if (val && (val < 50 || val > 1000)) { |
1786 | wil_err(wil, "Invalid resolution %d\n" , val); |
1787 | return -EINVAL; |
1788 | } |
1789 | |
1790 | enable = !!val; |
1791 | if (wil->tx_latency == enable) |
1792 | return len; |
1793 | |
1794 | wil_info(wil, "%s TX latency measurements (resolution %dusec)\n" , |
1795 | enable ? "Enabling" : "Disabling" , val); |
1796 | |
1797 | if (enable) { |
1798 | size_t sz = sizeof(u64) * WIL_NUM_LATENCY_BINS; |
1799 | |
1800 | wil->tx_latency_res = val; |
1801 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1802 | struct wil_sta_info *sta = &wil->sta[i]; |
1803 | |
1804 | kfree(objp: sta->tx_latency_bins); |
1805 | sta->tx_latency_bins = kzalloc(size: sz, GFP_KERNEL); |
1806 | if (!sta->tx_latency_bins) |
1807 | return -ENOMEM; |
1808 | sta->stats.tx_latency_min_us = U32_MAX; |
1809 | sta->stats.tx_latency_max_us = 0; |
1810 | sta->stats.tx_latency_total_us = 0; |
1811 | } |
1812 | } |
1813 | wil->tx_latency = enable; |
1814 | |
1815 | return len; |
1816 | } |
1817 | |
1818 | static const struct file_operations fops_tx_latency = { |
1819 | .open = wil_tx_latency_seq_open, |
1820 | .release = single_release, |
1821 | .read = seq_read, |
1822 | .write = wil_tx_latency_write, |
1823 | .llseek = seq_lseek, |
1824 | }; |
1825 | |
1826 | static void wil_link_stats_print_basic(struct wil6210_vif *vif, |
1827 | struct seq_file *s, |
1828 | struct wmi_link_stats_basic *basic) |
1829 | { |
1830 | char per[5] = "?" ; |
1831 | |
1832 | if (basic->per_average != 0xff) |
1833 | snprintf(buf: per, size: sizeof(per), fmt: "%d%%" , basic->per_average); |
1834 | |
1835 | seq_printf(m: s, fmt: "CID %d {\n" |
1836 | "\tTxMCS %s TxTpt %d\n" |
1837 | "\tGoodput(rx:tx) %d:%d\n" |
1838 | "\tRxBcastFrames %d\n" |
1839 | "\tRSSI %d SQI %d SNR %d PER %s\n" |
1840 | "\tRx RFC %d Ant num %d\n" |
1841 | "\tSectors(rx:tx) my %d:%d peer %d:%d\n" |
1842 | "}\n" , |
1843 | basic->cid, |
1844 | WIL_EXTENDED_MCS_CHECK(basic->bf_mcs), |
1845 | le32_to_cpu(basic->tx_tpt), |
1846 | le32_to_cpu(basic->rx_goodput), |
1847 | le32_to_cpu(basic->tx_goodput), |
1848 | le32_to_cpu(basic->rx_bcast_frames), |
1849 | basic->rssi, basic->sqi, basic->snr, per, |
1850 | basic->selected_rfc, basic->rx_effective_ant_num, |
1851 | basic->my_rx_sector, basic->my_tx_sector, |
1852 | basic->other_rx_sector, basic->other_tx_sector); |
1853 | } |
1854 | |
1855 | static void wil_link_stats_print_global(struct wil6210_priv *wil, |
1856 | struct seq_file *s, |
1857 | struct wmi_link_stats_global *global) |
1858 | { |
1859 | seq_printf(m: s, fmt: "Frames(rx:tx) %d:%d\n" |
1860 | "BA Frames(rx:tx) %d:%d\n" |
1861 | "Beacons %d\n" |
1862 | "Rx Errors (MIC:CRC) %d:%d\n" |
1863 | "Tx Errors (no ack) %d\n" , |
1864 | le32_to_cpu(global->rx_frames), |
1865 | le32_to_cpu(global->tx_frames), |
1866 | le32_to_cpu(global->rx_ba_frames), |
1867 | le32_to_cpu(global->tx_ba_frames), |
1868 | le32_to_cpu(global->tx_beacons), |
1869 | le32_to_cpu(global->rx_mic_errors), |
1870 | le32_to_cpu(global->rx_crc_errors), |
1871 | le32_to_cpu(global->tx_fail_no_ack)); |
1872 | } |
1873 | |
1874 | static void wil_link_stats_debugfs_show_vif(struct wil6210_vif *vif, |
1875 | struct seq_file *s) |
1876 | { |
1877 | struct wil6210_priv *wil = vif_to_wil(vif); |
1878 | struct wmi_link_stats_basic *stats; |
1879 | int i; |
1880 | |
1881 | if (!vif->fw_stats_ready) { |
1882 | seq_puts(m: s, s: "no statistics\n" ); |
1883 | return; |
1884 | } |
1885 | |
1886 | seq_printf(m: s, fmt: "TSF %lld\n" , vif->fw_stats_tsf); |
1887 | for (i = 0; i < wil->max_assoc_sta; i++) { |
1888 | if (wil->sta[i].status == wil_sta_unused) |
1889 | continue; |
1890 | if (wil->sta[i].mid != vif->mid) |
1891 | continue; |
1892 | |
1893 | stats = &wil->sta[i].fw_stats_basic; |
1894 | wil_link_stats_print_basic(vif, s, basic: stats); |
1895 | } |
1896 | } |
1897 | |
1898 | static int wil_link_stats_debugfs_show(struct seq_file *s, void *data) |
1899 | { |
1900 | struct wil6210_priv *wil = s->private; |
1901 | struct wil6210_vif *vif; |
1902 | int i, rc; |
1903 | |
1904 | rc = mutex_lock_interruptible(&wil->vif_mutex); |
1905 | if (rc) |
1906 | return rc; |
1907 | |
1908 | /* iterate over all MIDs and show per-cid statistics. Then show the |
1909 | * global statistics |
1910 | */ |
1911 | for (i = 0; i < GET_MAX_VIFS(wil); i++) { |
1912 | vif = wil->vifs[i]; |
1913 | |
1914 | seq_printf(m: s, fmt: "MID %d " , i); |
1915 | if (!vif) { |
1916 | seq_puts(m: s, s: "unused\n" ); |
1917 | continue; |
1918 | } |
1919 | |
1920 | wil_link_stats_debugfs_show_vif(vif, s); |
1921 | } |
1922 | |
1923 | mutex_unlock(lock: &wil->vif_mutex); |
1924 | |
1925 | return 0; |
1926 | } |
1927 | |
1928 | static int wil_link_stats_seq_open(struct inode *inode, struct file *file) |
1929 | { |
1930 | return single_open(file, wil_link_stats_debugfs_show, inode->i_private); |
1931 | } |
1932 | |
1933 | static ssize_t wil_link_stats_write(struct file *file, const char __user *buf, |
1934 | size_t len, loff_t *ppos) |
1935 | { |
1936 | struct seq_file *s = file->private_data; |
1937 | struct wil6210_priv *wil = s->private; |
1938 | int cid, interval, rc, i; |
1939 | struct wil6210_vif *vif; |
1940 | char *kbuf = kmalloc(size: len + 1, GFP_KERNEL); |
1941 | |
1942 | if (!kbuf) |
1943 | return -ENOMEM; |
1944 | |
1945 | rc = simple_write_to_buffer(to: kbuf, available: len, ppos, from: buf, count: len); |
1946 | if (rc != len) { |
1947 | kfree(objp: kbuf); |
1948 | return rc >= 0 ? -EIO : rc; |
1949 | } |
1950 | |
1951 | kbuf[len] = '\0'; |
1952 | /* specify cid (use -1 for all cids) and snapshot interval in ms */ |
1953 | rc = sscanf(kbuf, "%d %d" , &cid, &interval); |
1954 | kfree(objp: kbuf); |
1955 | if (rc < 0) |
1956 | return rc; |
1957 | if (rc < 2 || interval < 0) |
1958 | return -EINVAL; |
1959 | |
1960 | wil_info(wil, "request link statistics, cid %d interval %d\n" , |
1961 | cid, interval); |
1962 | |
1963 | rc = mutex_lock_interruptible(&wil->vif_mutex); |
1964 | if (rc) |
1965 | return rc; |
1966 | |
1967 | for (i = 0; i < GET_MAX_VIFS(wil); i++) { |
1968 | vif = wil->vifs[i]; |
1969 | if (!vif) |
1970 | continue; |
1971 | |
1972 | rc = wmi_link_stats_cfg(vif, type: WMI_LINK_STATS_TYPE_BASIC, |
1973 | cid: (cid == -1 ? 0xff : cid), interval); |
1974 | if (rc) |
1975 | wil_err(wil, "link statistics failed for mid %d\n" , i); |
1976 | } |
1977 | mutex_unlock(lock: &wil->vif_mutex); |
1978 | |
1979 | return len; |
1980 | } |
1981 | |
1982 | static const struct file_operations fops_link_stats = { |
1983 | .open = wil_link_stats_seq_open, |
1984 | .release = single_release, |
1985 | .read = seq_read, |
1986 | .write = wil_link_stats_write, |
1987 | .llseek = seq_lseek, |
1988 | }; |
1989 | |
1990 | static int |
1991 | wil_link_stats_global_debugfs_show(struct seq_file *s, void *data) |
1992 | { |
1993 | struct wil6210_priv *wil = s->private; |
1994 | |
1995 | if (!wil->fw_stats_global.ready) |
1996 | return 0; |
1997 | |
1998 | seq_printf(m: s, fmt: "TSF %lld\n" , wil->fw_stats_global.tsf); |
1999 | wil_link_stats_print_global(wil, s, global: &wil->fw_stats_global.stats); |
2000 | |
2001 | return 0; |
2002 | } |
2003 | |
2004 | static int |
2005 | wil_link_stats_global_seq_open(struct inode *inode, struct file *file) |
2006 | { |
2007 | return single_open(file, wil_link_stats_global_debugfs_show, |
2008 | inode->i_private); |
2009 | } |
2010 | |
2011 | static ssize_t |
2012 | wil_link_stats_global_write(struct file *file, const char __user *buf, |
2013 | size_t len, loff_t *ppos) |
2014 | { |
2015 | struct seq_file *s = file->private_data; |
2016 | struct wil6210_priv *wil = s->private; |
2017 | int interval, rc; |
2018 | struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev); |
2019 | |
2020 | /* specify snapshot interval in ms */ |
2021 | rc = kstrtoint_from_user(s: buf, count: len, base: 0, res: &interval); |
2022 | if (rc || interval < 0) { |
2023 | wil_err(wil, "Invalid argument\n" ); |
2024 | return -EINVAL; |
2025 | } |
2026 | |
2027 | wil_info(wil, "request global link stats, interval %d\n" , interval); |
2028 | |
2029 | rc = wmi_link_stats_cfg(vif, type: WMI_LINK_STATS_TYPE_GLOBAL, cid: 0, interval); |
2030 | if (rc) |
2031 | wil_err(wil, "global link stats failed %d\n" , rc); |
2032 | |
2033 | return rc ? rc : len; |
2034 | } |
2035 | |
2036 | static const struct file_operations fops_link_stats_global = { |
2037 | .open = wil_link_stats_global_seq_open, |
2038 | .release = single_release, |
2039 | .read = seq_read, |
2040 | .write = wil_link_stats_global_write, |
2041 | .llseek = seq_lseek, |
2042 | }; |
2043 | |
2044 | static ssize_t wil_read_file_led_cfg(struct file *file, char __user *user_buf, |
2045 | size_t count, loff_t *ppos) |
2046 | { |
2047 | char buf[80]; |
2048 | int n; |
2049 | |
2050 | n = snprintf(buf, size: sizeof(buf), |
2051 | fmt: "led_id is set to %d, echo 1 to enable, 0 to disable\n" , |
2052 | led_id); |
2053 | |
2054 | n = min_t(int, n, sizeof(buf)); |
2055 | |
2056 | return simple_read_from_buffer(to: user_buf, count, ppos, |
2057 | from: buf, available: n); |
2058 | } |
2059 | |
2060 | static ssize_t wil_write_file_led_cfg(struct file *file, |
2061 | const char __user *buf_, |
2062 | size_t count, loff_t *ppos) |
2063 | { |
2064 | struct wil6210_priv *wil = file->private_data; |
2065 | int val; |
2066 | int rc; |
2067 | |
2068 | rc = kstrtoint_from_user(s: buf_, count, base: 0, res: &val); |
2069 | if (rc) { |
2070 | wil_err(wil, "Invalid argument\n" ); |
2071 | return rc; |
2072 | } |
2073 | |
2074 | wil_info(wil, "%s led %d\n" , val ? "Enabling" : "Disabling" , led_id); |
2075 | rc = wmi_led_cfg(wil, enable: val); |
2076 | if (rc) { |
2077 | wil_info(wil, "%s led %d failed\n" , |
2078 | val ? "Enabling" : "Disabling" , led_id); |
2079 | return rc; |
2080 | } |
2081 | |
2082 | return count; |
2083 | } |
2084 | |
2085 | static const struct file_operations fops_led_cfg = { |
2086 | .read = wil_read_file_led_cfg, |
2087 | .write = wil_write_file_led_cfg, |
2088 | .open = simple_open, |
2089 | }; |
2090 | |
2091 | /* led_blink_time, write: |
2092 | * "<blink_on_slow> <blink_off_slow> <blink_on_med> <blink_off_med> <blink_on_fast> <blink_off_fast> |
2093 | */ |
2094 | static ssize_t wil_write_led_blink_time(struct file *file, |
2095 | const char __user *buf, |
2096 | size_t len, loff_t *ppos) |
2097 | { |
2098 | int rc; |
2099 | char *kbuf = kmalloc(size: len + 1, GFP_KERNEL); |
2100 | |
2101 | if (!kbuf) |
2102 | return -ENOMEM; |
2103 | |
2104 | rc = simple_write_to_buffer(to: kbuf, available: len, ppos, from: buf, count: len); |
2105 | if (rc != len) { |
2106 | kfree(objp: kbuf); |
2107 | return rc >= 0 ? -EIO : rc; |
2108 | } |
2109 | |
2110 | kbuf[len] = '\0'; |
2111 | rc = sscanf(kbuf, "%d %d %d %d %d %d" , |
2112 | &led_blink_time[WIL_LED_TIME_SLOW].on_ms, |
2113 | &led_blink_time[WIL_LED_TIME_SLOW].off_ms, |
2114 | &led_blink_time[WIL_LED_TIME_MED].on_ms, |
2115 | &led_blink_time[WIL_LED_TIME_MED].off_ms, |
2116 | &led_blink_time[WIL_LED_TIME_FAST].on_ms, |
2117 | &led_blink_time[WIL_LED_TIME_FAST].off_ms); |
2118 | kfree(objp: kbuf); |
2119 | |
2120 | if (rc < 0) |
2121 | return rc; |
2122 | if (rc < 6) |
2123 | return -EINVAL; |
2124 | |
2125 | return len; |
2126 | } |
2127 | |
2128 | static ssize_t wil_read_led_blink_time(struct file *file, char __user *user_buf, |
2129 | size_t count, loff_t *ppos) |
2130 | { |
2131 | static char text[400]; |
2132 | |
2133 | snprintf(buf: text, size: sizeof(text), |
2134 | fmt: "To set led blink on/off time variables write:\n" |
2135 | "<blink_on_slow> <blink_off_slow> <blink_on_med> " |
2136 | "<blink_off_med> <blink_on_fast> <blink_off_fast>\n" |
2137 | "The current values are:\n" |
2138 | "%d %d %d %d %d %d\n" , |
2139 | led_blink_time[WIL_LED_TIME_SLOW].on_ms, |
2140 | led_blink_time[WIL_LED_TIME_SLOW].off_ms, |
2141 | led_blink_time[WIL_LED_TIME_MED].on_ms, |
2142 | led_blink_time[WIL_LED_TIME_MED].off_ms, |
2143 | led_blink_time[WIL_LED_TIME_FAST].on_ms, |
2144 | led_blink_time[WIL_LED_TIME_FAST].off_ms); |
2145 | |
2146 | return simple_read_from_buffer(to: user_buf, count, ppos, from: text, |
2147 | available: sizeof(text)); |
2148 | } |
2149 | |
2150 | static const struct file_operations fops_led_blink_time = { |
2151 | .read = wil_read_led_blink_time, |
2152 | .write = wil_write_led_blink_time, |
2153 | .open = simple_open, |
2154 | }; |
2155 | |
2156 | /*---------FW capabilities------------*/ |
2157 | static int fw_capabilities_show(struct seq_file *s, void *data) |
2158 | { |
2159 | struct wil6210_priv *wil = s->private; |
2160 | |
2161 | seq_printf(m: s, fmt: "fw_capabilities : %*pb\n" , WMI_FW_CAPABILITY_MAX, |
2162 | wil->fw_capabilities); |
2163 | |
2164 | return 0; |
2165 | } |
2166 | DEFINE_SHOW_ATTRIBUTE(fw_capabilities); |
2167 | |
2168 | /*---------FW version------------*/ |
2169 | static int fw_version_show(struct seq_file *s, void *data) |
2170 | { |
2171 | struct wil6210_priv *wil = s->private; |
2172 | |
2173 | if (wil->fw_version[0]) |
2174 | seq_printf(m: s, fmt: "%s\n" , wil->fw_version); |
2175 | else |
2176 | seq_puts(m: s, s: "N/A\n" ); |
2177 | |
2178 | return 0; |
2179 | } |
2180 | DEFINE_SHOW_ATTRIBUTE(fw_version); |
2181 | |
2182 | /*---------suspend_stats---------*/ |
2183 | static ssize_t wil_write_suspend_stats(struct file *file, |
2184 | const char __user *buf, |
2185 | size_t len, loff_t *ppos) |
2186 | { |
2187 | struct wil6210_priv *wil = file->private_data; |
2188 | |
2189 | memset(&wil->suspend_stats, 0, sizeof(wil->suspend_stats)); |
2190 | |
2191 | return len; |
2192 | } |
2193 | |
2194 | static ssize_t wil_read_suspend_stats(struct file *file, |
2195 | char __user *user_buf, |
2196 | size_t count, loff_t *ppos) |
2197 | { |
2198 | struct wil6210_priv *wil = file->private_data; |
2199 | char *text; |
2200 | int n, ret, text_size = 500; |
2201 | |
2202 | text = kmalloc(size: text_size, GFP_KERNEL); |
2203 | if (!text) |
2204 | return -ENOMEM; |
2205 | |
2206 | n = snprintf(buf: text, size: text_size, |
2207 | fmt: "Radio on suspend statistics:\n" |
2208 | "successful suspends:%ld failed suspends:%ld\n" |
2209 | "successful resumes:%ld failed resumes:%ld\n" |
2210 | "rejected by device:%ld\n" |
2211 | "Radio off suspend statistics:\n" |
2212 | "successful suspends:%ld failed suspends:%ld\n" |
2213 | "successful resumes:%ld failed resumes:%ld\n" |
2214 | "General statistics:\n" |
2215 | "rejected by host:%ld\n" , |
2216 | wil->suspend_stats.r_on.successful_suspends, |
2217 | wil->suspend_stats.r_on.failed_suspends, |
2218 | wil->suspend_stats.r_on.successful_resumes, |
2219 | wil->suspend_stats.r_on.failed_resumes, |
2220 | wil->suspend_stats.rejected_by_device, |
2221 | wil->suspend_stats.r_off.successful_suspends, |
2222 | wil->suspend_stats.r_off.failed_suspends, |
2223 | wil->suspend_stats.r_off.successful_resumes, |
2224 | wil->suspend_stats.r_off.failed_resumes, |
2225 | wil->suspend_stats.rejected_by_host); |
2226 | |
2227 | n = min_t(int, n, text_size); |
2228 | |
2229 | ret = simple_read_from_buffer(to: user_buf, count, ppos, from: text, available: n); |
2230 | |
2231 | kfree(objp: text); |
2232 | |
2233 | return ret; |
2234 | } |
2235 | |
2236 | static const struct file_operations fops_suspend_stats = { |
2237 | .read = wil_read_suspend_stats, |
2238 | .write = wil_write_suspend_stats, |
2239 | .open = simple_open, |
2240 | }; |
2241 | |
2242 | /*---------compressed_rx_status---------*/ |
2243 | static ssize_t wil_compressed_rx_status_write(struct file *file, |
2244 | const char __user *buf, |
2245 | size_t len, loff_t *ppos) |
2246 | { |
2247 | struct seq_file *s = file->private_data; |
2248 | struct wil6210_priv *wil = s->private; |
2249 | int compressed_rx_status; |
2250 | int rc; |
2251 | |
2252 | rc = kstrtoint_from_user(s: buf, count: len, base: 0, res: &compressed_rx_status); |
2253 | if (rc) { |
2254 | wil_err(wil, "Invalid argument\n" ); |
2255 | return rc; |
2256 | } |
2257 | |
2258 | if (wil_has_active_ifaces(wil, up: true, ok: false)) { |
2259 | wil_err(wil, "cannot change edma config after iface is up\n" ); |
2260 | return -EPERM; |
2261 | } |
2262 | |
2263 | wil_info(wil, "%sable compressed_rx_status\n" , |
2264 | compressed_rx_status ? "En" : "Dis" ); |
2265 | |
2266 | wil->use_compressed_rx_status = compressed_rx_status; |
2267 | |
2268 | return len; |
2269 | } |
2270 | |
2271 | static int |
2272 | wil_compressed_rx_status_show(struct seq_file *s, void *data) |
2273 | { |
2274 | struct wil6210_priv *wil = s->private; |
2275 | |
2276 | seq_printf(m: s, fmt: "%d\n" , wil->use_compressed_rx_status); |
2277 | |
2278 | return 0; |
2279 | } |
2280 | |
2281 | static int |
2282 | wil_compressed_rx_status_seq_open(struct inode *inode, struct file *file) |
2283 | { |
2284 | return single_open(file, wil_compressed_rx_status_show, |
2285 | inode->i_private); |
2286 | } |
2287 | |
2288 | static const struct file_operations fops_compressed_rx_status = { |
2289 | .open = wil_compressed_rx_status_seq_open, |
2290 | .release = single_release, |
2291 | .read = seq_read, |
2292 | .write = wil_compressed_rx_status_write, |
2293 | .llseek = seq_lseek, |
2294 | }; |
2295 | |
2296 | /*----------------*/ |
2297 | static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil, |
2298 | struct dentry *dbg) |
2299 | { |
2300 | int i; |
2301 | char name[32]; |
2302 | |
2303 | for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { |
2304 | struct wil_blob_wrapper *wil_blob = &wil->blobs[i]; |
2305 | struct debugfs_blob_wrapper *blob = &wil_blob->blob; |
2306 | const struct fw_map *map = &fw_mapping[i]; |
2307 | |
2308 | if (!map->name) |
2309 | continue; |
2310 | |
2311 | wil_blob->wil = wil; |
2312 | blob->data = (void * __force)wil->csr + HOSTADDR(map->host); |
2313 | blob->size = map->to - map->from; |
2314 | snprintf(buf: name, size: sizeof(name), fmt: "blob_%s" , map->name); |
2315 | wil_debugfs_create_ioblob(name, mode: 0444, parent: dbg, wil_blob); |
2316 | } |
2317 | } |
2318 | |
2319 | /* misc files */ |
2320 | static const struct { |
2321 | const char *name; |
2322 | umode_t mode; |
2323 | const struct file_operations *fops; |
2324 | } dbg_files[] = { |
2325 | {"mbox" , 0444, &mbox_fops}, |
2326 | {"rings" , 0444, &ring_fops}, |
2327 | {"stations" , 0444, &sta_fops}, |
2328 | {"mids" , 0444, &mids_fops}, |
2329 | {"desc" , 0444, &txdesc_fops}, |
2330 | {"bf" , 0444, &bf_fops}, |
2331 | {"mem_val" , 0644, &memread_fops}, |
2332 | {"rxon" , 0244, &fops_rxon}, |
2333 | {"tx_mgmt" , 0244, &fops_txmgmt}, |
2334 | {"wmi_send" , 0244, &fops_wmi}, |
2335 | {"back" , 0644, &fops_back}, |
2336 | {"pmccfg" , 0644, &fops_pmccfg}, |
2337 | {"pmcdata" , 0444, &fops_pmcdata}, |
2338 | {"pmcring" , 0444, &fops_pmcring}, |
2339 | {"temp" , 0444, &temp_fops}, |
2340 | {"link" , 0444, &link_fops}, |
2341 | {"info" , 0444, &info_fops}, |
2342 | {"recovery" , 0644, &fops_recovery}, |
2343 | {"led_cfg" , 0644, &fops_led_cfg}, |
2344 | {"led_blink_time" , 0644, &fops_led_blink_time}, |
2345 | {"fw_capabilities" , 0444, &fw_capabilities_fops}, |
2346 | {"fw_version" , 0444, &fw_version_fops}, |
2347 | {"suspend_stats" , 0644, &fops_suspend_stats}, |
2348 | {"compressed_rx_status" , 0644, &fops_compressed_rx_status}, |
2349 | {"srings" , 0444, &srings_fops}, |
2350 | {"status_msg" , 0444, &status_msg_fops}, |
2351 | {"rx_buff_mgmt" , 0444, &rx_buff_mgmt_fops}, |
2352 | {"tx_latency" , 0644, &fops_tx_latency}, |
2353 | {"link_stats" , 0644, &fops_link_stats}, |
2354 | {"link_stats_global" , 0644, &fops_link_stats_global}, |
2355 | {"rbufcap" , 0244, &fops_rbufcap}, |
2356 | }; |
2357 | |
2358 | static void wil6210_debugfs_init_files(struct wil6210_priv *wil, |
2359 | struct dentry *dbg) |
2360 | { |
2361 | int i; |
2362 | |
2363 | for (i = 0; i < ARRAY_SIZE(dbg_files); i++) |
2364 | debugfs_create_file(name: dbg_files[i].name, mode: dbg_files[i].mode, parent: dbg, |
2365 | data: wil, fops: dbg_files[i].fops); |
2366 | } |
2367 | |
2368 | /* interrupt control blocks */ |
2369 | static const struct { |
2370 | const char *name; |
2371 | u32 icr_off; |
2372 | } dbg_icr[] = { |
2373 | {"USER_ICR" , HOSTADDR(RGF_USER_USER_ICR)}, |
2374 | {"DMA_EP_TX_ICR" , HOSTADDR(RGF_DMA_EP_TX_ICR)}, |
2375 | {"DMA_EP_RX_ICR" , HOSTADDR(RGF_DMA_EP_RX_ICR)}, |
2376 | {"DMA_EP_MISC_ICR" , HOSTADDR(RGF_DMA_EP_MISC_ICR)}, |
2377 | }; |
2378 | |
2379 | static void wil6210_debugfs_init_isr(struct wil6210_priv *wil, |
2380 | struct dentry *dbg) |
2381 | { |
2382 | int i; |
2383 | |
2384 | for (i = 0; i < ARRAY_SIZE(dbg_icr); i++) |
2385 | wil6210_debugfs_create_ISR(wil, name: dbg_icr[i].name, parent: dbg, |
2386 | off: dbg_icr[i].icr_off); |
2387 | } |
2388 | |
2389 | #define WIL_FIELD(name, mode, type) { __stringify(name), mode, \ |
2390 | offsetof(struct wil6210_priv, name), type} |
2391 | |
2392 | /* fields in struct wil6210_priv */ |
2393 | static const struct dbg_off dbg_wil_off[] = { |
2394 | WIL_FIELD(status[0], 0644, doff_ulong), |
2395 | WIL_FIELD(hw_version, 0444, doff_x32), |
2396 | WIL_FIELD(recovery_count, 0444, doff_u32), |
2397 | WIL_FIELD(discovery_mode, 0644, doff_u8), |
2398 | WIL_FIELD(chip_revision, 0444, doff_u8), |
2399 | WIL_FIELD(abft_len, 0644, doff_u8), |
2400 | WIL_FIELD(wakeup_trigger, 0644, doff_u8), |
2401 | WIL_FIELD(ring_idle_trsh, 0644, doff_u32), |
2402 | WIL_FIELD(num_rx_status_rings, 0644, doff_u8), |
2403 | WIL_FIELD(rx_status_ring_order, 0644, doff_u32), |
2404 | WIL_FIELD(tx_status_ring_order, 0644, doff_u32), |
2405 | WIL_FIELD(rx_buff_id_count, 0644, doff_u32), |
2406 | WIL_FIELD(amsdu_en, 0644, doff_u8), |
2407 | {}, |
2408 | }; |
2409 | |
2410 | static const struct dbg_off dbg_wil_regs[] = { |
2411 | {"RGF_MAC_MTRL_COUNTER_0" , 0444, HOSTADDR(RGF_MAC_MTRL_COUNTER_0), |
2412 | doff_io32}, |
2413 | {"RGF_USER_USAGE_1" , 0444, HOSTADDR(RGF_USER_USAGE_1), doff_io32}, |
2414 | {"RGF_USER_USAGE_2" , 0444, HOSTADDR(RGF_USER_USAGE_2), doff_io32}, |
2415 | {}, |
2416 | }; |
2417 | |
2418 | /* static parameters */ |
2419 | static const struct dbg_off dbg_statics[] = { |
2420 | {"desc_index" , 0644, (ulong)&dbg_txdesc_index, doff_u32}, |
2421 | {"ring_index" , 0644, (ulong)&dbg_ring_index, doff_u32}, |
2422 | {"mem_addr" , 0644, (ulong)&mem_addr, doff_u32}, |
2423 | {"led_polarity" , 0644, (ulong)&led_polarity, doff_u8}, |
2424 | {"status_index" , 0644, (ulong)&dbg_status_msg_index, doff_u32}, |
2425 | {"sring_index" , 0644, (ulong)&dbg_sring_index, doff_u32}, |
2426 | {"drop_if_ring_full" , 0644, (ulong)&drop_if_ring_full, doff_u8}, |
2427 | {}, |
2428 | }; |
2429 | |
2430 | static const int dbg_off_count = 4 * (ARRAY_SIZE(isr_off) - 1) + |
2431 | ARRAY_SIZE(dbg_wil_regs) - 1 + |
2432 | ARRAY_SIZE(pseudo_isr_off) - 1 + |
2433 | ARRAY_SIZE(lgc_itr_cnt_off) - 1 + |
2434 | ARRAY_SIZE(tx_itr_cnt_off) - 1 + |
2435 | ARRAY_SIZE(rx_itr_cnt_off) - 1; |
2436 | |
2437 | int wil6210_debugfs_init(struct wil6210_priv *wil) |
2438 | { |
2439 | struct dentry *dbg = wil->debug = debugfs_create_dir(WIL_NAME, |
2440 | wil_to_wiphy(wil)->debugfsdir); |
2441 | if (IS_ERR_OR_NULL(ptr: dbg)) |
2442 | return -ENODEV; |
2443 | |
2444 | wil->dbg_data.data_arr = kcalloc(n: dbg_off_count, |
2445 | size: sizeof(struct wil_debugfs_iomem_data), |
2446 | GFP_KERNEL); |
2447 | if (!wil->dbg_data.data_arr) { |
2448 | debugfs_remove_recursive(dentry: dbg); |
2449 | wil->debug = NULL; |
2450 | return -ENOMEM; |
2451 | } |
2452 | |
2453 | wil->dbg_data.iomem_data_count = 0; |
2454 | |
2455 | wil_pmc_init(wil); |
2456 | |
2457 | wil6210_debugfs_init_files(wil, dbg); |
2458 | wil6210_debugfs_init_isr(wil, dbg); |
2459 | wil6210_debugfs_init_blobs(wil, dbg); |
2460 | wil6210_debugfs_init_offset(wil, dbg, base: wil, tbl: dbg_wil_off); |
2461 | wil6210_debugfs_init_offset(wil, dbg, base: (void * __force)wil->csr, |
2462 | tbl: dbg_wil_regs); |
2463 | wil6210_debugfs_init_offset(wil, dbg, NULL, tbl: dbg_statics); |
2464 | |
2465 | wil6210_debugfs_create_pseudo_ISR(wil, parent: dbg); |
2466 | |
2467 | wil6210_debugfs_create_ITR_CNT(wil, parent: dbg); |
2468 | |
2469 | return 0; |
2470 | } |
2471 | |
2472 | void wil6210_debugfs_remove(struct wil6210_priv *wil) |
2473 | { |
2474 | int i; |
2475 | |
2476 | debugfs_remove_recursive(dentry: wil->debug); |
2477 | wil->debug = NULL; |
2478 | |
2479 | kfree(objp: wil->dbg_data.data_arr); |
2480 | for (i = 0; i < wil->max_assoc_sta; i++) |
2481 | kfree(objp: wil->sta[i].tx_latency_bins); |
2482 | |
2483 | /* free pmc memory without sending command to fw, as it will |
2484 | * be reset on the way down anyway |
2485 | */ |
2486 | wil_pmc_free(wil, send_pmc_cmd: false); |
2487 | } |
2488 | |