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 const size_t buforder = get_order(bufsize); 196 int err = 0; 197 198 if (!count) 199 return 0; 200 dev = file->private_data; 201 if (!dev) 202 return -ENODEV; 203 204 mutex_lock(&dev->wl->mutex); 205 if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) { 206 err = -ENODEV; 207 goto out_unlock; 208 } 209 210 dfops = debugfs_get_aux(file); 211 if (!dfops->read) { 212 err = -ENOSYS; 213 goto out_unlock; 214 } 215 dfile = fops_to_dfs_file(dev, dfops); 216 217 if (!dfile->buffer) { 218 buf = (char *)__get_free_pages(GFP_KERNEL, buforder); 219 if (!buf) { 220 err = -ENOMEM; 221 goto out_unlock; 222 } 223 memset(buf, 0, bufsize); 224 if (dfops->take_irqlock) { 225 spin_lock_irq(&dev->wl->irq_lock); 226 ret = dfops->read(dev, buf, bufsize); 227 spin_unlock_irq(&dev->wl->irq_lock); 228 } else 229 ret = dfops->read(dev, buf, bufsize); 230 if (ret <= 0) { 231 free_pages((unsigned long)buf, buforder); 232 err = ret; 233 goto out_unlock; 234 } 235 dfile->data_len = ret; 236 dfile->buffer = buf; 237 } 238 239 ret = simple_read_from_buffer(userbuf, count, ppos, 240 dfile->buffer, 241 dfile->data_len); 242 if (*ppos >= dfile->data_len) { 243 free_pages((unsigned long)dfile->buffer, buforder); 244 dfile->buffer = NULL; 245 dfile->data_len = 0; 246 } 247 out_unlock: 248 mutex_unlock(&dev->wl->mutex); 249 250 return err ? err : ret; 251 } 252 253 static ssize_t b43legacy_debugfs_write(struct file *file, 254 const char __user *userbuf, 255 size_t count, loff_t *ppos) 256 { 257 struct b43legacy_wldev *dev; 258 const struct b43legacy_debugfs_fops *dfops; 259 char *buf; 260 int err = 0; 261 262 if (!count) 263 return 0; 264 if (count > PAGE_SIZE) 265 return -E2BIG; 266 dev = file->private_data; 267 if (!dev) 268 return -ENODEV; 269 270 mutex_lock(&dev->wl->mutex); 271 if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) { 272 err = -ENODEV; 273 goto out_unlock; 274 } 275 276 dfops = debugfs_get_aux(file); 277 if (!dfops->write) { 278 err = -ENOSYS; 279 goto out_unlock; 280 } 281 282 buf = (char *)get_zeroed_page(GFP_KERNEL); 283 if (!buf) { 284 err = -ENOMEM; 285 goto out_unlock; 286 } 287 if (copy_from_user(buf, userbuf, count)) { 288 err = -EFAULT; 289 goto out_freepage; 290 } 291 if (dfops->take_irqlock) { 292 spin_lock_irq(&dev->wl->irq_lock); 293 err = dfops->write(dev, buf, count); 294 spin_unlock_irq(&dev->wl->irq_lock); 295 } else 296 err = dfops->write(dev, buf, count); 297 if (err) 298 goto out_freepage; 299 300 out_freepage: 301 free_page((unsigned long)buf); 302 out_unlock: 303 mutex_unlock(&dev->wl->mutex); 304 305 return err ? err : count; 306 } 307 308 static struct debugfs_short_fops debugfs_ops = { 309 .read = b43legacy_debugfs_read, 310 .write = b43legacy_debugfs_write, 311 .llseek = generic_file_llseek 312 }; 313 314 #define B43legacy_DEBUGFS_FOPS(name, _read, _write, _take_irqlock) \ 315 static struct b43legacy_debugfs_fops fops_##name = { \ 316 .read = _read, \ 317 .write = _write, \ 318 .file_struct_offset = offsetof(struct b43legacy_dfsentry, \ 319 file_##name), \ 320 .take_irqlock = _take_irqlock, \ 321 } 322 323 B43legacy_DEBUGFS_FOPS(tsf, tsf_read_file, tsf_write_file, 1); 324 B43legacy_DEBUGFS_FOPS(ucode_regs, ucode_regs_read_file, NULL, 1); 325 B43legacy_DEBUGFS_FOPS(shm, shm_read_file, NULL, 1); 326 B43legacy_DEBUGFS_FOPS(txstat, txstat_read_file, NULL, 0); 327 B43legacy_DEBUGFS_FOPS(restart, NULL, restart_write_file, 1); 328 329 330 int b43legacy_debug(struct b43legacy_wldev *dev, enum b43legacy_dyndbg feature) 331 { 332 return !!(dev->dfsentry && dev->dfsentry->dyn_debug[feature]); 333 } 334 335 static void b43legacy_add_dynamic_debug(struct b43legacy_wldev *dev) 336 { 337 struct b43legacy_dfsentry *e = dev->dfsentry; 338 339 #define add_dyn_dbg(name, id, initstate) do { \ 340 e->dyn_debug[id] = (initstate); \ 341 debugfs_create_bool(name, 0600, e->subdir, \ 342 &(e->dyn_debug[id])); \ 343 } while (0) 344 345 add_dyn_dbg("debug_xmitpower", B43legacy_DBG_XMITPOWER, false); 346 add_dyn_dbg("debug_dmaoverflow", B43legacy_DBG_DMAOVERFLOW, false); 347 add_dyn_dbg("debug_dmaverbose", B43legacy_DBG_DMAVERBOSE, false); 348 add_dyn_dbg("debug_pwork_fast", B43legacy_DBG_PWORK_FAST, false); 349 add_dyn_dbg("debug_pwork_stop", B43legacy_DBG_PWORK_STOP, false); 350 351 #undef add_dyn_dbg 352 } 353 354 void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev) 355 { 356 struct b43legacy_dfsentry *e; 357 struct b43legacy_txstatus_log *log; 358 char devdir[16]; 359 360 B43legacy_WARN_ON(!dev); 361 e = kzalloc(sizeof(*e), GFP_KERNEL); 362 if (!e) { 363 b43legacyerr(dev->wl, "debugfs: add device OOM\n"); 364 return; 365 } 366 e->dev = dev; 367 log = &e->txstatlog; 368 log->log = kcalloc(B43legacy_NR_LOGGED_TXSTATUS, 369 sizeof(struct b43legacy_txstatus), GFP_KERNEL); 370 if (!log->log) { 371 b43legacyerr(dev->wl, "debugfs: add device txstatus OOM\n"); 372 kfree(e); 373 return; 374 } 375 log->end = -1; 376 spin_lock_init(&log->lock); 377 378 dev->dfsentry = e; 379 380 snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy)); 381 e->subdir = debugfs_create_dir(devdir, rootdir); 382 383 #define ADD_FILE(name, mode) \ 384 do { \ 385 debugfs_create_file_aux(__stringify(name), mode, \ 386 e->subdir, dev, \ 387 &fops_##name, &debugfs_ops); \ 388 } while (0) 389 390 391 ADD_FILE(tsf, 0600); 392 ADD_FILE(ucode_regs, 0400); 393 ADD_FILE(shm, 0400); 394 ADD_FILE(txstat, 0400); 395 ADD_FILE(restart, 0200); 396 397 #undef ADD_FILE 398 399 b43legacy_add_dynamic_debug(dev); 400 } 401 402 void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev) 403 { 404 struct b43legacy_dfsentry *e; 405 406 if (!dev) 407 return; 408 e = dev->dfsentry; 409 if (!e) 410 return; 411 412 debugfs_remove(e->subdir); 413 kfree(e->txstatlog.log); 414 kfree(e); 415 } 416 417 void b43legacy_debugfs_log_txstat(struct b43legacy_wldev *dev, 418 const struct b43legacy_txstatus *status) 419 { 420 struct b43legacy_dfsentry *e = dev->dfsentry; 421 struct b43legacy_txstatus_log *log; 422 struct b43legacy_txstatus *cur; 423 int i; 424 425 if (!e) 426 return; 427 log = &e->txstatlog; 428 B43legacy_WARN_ON(!irqs_disabled()); 429 spin_lock(&log->lock); 430 i = log->end + 1; 431 if (i == B43legacy_NR_LOGGED_TXSTATUS) 432 i = 0; 433 log->end = i; 434 cur = &(log->log[i]); 435 memcpy(cur, status, sizeof(*cur)); 436 spin_unlock(&log->lock); 437 } 438 439 void b43legacy_debugfs_init(void) 440 { 441 rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); 442 } 443 444 void b43legacy_debugfs_exit(void) 445 { 446 debugfs_remove(rootdir); 447 } 448