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