Frank Haverkamp | c59330c | 2013-12-09 13:30:42 +0100 | [diff] [blame] | 1 | /** |
| 2 | * IBM Accelerator Family 'GenWQE' |
| 3 | * |
| 4 | * (C) Copyright IBM Corp. 2013 |
| 5 | * |
| 6 | * Author: Frank Haverkamp <haver@linux.vnet.ibm.com> |
| 7 | * Author: Joerg-Stephan Vogt <jsvogt@de.ibm.com> |
| 8 | * Author: Michael Jung <mijung@de.ibm.com> |
| 9 | * Author: Michael Ruettger <michael@ibmra.de> |
| 10 | * |
| 11 | * This program is free software; you can redistribute it and/or modify |
| 12 | * it under the terms of the GNU General Public License (version 2 only) |
| 13 | * as published by the Free Software Foundation. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU General Public License for more details. |
| 19 | */ |
| 20 | |
| 21 | /* |
| 22 | * Debugfs interfaces for the GenWQE card. Help to debug potential |
| 23 | * problems. Dump internal chip state for debugging and failure |
| 24 | * determination. |
| 25 | */ |
| 26 | |
| 27 | #include <linux/module.h> |
| 28 | #include <linux/kernel.h> |
Frank Haverkamp | c59330c | 2013-12-09 13:30:42 +0100 | [diff] [blame] | 29 | #include <linux/debugfs.h> |
| 30 | #include <linux/seq_file.h> |
| 31 | #include <linux/uaccess.h> |
| 32 | |
| 33 | #include "card_base.h" |
| 34 | #include "card_ddcb.h" |
| 35 | |
| 36 | #define GENWQE_DEBUGFS_RO(_name, _showfn) \ |
| 37 | static int genwqe_debugfs_##_name##_open(struct inode *inode, \ |
| 38 | struct file *file) \ |
| 39 | { \ |
| 40 | return single_open(file, _showfn, inode->i_private); \ |
| 41 | } \ |
| 42 | static const struct file_operations genwqe_##_name##_fops = { \ |
| 43 | .open = genwqe_debugfs_##_name##_open, \ |
| 44 | .read = seq_read, \ |
| 45 | .llseek = seq_lseek, \ |
| 46 | .release = single_release, \ |
| 47 | } |
| 48 | |
| 49 | static void dbg_uidn_show(struct seq_file *s, struct genwqe_reg *regs, |
| 50 | int entries) |
| 51 | { |
| 52 | unsigned int i; |
| 53 | u32 v_hi, v_lo; |
| 54 | |
| 55 | for (i = 0; i < entries; i++) { |
| 56 | v_hi = (regs[i].val >> 32) & 0xffffffff; |
| 57 | v_lo = (regs[i].val) & 0xffffffff; |
| 58 | |
| 59 | seq_printf(s, " 0x%08x 0x%08x 0x%08x 0x%08x EXT_ERR_REC\n", |
| 60 | regs[i].addr, regs[i].idx, v_hi, v_lo); |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | static int curr_dbg_uidn_show(struct seq_file *s, void *unused, int uid) |
| 65 | { |
| 66 | struct genwqe_dev *cd = s->private; |
| 67 | int entries; |
| 68 | struct genwqe_reg *regs; |
| 69 | |
| 70 | entries = genwqe_ffdc_buff_size(cd, uid); |
| 71 | if (entries < 0) |
| 72 | return -EINVAL; |
| 73 | |
| 74 | if (entries == 0) |
| 75 | return 0; |
| 76 | |
| 77 | regs = kcalloc(entries, sizeof(*regs), GFP_KERNEL); |
| 78 | if (regs == NULL) |
| 79 | return -ENOMEM; |
| 80 | |
| 81 | genwqe_stop_traps(cd); /* halt the traps while dumping data */ |
| 82 | genwqe_ffdc_buff_read(cd, uid, regs, entries); |
| 83 | genwqe_start_traps(cd); |
| 84 | |
| 85 | dbg_uidn_show(s, regs, entries); |
| 86 | kfree(regs); |
| 87 | return 0; |
| 88 | } |
| 89 | |
| 90 | static int genwqe_curr_dbg_uid0_show(struct seq_file *s, void *unused) |
| 91 | { |
| 92 | return curr_dbg_uidn_show(s, unused, 0); |
| 93 | } |
| 94 | |
| 95 | GENWQE_DEBUGFS_RO(curr_dbg_uid0, genwqe_curr_dbg_uid0_show); |
| 96 | |
| 97 | static int genwqe_curr_dbg_uid1_show(struct seq_file *s, void *unused) |
| 98 | { |
| 99 | return curr_dbg_uidn_show(s, unused, 1); |
| 100 | } |
| 101 | |
| 102 | GENWQE_DEBUGFS_RO(curr_dbg_uid1, genwqe_curr_dbg_uid1_show); |
| 103 | |
| 104 | static int genwqe_curr_dbg_uid2_show(struct seq_file *s, void *unused) |
| 105 | { |
| 106 | return curr_dbg_uidn_show(s, unused, 2); |
| 107 | } |
| 108 | |
| 109 | GENWQE_DEBUGFS_RO(curr_dbg_uid2, genwqe_curr_dbg_uid2_show); |
| 110 | |
| 111 | static int prev_dbg_uidn_show(struct seq_file *s, void *unused, int uid) |
| 112 | { |
| 113 | struct genwqe_dev *cd = s->private; |
| 114 | |
| 115 | dbg_uidn_show(s, cd->ffdc[uid].regs, cd->ffdc[uid].entries); |
| 116 | return 0; |
| 117 | } |
| 118 | |
| 119 | static int genwqe_prev_dbg_uid0_show(struct seq_file *s, void *unused) |
| 120 | { |
| 121 | return prev_dbg_uidn_show(s, unused, 0); |
| 122 | } |
| 123 | |
| 124 | GENWQE_DEBUGFS_RO(prev_dbg_uid0, genwqe_prev_dbg_uid0_show); |
| 125 | |
| 126 | static int genwqe_prev_dbg_uid1_show(struct seq_file *s, void *unused) |
| 127 | { |
| 128 | return prev_dbg_uidn_show(s, unused, 1); |
| 129 | } |
| 130 | |
| 131 | GENWQE_DEBUGFS_RO(prev_dbg_uid1, genwqe_prev_dbg_uid1_show); |
| 132 | |
| 133 | static int genwqe_prev_dbg_uid2_show(struct seq_file *s, void *unused) |
| 134 | { |
| 135 | return prev_dbg_uidn_show(s, unused, 2); |
| 136 | } |
| 137 | |
| 138 | GENWQE_DEBUGFS_RO(prev_dbg_uid2, genwqe_prev_dbg_uid2_show); |
| 139 | |
| 140 | static int genwqe_curr_regs_show(struct seq_file *s, void *unused) |
| 141 | { |
| 142 | struct genwqe_dev *cd = s->private; |
| 143 | unsigned int i; |
| 144 | struct genwqe_reg *regs; |
| 145 | |
| 146 | regs = kcalloc(GENWQE_FFDC_REGS, sizeof(*regs), GFP_KERNEL); |
| 147 | if (regs == NULL) |
| 148 | return -ENOMEM; |
| 149 | |
| 150 | genwqe_stop_traps(cd); |
| 151 | genwqe_read_ffdc_regs(cd, regs, GENWQE_FFDC_REGS, 1); |
| 152 | genwqe_start_traps(cd); |
| 153 | |
| 154 | for (i = 0; i < GENWQE_FFDC_REGS; i++) { |
| 155 | if (regs[i].addr == 0xffffffff) |
| 156 | break; /* invalid entries */ |
| 157 | |
| 158 | if (regs[i].val == 0x0ull) |
| 159 | continue; /* do not print 0x0 FIRs */ |
| 160 | |
| 161 | seq_printf(s, " 0x%08x 0x%016llx\n", |
| 162 | regs[i].addr, regs[i].val); |
| 163 | } |
| 164 | return 0; |
| 165 | } |
| 166 | |
| 167 | GENWQE_DEBUGFS_RO(curr_regs, genwqe_curr_regs_show); |
| 168 | |
| 169 | static int genwqe_prev_regs_show(struct seq_file *s, void *unused) |
| 170 | { |
| 171 | struct genwqe_dev *cd = s->private; |
| 172 | unsigned int i; |
| 173 | struct genwqe_reg *regs = cd->ffdc[GENWQE_DBG_REGS].regs; |
| 174 | |
| 175 | if (regs == NULL) |
| 176 | return -EINVAL; |
| 177 | |
| 178 | for (i = 0; i < GENWQE_FFDC_REGS; i++) { |
| 179 | if (regs[i].addr == 0xffffffff) |
| 180 | break; /* invalid entries */ |
| 181 | |
| 182 | if (regs[i].val == 0x0ull) |
| 183 | continue; /* do not print 0x0 FIRs */ |
| 184 | |
| 185 | seq_printf(s, " 0x%08x 0x%016llx\n", |
| 186 | regs[i].addr, regs[i].val); |
| 187 | } |
| 188 | return 0; |
| 189 | } |
| 190 | |
| 191 | GENWQE_DEBUGFS_RO(prev_regs, genwqe_prev_regs_show); |
| 192 | |
| 193 | static int genwqe_jtimer_show(struct seq_file *s, void *unused) |
| 194 | { |
| 195 | struct genwqe_dev *cd = s->private; |
| 196 | unsigned int vf_num; |
| 197 | u64 jtimer; |
| 198 | |
| 199 | jtimer = genwqe_read_vreg(cd, IO_SLC_VF_APPJOB_TIMEOUT, 0); |
| 200 | seq_printf(s, " PF 0x%016llx %d msec\n", jtimer, |
| 201 | genwqe_pf_jobtimeout_msec); |
| 202 | |
| 203 | for (vf_num = 0; vf_num < cd->num_vfs; vf_num++) { |
| 204 | jtimer = genwqe_read_vreg(cd, IO_SLC_VF_APPJOB_TIMEOUT, |
| 205 | vf_num + 1); |
| 206 | seq_printf(s, " VF%-2d 0x%016llx %d msec\n", vf_num, jtimer, |
| 207 | cd->vf_jobtimeout_msec[vf_num]); |
| 208 | } |
| 209 | return 0; |
| 210 | } |
| 211 | |
| 212 | GENWQE_DEBUGFS_RO(jtimer, genwqe_jtimer_show); |
| 213 | |
| 214 | static int genwqe_queue_working_time_show(struct seq_file *s, void *unused) |
| 215 | { |
| 216 | struct genwqe_dev *cd = s->private; |
| 217 | unsigned int vf_num; |
| 218 | u64 t; |
| 219 | |
| 220 | t = genwqe_read_vreg(cd, IO_SLC_VF_QUEUE_WTIME, 0); |
| 221 | seq_printf(s, " PF 0x%016llx\n", t); |
| 222 | |
| 223 | for (vf_num = 0; vf_num < cd->num_vfs; vf_num++) { |
| 224 | t = genwqe_read_vreg(cd, IO_SLC_VF_QUEUE_WTIME, vf_num + 1); |
| 225 | seq_printf(s, " VF%-2d 0x%016llx\n", vf_num, t); |
| 226 | } |
| 227 | return 0; |
| 228 | } |
| 229 | |
| 230 | GENWQE_DEBUGFS_RO(queue_working_time, genwqe_queue_working_time_show); |
| 231 | |
| 232 | static int genwqe_ddcb_info_show(struct seq_file *s, void *unused) |
| 233 | { |
| 234 | struct genwqe_dev *cd = s->private; |
| 235 | unsigned int i; |
| 236 | struct ddcb_queue *queue; |
| 237 | struct ddcb *pddcb; |
| 238 | |
| 239 | queue = &cd->queue; |
| 240 | seq_puts(s, "DDCB QUEUE:\n"); |
| 241 | seq_printf(s, " ddcb_max: %d\n" |
| 242 | " ddcb_daddr: %016llx - %016llx\n" |
| 243 | " ddcb_vaddr: %016llx\n" |
| 244 | " ddcbs_in_flight: %u\n" |
| 245 | " ddcbs_max_in_flight: %u\n" |
| 246 | " ddcbs_completed: %u\n" |
| 247 | " busy: %u\n" |
| 248 | " irqs_processed: %u\n", |
| 249 | queue->ddcb_max, (long long)queue->ddcb_daddr, |
| 250 | (long long)queue->ddcb_daddr + |
| 251 | (queue->ddcb_max * DDCB_LENGTH), |
| 252 | (long long)queue->ddcb_vaddr, queue->ddcbs_in_flight, |
| 253 | queue->ddcbs_max_in_flight, queue->ddcbs_completed, |
| 254 | queue->busy, cd->irqs_processed); |
| 255 | |
| 256 | /* Hardware State */ |
| 257 | seq_printf(s, " 0x%08x 0x%016llx IO_QUEUE_CONFIG\n" |
| 258 | " 0x%08x 0x%016llx IO_QUEUE_STATUS\n" |
| 259 | " 0x%08x 0x%016llx IO_QUEUE_SEGMENT\n" |
| 260 | " 0x%08x 0x%016llx IO_QUEUE_INITSQN\n" |
| 261 | " 0x%08x 0x%016llx IO_QUEUE_WRAP\n" |
| 262 | " 0x%08x 0x%016llx IO_QUEUE_OFFSET\n" |
| 263 | " 0x%08x 0x%016llx IO_QUEUE_WTIME\n" |
| 264 | " 0x%08x 0x%016llx IO_QUEUE_ERRCNTS\n" |
| 265 | " 0x%08x 0x%016llx IO_QUEUE_LRW\n", |
| 266 | queue->IO_QUEUE_CONFIG, |
| 267 | __genwqe_readq(cd, queue->IO_QUEUE_CONFIG), |
| 268 | queue->IO_QUEUE_STATUS, |
| 269 | __genwqe_readq(cd, queue->IO_QUEUE_STATUS), |
| 270 | queue->IO_QUEUE_SEGMENT, |
| 271 | __genwqe_readq(cd, queue->IO_QUEUE_SEGMENT), |
| 272 | queue->IO_QUEUE_INITSQN, |
| 273 | __genwqe_readq(cd, queue->IO_QUEUE_INITSQN), |
| 274 | queue->IO_QUEUE_WRAP, |
| 275 | __genwqe_readq(cd, queue->IO_QUEUE_WRAP), |
| 276 | queue->IO_QUEUE_OFFSET, |
| 277 | __genwqe_readq(cd, queue->IO_QUEUE_OFFSET), |
| 278 | queue->IO_QUEUE_WTIME, |
| 279 | __genwqe_readq(cd, queue->IO_QUEUE_WTIME), |
| 280 | queue->IO_QUEUE_ERRCNTS, |
| 281 | __genwqe_readq(cd, queue->IO_QUEUE_ERRCNTS), |
| 282 | queue->IO_QUEUE_LRW, |
| 283 | __genwqe_readq(cd, queue->IO_QUEUE_LRW)); |
| 284 | |
| 285 | seq_printf(s, "DDCB list (ddcb_act=%d/ddcb_next=%d):\n", |
| 286 | queue->ddcb_act, queue->ddcb_next); |
| 287 | |
| 288 | pddcb = queue->ddcb_vaddr; |
| 289 | for (i = 0; i < queue->ddcb_max; i++) { |
| 290 | seq_printf(s, " %-3d: RETC=%03x SEQ=%04x HSI/SHI=%02x/%02x ", |
| 291 | i, be16_to_cpu(pddcb->retc_16), |
| 292 | be16_to_cpu(pddcb->seqnum_16), |
| 293 | pddcb->hsi, pddcb->shi); |
| 294 | seq_printf(s, "PRIV=%06llx CMD=%02x\n", |
| 295 | be64_to_cpu(pddcb->priv_64), pddcb->cmd); |
| 296 | pddcb++; |
| 297 | } |
| 298 | return 0; |
| 299 | } |
| 300 | |
| 301 | GENWQE_DEBUGFS_RO(ddcb_info, genwqe_ddcb_info_show); |
| 302 | |
| 303 | static int genwqe_info_show(struct seq_file *s, void *unused) |
| 304 | { |
| 305 | struct genwqe_dev *cd = s->private; |
| 306 | u16 val16, type; |
| 307 | u64 app_id, slu_id, bitstream = -1; |
| 308 | struct pci_dev *pci_dev = cd->pci_dev; |
| 309 | |
| 310 | slu_id = __genwqe_readq(cd, IO_SLU_UNITCFG); |
| 311 | app_id = __genwqe_readq(cd, IO_APP_UNITCFG); |
| 312 | |
| 313 | if (genwqe_is_privileged(cd)) |
| 314 | bitstream = __genwqe_readq(cd, IO_SLU_BITSTREAM); |
| 315 | |
| 316 | val16 = (u16)(slu_id & 0x0fLLU); |
| 317 | type = (u16)((slu_id >> 20) & 0xffLLU); |
| 318 | |
| 319 | seq_printf(s, "%s driver version: %s\n" |
| 320 | " Device Name/Type: %s %s CardIdx: %d\n" |
| 321 | " SLU/APP Config : 0x%016llx/0x%016llx\n" |
| 322 | " Build Date : %u/%x/%u\n" |
| 323 | " Base Clock : %u MHz\n" |
| 324 | " Arch/SVN Release: %u/%llx\n" |
| 325 | " Bitstream : %llx\n", |
| 326 | GENWQE_DEVNAME, DRV_VERS_STRING, dev_name(&pci_dev->dev), |
| 327 | genwqe_is_privileged(cd) ? |
| 328 | "Physical" : "Virtual or no SR-IOV", |
| 329 | cd->card_idx, slu_id, app_id, |
| 330 | (u16)((slu_id >> 12) & 0x0fLLU), /* month */ |
| 331 | (u16)((slu_id >> 4) & 0xffLLU), /* day */ |
| 332 | (u16)((slu_id >> 16) & 0x0fLLU) + 2010, /* year */ |
| 333 | genwqe_base_clock_frequency(cd), |
| 334 | (u16)((slu_id >> 32) & 0xffLLU), slu_id >> 40, |
| 335 | bitstream); |
| 336 | |
| 337 | return 0; |
| 338 | } |
| 339 | |
| 340 | GENWQE_DEBUGFS_RO(info, genwqe_info_show); |
| 341 | |
| 342 | int genwqe_init_debugfs(struct genwqe_dev *cd) |
| 343 | { |
| 344 | struct dentry *root; |
| 345 | struct dentry *file; |
| 346 | int ret; |
| 347 | char card_name[64]; |
| 348 | char name[64]; |
| 349 | unsigned int i; |
| 350 | |
Masanari Iida | 32d9dbe | 2014-04-24 12:25:49 +0900 | [diff] [blame] | 351 | sprintf(card_name, "%s%d_card", GENWQE_DEVNAME, cd->card_idx); |
Frank Haverkamp | c59330c | 2013-12-09 13:30:42 +0100 | [diff] [blame] | 352 | |
| 353 | root = debugfs_create_dir(card_name, cd->debugfs_genwqe); |
| 354 | if (!root) { |
| 355 | ret = -ENOMEM; |
| 356 | goto err0; |
| 357 | } |
| 358 | |
| 359 | /* non privileged interfaces are done here */ |
| 360 | file = debugfs_create_file("ddcb_info", S_IRUGO, root, cd, |
| 361 | &genwqe_ddcb_info_fops); |
| 362 | if (!file) { |
| 363 | ret = -ENOMEM; |
| 364 | goto err1; |
| 365 | } |
| 366 | |
| 367 | file = debugfs_create_file("info", S_IRUGO, root, cd, |
| 368 | &genwqe_info_fops); |
| 369 | if (!file) { |
| 370 | ret = -ENOMEM; |
| 371 | goto err1; |
| 372 | } |
| 373 | |
| 374 | file = debugfs_create_x64("err_inject", 0666, root, &cd->err_inject); |
| 375 | if (!file) { |
| 376 | ret = -ENOMEM; |
| 377 | goto err1; |
| 378 | } |
| 379 | |
| 380 | file = debugfs_create_u32("ddcb_software_timeout", 0666, root, |
| 381 | &cd->ddcb_software_timeout); |
| 382 | if (!file) { |
| 383 | ret = -ENOMEM; |
| 384 | goto err1; |
| 385 | } |
| 386 | |
| 387 | file = debugfs_create_u32("kill_timeout", 0666, root, |
| 388 | &cd->kill_timeout); |
| 389 | if (!file) { |
| 390 | ret = -ENOMEM; |
| 391 | goto err1; |
| 392 | } |
| 393 | |
| 394 | /* privileged interfaces follow here */ |
| 395 | if (!genwqe_is_privileged(cd)) { |
| 396 | cd->debugfs_root = root; |
| 397 | return 0; |
| 398 | } |
| 399 | |
| 400 | file = debugfs_create_file("curr_regs", S_IRUGO, root, cd, |
| 401 | &genwqe_curr_regs_fops); |
| 402 | if (!file) { |
| 403 | ret = -ENOMEM; |
| 404 | goto err1; |
| 405 | } |
| 406 | |
| 407 | file = debugfs_create_file("curr_dbg_uid0", S_IRUGO, root, cd, |
| 408 | &genwqe_curr_dbg_uid0_fops); |
| 409 | if (!file) { |
| 410 | ret = -ENOMEM; |
| 411 | goto err1; |
| 412 | } |
| 413 | |
| 414 | file = debugfs_create_file("curr_dbg_uid1", S_IRUGO, root, cd, |
| 415 | &genwqe_curr_dbg_uid1_fops); |
| 416 | if (!file) { |
| 417 | ret = -ENOMEM; |
| 418 | goto err1; |
| 419 | } |
| 420 | |
| 421 | file = debugfs_create_file("curr_dbg_uid2", S_IRUGO, root, cd, |
| 422 | &genwqe_curr_dbg_uid2_fops); |
| 423 | if (!file) { |
| 424 | ret = -ENOMEM; |
| 425 | goto err1; |
| 426 | } |
| 427 | |
| 428 | file = debugfs_create_file("prev_regs", S_IRUGO, root, cd, |
| 429 | &genwqe_prev_regs_fops); |
| 430 | if (!file) { |
| 431 | ret = -ENOMEM; |
| 432 | goto err1; |
| 433 | } |
| 434 | |
| 435 | file = debugfs_create_file("prev_dbg_uid0", S_IRUGO, root, cd, |
| 436 | &genwqe_prev_dbg_uid0_fops); |
| 437 | if (!file) { |
| 438 | ret = -ENOMEM; |
| 439 | goto err1; |
| 440 | } |
| 441 | |
| 442 | file = debugfs_create_file("prev_dbg_uid1", S_IRUGO, root, cd, |
| 443 | &genwqe_prev_dbg_uid1_fops); |
| 444 | if (!file) { |
| 445 | ret = -ENOMEM; |
| 446 | goto err1; |
| 447 | } |
| 448 | |
| 449 | file = debugfs_create_file("prev_dbg_uid2", S_IRUGO, root, cd, |
| 450 | &genwqe_prev_dbg_uid2_fops); |
| 451 | if (!file) { |
| 452 | ret = -ENOMEM; |
| 453 | goto err1; |
| 454 | } |
| 455 | |
| 456 | for (i = 0; i < GENWQE_MAX_VFS; i++) { |
Masanari Iida | 32d9dbe | 2014-04-24 12:25:49 +0900 | [diff] [blame] | 457 | sprintf(name, "vf%u_jobtimeout_msec", i); |
Frank Haverkamp | c59330c | 2013-12-09 13:30:42 +0100 | [diff] [blame] | 458 | |
| 459 | file = debugfs_create_u32(name, 0666, root, |
| 460 | &cd->vf_jobtimeout_msec[i]); |
| 461 | if (!file) { |
| 462 | ret = -ENOMEM; |
| 463 | goto err1; |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | file = debugfs_create_file("jobtimer", S_IRUGO, root, cd, |
| 468 | &genwqe_jtimer_fops); |
| 469 | if (!file) { |
| 470 | ret = -ENOMEM; |
| 471 | goto err1; |
| 472 | } |
| 473 | |
| 474 | file = debugfs_create_file("queue_working_time", S_IRUGO, root, cd, |
| 475 | &genwqe_queue_working_time_fops); |
| 476 | if (!file) { |
| 477 | ret = -ENOMEM; |
| 478 | goto err1; |
| 479 | } |
| 480 | |
| 481 | file = debugfs_create_u32("skip_recovery", 0666, root, |
| 482 | &cd->skip_recovery); |
| 483 | if (!file) { |
| 484 | ret = -ENOMEM; |
| 485 | goto err1; |
| 486 | } |
| 487 | |
| 488 | cd->debugfs_root = root; |
| 489 | return 0; |
| 490 | err1: |
| 491 | debugfs_remove_recursive(root); |
| 492 | err0: |
| 493 | return ret; |
| 494 | } |
| 495 | |
| 496 | void genqwe_exit_debugfs(struct genwqe_dev *cd) |
| 497 | { |
| 498 | debugfs_remove_recursive(cd->debugfs_root); |
| 499 | } |