1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 
4   Broadcom B43legacy wireless driver
5 
6   debugfs driver debugging code
7 
8   Copyright (c) 2005-2007 Michael Buesch <m@bues.ch>
9 
10 
11 */
12 
13 #include <linux/fs.h>
14 #include <linux/debugfs.h>
15 #include <linux/slab.h>
16 #include <linux/netdevice.h>
17 #include <linux/pci.h>
18 #include <linux/mutex.h>
19 
20 #include "b43legacy.h"
21 #include "main.h"
22 #include "debugfs.h"
23 #include "dma.h"
24 #include "pio.h"
25 #include "xmit.h"
26 
27 
28 /* The root directory. */
29 static struct dentry *rootdir;
30 
31 struct b43legacy_debugfs_fops {
32 	ssize_t (*read)(struct b43legacy_wldev *dev, char *buf, size_t bufsize);
33 	int (*write)(struct b43legacy_wldev *dev, const char *buf, size_t count);
34 	struct file_operations fops;
35 	/* Offset of struct b43legacy_dfs_file in struct b43legacy_dfsentry */
36 	size_t file_struct_offset;
37 	/* Take wl->irq_lock before calling read/write? */
38 	bool take_irqlock;
39 };
40 
41 static inline
42 struct b43legacy_dfs_file * fops_to_dfs_file(struct b43legacy_wldev *dev,
43 				       const struct b43legacy_debugfs_fops *dfops)
44 {
45 	void *p;
46 
47 	p = dev->dfsentry;
48 	p += dfops->file_struct_offset;
49 
50 	return p;
51 }
52 
53 
54 #define fappend(fmt, x...)	\
55 	do {							\
56 		if (bufsize - count)				\
57 			count += scnprintf(buf + count,		\
58 					  bufsize - count,	\
59 					  fmt , ##x);		\
60 		else						\
61 			printk(KERN_ERR "b43legacy: fappend overflow\n"); \
62 	} while (0)
63 
64 
65 /* wl->irq_lock is locked */
66 static ssize_t tsf_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
67 {
68 	ssize_t count = 0;
69 	u64 tsf;
70 
71 	b43legacy_tsf_read(dev, &tsf);
72 	fappend("0x%08x%08x\n",
73 		(unsigned int)((tsf & 0xFFFFFFFF00000000ULL) >> 32),
74 		(unsigned int)(tsf & 0xFFFFFFFFULL));
75 
76 	return count;
77 }
78 
79 /* wl->irq_lock is locked */
80 static int tsf_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count)
81 {
82 	u64 tsf;
83 
84 	if (sscanf(buf, "%llu", (unsigned long long *)(&tsf)) != 1)
85 		return -EINVAL;
86 	b43legacy_tsf_write(dev, tsf);
87 
88 	return 0;
89 }
90 
91 /* wl->irq_lock is locked */
92 static ssize_t ucode_regs_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
93 {
94 	ssize_t count = 0;
95 	int i;
96 
97 	for (i = 0; i < 64; i++) {
98 		fappend("r%d = 0x%04x\n", i,
99 			b43legacy_shm_read16(dev, B43legacy_SHM_WIRELESS, i));
100 	}
101 
102 	return count;
103 }
104 
105 /* wl->irq_lock is locked */
106 static ssize_t shm_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
107 {
108 	ssize_t count = 0;
109 	int i;
110 	u16 tmp;
111 	__le16 *le16buf = (__le16 *)buf;
112 
113 	for (i = 0; i < 0x1000; i++) {
114 		if (bufsize < sizeof(tmp))
115 			break;
116 		tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 2 * i);
117 		le16buf[i] = cpu_to_le16(tmp);
118 		count += sizeof(tmp);
119 		bufsize -= sizeof(tmp);
120 	}
121 
122 	return count;
123 }
124 
125 static ssize_t txstat_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
126 {
127 	struct b43legacy_txstatus_log *log = &dev->dfsentry->txstatlog;
128 	ssize_t count = 0;
129 	unsigned long flags;
130 	int i, idx;
131 	struct b43legacy_txstatus *stat;
132 
133 	spin_lock_irqsave(&log->lock, flags);
134 	if (log->end < 0) {
135 		fappend("Nothing transmitted, yet\n");
136 		goto out_unlock;
137 	}
138 	fappend("b43legacy TX status reports:\n\n"
139 		"index | cookie | seq | phy_stat | frame_count | "
140 		"rts_count | supp_reason | pm_indicated | "
141 		"intermediate | for_ampdu | acked\n" "---\n");
142 	i = log->end + 1;
143 	idx = 0;
144 	while (1) {
145 		if (i == B43legacy_NR_LOGGED_TXSTATUS)
146 			i = 0;
147 		stat = &(log->log[i]);
148 		if (stat->cookie) {
149 			fappend("%03d | "
150 				"0x%04X | 0x%04X | 0x%02X | "
151 				"0x%X | 0x%X | "
152 				"%u | %u | "
153 				"%u | %u | %u\n",
154 				idx,
155 				stat->cookie, stat->seq, stat->phy_stat,
156 				stat->frame_count, stat->rts_count,
157 				stat->supp_reason, stat->pm_indicated,
158 				stat->intermediate, stat->for_ampdu,
159 				stat->acked);
160 			idx++;
161 		}
162 		if (i == log->end)
163 			break;
164 		i++;
165 	}
166 out_unlock:
167 	spin_unlock_irqrestore(&log->lock, flags);
168 
169 	return count;
170 }
171 
172 /* wl->irq_lock is locked */
173 static int restart_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count)
174 {
175 	int err = 0;
176 
177 	if (count > 0 && buf[0] == '1') {
178 		b43legacy_controller_restart(dev, "manually restarted");
179 	} else
180 		err = -EINVAL;
181 
182 	return err;
183 }
184 
185 #undef fappend
186 
187 static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
188 				size_t count, loff_t *ppos)
189 {
190 	struct b43legacy_wldev *dev;
191 	struct b43legacy_debugfs_fops *dfops;
192 	struct b43legacy_dfs_file *dfile;
193 	ssize_t ret;
194 	char *buf;
195 	const size_t bufsize = 1024 * 16; /* 16 KiB buffer */
196 	const size_t buforder = get_order(bufsize);
197 	int err = 0;
198 
199 	if (!count)
200 		return 0;
201 	dev = file->private_data;
202 	if (!dev)
203 		return -ENODEV;
204 
205 	mutex_lock(&dev->wl->mutex);
206 	if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) {
207 		err = -ENODEV;
208 		goto out_unlock;
209 	}
210 
211 	dfops = container_of(debugfs_real_fops(file),
212 			     struct b43legacy_debugfs_fops, fops);
213 	if (!dfops->read) {
214 		err = -ENOSYS;
215 		goto out_unlock;
216 	}
217 	dfile = fops_to_dfs_file(dev, dfops);
218 
219 	if (!dfile->buffer) {
220 		buf = (char *)__get_free_pages(GFP_KERNEL, buforder);
221 		if (!buf) {
222 			err = -ENOMEM;
223 			goto out_unlock;
224 		}
225 		memset(buf, 0, bufsize);
226 		if (dfops->take_irqlock) {
227 			spin_lock_irq(&dev->wl->irq_lock);
228 			ret = dfops->read(dev, buf, bufsize);
229 			spin_unlock_irq(&dev->wl->irq_lock);
230 		} else
231 			ret = dfops->read(dev, buf, bufsize);
232 		if (ret <= 0) {
233 			free_pages((unsigned long)buf, buforder);
234 			err = ret;
235 			goto out_unlock;
236 		}
237 		dfile->data_len = ret;
238 		dfile->buffer = buf;
239 	}
240 
241 	ret = simple_read_from_buffer(userbuf, count, ppos,
242 				      dfile->buffer,
243 				      dfile->data_len);
244 	if (*ppos >= dfile->data_len) {
245 		free_pages((unsigned long)dfile->buffer, buforder);
246 		dfile->buffer = NULL;
247 		dfile->data_len = 0;
248 	}
249 out_unlock:
250 	mutex_unlock(&dev->wl->mutex);
251 
252 	return err ? err : ret;
253 }
254 
255 static ssize_t b43legacy_debugfs_write(struct file *file,
256 				 const char __user *userbuf,
257 				 size_t count, loff_t *ppos)
258 {
259 	struct b43legacy_wldev *dev;
260 	struct b43legacy_debugfs_fops *dfops;
261 	char *buf;
262 	int err = 0;
263 
264 	if (!count)
265 		return 0;
266 	if (count > PAGE_SIZE)
267 		return -E2BIG;
268 	dev = file->private_data;
269 	if (!dev)
270 		return -ENODEV;
271 
272 	mutex_lock(&dev->wl->mutex);
273 	if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) {
274 		err = -ENODEV;
275 		goto out_unlock;
276 	}
277 
278 	dfops = container_of(debugfs_real_fops(file),
279 			     struct b43legacy_debugfs_fops, fops);
280 	if (!dfops->write) {
281 		err = -ENOSYS;
282 		goto out_unlock;
283 	}
284 
285 	buf = (char *)get_zeroed_page(GFP_KERNEL);
286 	if (!buf) {
287 		err = -ENOMEM;
288 		goto out_unlock;
289 	}
290 	if (copy_from_user(buf, userbuf, count)) {
291 		err = -EFAULT;
292 		goto out_freepage;
293 	}
294 	if (dfops->take_irqlock) {
295 		spin_lock_irq(&dev->wl->irq_lock);
296 		err = dfops->write(dev, buf, count);
297 		spin_unlock_irq(&dev->wl->irq_lock);
298 	} else
299 		err = dfops->write(dev, buf, count);
300 	if (err)
301 		goto out_freepage;
302 
303 out_freepage:
304 	free_page((unsigned long)buf);
305 out_unlock:
306 	mutex_unlock(&dev->wl->mutex);
307 
308 	return err ? err : count;
309 }
310 
311 
312 #define B43legacy_DEBUGFS_FOPS(name, _read, _write, _take_irqlock)	\
313 	static struct b43legacy_debugfs_fops fops_##name = {		\
314 		.read	= _read,				\
315 		.write	= _write,				\
316 		.fops	= {					\
317 			.open	= simple_open,				\
318 			.read	= b43legacy_debugfs_read,		\
319 			.write	= b43legacy_debugfs_write,		\
320 			.llseek = generic_file_llseek,			\
321 		},						\
322 		.file_struct_offset = offsetof(struct b43legacy_dfsentry, \
323 					       file_##name),	\
324 		.take_irqlock	= _take_irqlock,		\
325 	}
326 
327 B43legacy_DEBUGFS_FOPS(tsf, tsf_read_file, tsf_write_file, 1);
328 B43legacy_DEBUGFS_FOPS(ucode_regs, ucode_regs_read_file, NULL, 1);
329 B43legacy_DEBUGFS_FOPS(shm, shm_read_file, NULL, 1);
330 B43legacy_DEBUGFS_FOPS(txstat, txstat_read_file, NULL, 0);
331 B43legacy_DEBUGFS_FOPS(restart, NULL, restart_write_file, 1);
332 
333 
334 int b43legacy_debug(struct b43legacy_wldev *dev, enum b43legacy_dyndbg feature)
335 {
336 	return !!(dev->dfsentry && dev->dfsentry->dyn_debug[feature]);
337 }
338 
339 static void b43legacy_add_dynamic_debug(struct b43legacy_wldev *dev)
340 {
341 	struct b43legacy_dfsentry *e = dev->dfsentry;
342 
343 #define add_dyn_dbg(name, id, initstate) do {			\
344 	e->dyn_debug[id] = (initstate);				\
345 	debugfs_create_bool(name, 0600, e->subdir,		\
346 			    &(e->dyn_debug[id]));		\
347 	} while (0)
348 
349 	add_dyn_dbg("debug_xmitpower", B43legacy_DBG_XMITPOWER, false);
350 	add_dyn_dbg("debug_dmaoverflow", B43legacy_DBG_DMAOVERFLOW, false);
351 	add_dyn_dbg("debug_dmaverbose", B43legacy_DBG_DMAVERBOSE, false);
352 	add_dyn_dbg("debug_pwork_fast", B43legacy_DBG_PWORK_FAST, false);
353 	add_dyn_dbg("debug_pwork_stop", B43legacy_DBG_PWORK_STOP, false);
354 
355 #undef add_dyn_dbg
356 }
357 
358 void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev)
359 {
360 	struct b43legacy_dfsentry *e;
361 	struct b43legacy_txstatus_log *log;
362 	char devdir[16];
363 
364 	B43legacy_WARN_ON(!dev);
365 	e = kzalloc(sizeof(*e), GFP_KERNEL);
366 	if (!e) {
367 		b43legacyerr(dev->wl, "debugfs: add device OOM\n");
368 		return;
369 	}
370 	e->dev = dev;
371 	log = &e->txstatlog;
372 	log->log = kcalloc(B43legacy_NR_LOGGED_TXSTATUS,
373 			   sizeof(struct b43legacy_txstatus), GFP_KERNEL);
374 	if (!log->log) {
375 		b43legacyerr(dev->wl, "debugfs: add device txstatus OOM\n");
376 		kfree(e);
377 		return;
378 	}
379 	log->end = -1;
380 	spin_lock_init(&log->lock);
381 
382 	dev->dfsentry = e;
383 
384 	snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy));
385 	e->subdir = debugfs_create_dir(devdir, rootdir);
386 
387 #define ADD_FILE(name, mode)	\
388 	do {							\
389 		debugfs_create_file(__stringify(name), mode,	\
390 				    e->subdir, dev,		\
391 				    &fops_##name.fops);		\
392 	} while (0)
393 
394 
395 	ADD_FILE(tsf, 0600);
396 	ADD_FILE(ucode_regs, 0400);
397 	ADD_FILE(shm, 0400);
398 	ADD_FILE(txstat, 0400);
399 	ADD_FILE(restart, 0200);
400 
401 #undef ADD_FILE
402 
403 	b43legacy_add_dynamic_debug(dev);
404 }
405 
406 void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev)
407 {
408 	struct b43legacy_dfsentry *e;
409 
410 	if (!dev)
411 		return;
412 	e = dev->dfsentry;
413 	if (!e)
414 		return;
415 
416 	debugfs_remove(e->subdir);
417 	kfree(e->txstatlog.log);
418 	kfree(e);
419 }
420 
421 void b43legacy_debugfs_log_txstat(struct b43legacy_wldev *dev,
422 			    const struct b43legacy_txstatus *status)
423 {
424 	struct b43legacy_dfsentry *e = dev->dfsentry;
425 	struct b43legacy_txstatus_log *log;
426 	struct b43legacy_txstatus *cur;
427 	int i;
428 
429 	if (!e)
430 		return;
431 	log = &e->txstatlog;
432 	B43legacy_WARN_ON(!irqs_disabled());
433 	spin_lock(&log->lock);
434 	i = log->end + 1;
435 	if (i == B43legacy_NR_LOGGED_TXSTATUS)
436 		i = 0;
437 	log->end = i;
438 	cur = &(log->log[i]);
439 	memcpy(cur, status, sizeof(*cur));
440 	spin_unlock(&log->lock);
441 }
442 
443 void b43legacy_debugfs_init(void)
444 {
445 	rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL);
446 }
447 
448 void b43legacy_debugfs_exit(void)
449 {
450 	debugfs_remove(rootdir);
451 }
452