Commit | Line | Data |
---|---|---|
ab2a9ba1 JH |
1 | /* |
2 | * Copyright (c) 2005-2010 Brocade Communications Systems, Inc. | |
3 | * All rights reserved | |
4 | * www.brocade.com | |
5 | * | |
6 | * Linux driver for Brocade Fibre Channel Host Bus Adapter. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License (GPL) Version 2 as | |
10 | * published by the Free Software Foundation | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | */ | |
17 | ||
18 | #include <linux/debugfs.h> | |
19 | ||
20 | #include <bfad_drv.h> | |
21 | #include <bfad_im.h> | |
22 | ||
23 | /* | |
24 | * BFA debufs interface | |
25 | * | |
26 | * To access the interface, debugfs file system should be mounted | |
27 | * if not already mounted using: | |
28 | * mount -t debugfs none /sys/kernel/debug | |
29 | * | |
30 | * BFA Hierarchy: | |
31 | * - bfa/host# | |
32 | * where the host number corresponds to the one under /sys/class/scsi_host/host# | |
33 | * | |
34 | * Debugging service available per host: | |
35 | * fwtrc: To collect current firmware trace. | |
36 | * drvtrc: To collect current driver trace | |
37 | * fwsave: To collect last saved fw trace as a result of firmware crash. | |
38 | * regwr: To write one word to chip register | |
39 | * regrd: To read one or more words from chip register. | |
40 | */ | |
41 | ||
42 | struct bfad_debug_info { | |
43 | char *debug_buffer; | |
44 | void *i_private; | |
45 | int buffer_len; | |
46 | }; | |
47 | ||
48 | static int | |
49 | bfad_debugfs_open_drvtrc(struct inode *inode, struct file *file) | |
50 | { | |
51 | struct bfad_port_s *port = inode->i_private; | |
52 | struct bfad_s *bfad = port->bfad; | |
53 | struct bfad_debug_info *debug; | |
54 | ||
55 | debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL); | |
56 | if (!debug) | |
57 | return -ENOMEM; | |
58 | ||
59 | debug->debug_buffer = (void *) bfad->trcmod; | |
60 | debug->buffer_len = sizeof(struct bfa_trc_mod_s); | |
61 | ||
62 | file->private_data = debug; | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static int | |
68 | bfad_debugfs_open_fwtrc(struct inode *inode, struct file *file) | |
69 | { | |
70 | struct bfad_port_s *port = inode->i_private; | |
71 | struct bfad_s *bfad = port->bfad; | |
72 | struct bfad_debug_info *fw_debug; | |
73 | unsigned long flags; | |
74 | int rc; | |
75 | ||
76 | fw_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL); | |
77 | if (!fw_debug) | |
78 | return -ENOMEM; | |
79 | ||
80 | fw_debug->buffer_len = sizeof(struct bfa_trc_mod_s); | |
81 | ||
82 | fw_debug->debug_buffer = vmalloc(fw_debug->buffer_len); | |
83 | if (!fw_debug->debug_buffer) { | |
84 | kfree(fw_debug); | |
85 | printk(KERN_INFO "bfad[%d]: Failed to allocate fwtrc buffer\n", | |
86 | bfad->inst_no); | |
87 | return -ENOMEM; | |
88 | } | |
89 | ||
90 | memset(fw_debug->debug_buffer, 0, fw_debug->buffer_len); | |
91 | ||
92 | spin_lock_irqsave(&bfad->bfad_lock, flags); | |
93 | rc = bfa_debug_fwtrc(&bfad->bfa, | |
94 | fw_debug->debug_buffer, | |
95 | &fw_debug->buffer_len); | |
96 | spin_unlock_irqrestore(&bfad->bfad_lock, flags); | |
97 | if (rc != BFA_STATUS_OK) { | |
98 | vfree(fw_debug->debug_buffer); | |
99 | fw_debug->debug_buffer = NULL; | |
100 | kfree(fw_debug); | |
101 | printk(KERN_INFO "bfad[%d]: Failed to collect fwtrc\n", | |
102 | bfad->inst_no); | |
103 | return -ENOMEM; | |
104 | } | |
105 | ||
106 | file->private_data = fw_debug; | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | static int | |
112 | bfad_debugfs_open_fwsave(struct inode *inode, struct file *file) | |
113 | { | |
114 | struct bfad_port_s *port = inode->i_private; | |
115 | struct bfad_s *bfad = port->bfad; | |
116 | struct bfad_debug_info *fw_debug; | |
117 | unsigned long flags; | |
118 | int rc; | |
119 | ||
120 | fw_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL); | |
121 | if (!fw_debug) | |
122 | return -ENOMEM; | |
123 | ||
124 | fw_debug->buffer_len = sizeof(struct bfa_trc_mod_s); | |
125 | ||
126 | fw_debug->debug_buffer = vmalloc(fw_debug->buffer_len); | |
127 | if (!fw_debug->debug_buffer) { | |
128 | kfree(fw_debug); | |
129 | printk(KERN_INFO "bfad[%d]: Failed to allocate fwsave buffer\n", | |
130 | bfad->inst_no); | |
131 | return -ENOMEM; | |
132 | } | |
133 | ||
134 | memset(fw_debug->debug_buffer, 0, fw_debug->buffer_len); | |
135 | ||
136 | spin_lock_irqsave(&bfad->bfad_lock, flags); | |
137 | rc = bfa_debug_fwsave(&bfad->bfa, | |
138 | fw_debug->debug_buffer, | |
139 | &fw_debug->buffer_len); | |
140 | spin_unlock_irqrestore(&bfad->bfad_lock, flags); | |
141 | if (rc != BFA_STATUS_OK) { | |
142 | vfree(fw_debug->debug_buffer); | |
143 | fw_debug->debug_buffer = NULL; | |
144 | kfree(fw_debug); | |
145 | printk(KERN_INFO "bfad[%d]: Failed to collect fwsave\n", | |
146 | bfad->inst_no); | |
147 | return -ENOMEM; | |
148 | } | |
149 | ||
150 | file->private_data = fw_debug; | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static int | |
156 | bfad_debugfs_open_reg(struct inode *inode, struct file *file) | |
157 | { | |
158 | struct bfad_debug_info *reg_debug; | |
159 | ||
160 | reg_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL); | |
161 | if (!reg_debug) | |
162 | return -ENOMEM; | |
163 | ||
164 | reg_debug->i_private = inode->i_private; | |
165 | ||
166 | file->private_data = reg_debug; | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | /* Changes the current file position */ | |
172 | static loff_t | |
173 | bfad_debugfs_lseek(struct file *file, loff_t offset, int orig) | |
174 | { | |
175 | struct bfad_debug_info *debug; | |
176 | loff_t pos = file->f_pos; | |
177 | ||
178 | debug = file->private_data; | |
179 | ||
180 | switch (orig) { | |
181 | case 0: | |
182 | file->f_pos = offset; | |
183 | break; | |
184 | case 1: | |
185 | file->f_pos += offset; | |
186 | break; | |
187 | case 2: | |
188 | file->f_pos = debug->buffer_len - offset; | |
189 | break; | |
190 | default: | |
191 | return -EINVAL; | |
192 | } | |
193 | ||
194 | if (file->f_pos < 0 || file->f_pos > debug->buffer_len) { | |
195 | file->f_pos = pos; | |
196 | return -EINVAL; | |
197 | } | |
198 | ||
199 | return file->f_pos; | |
200 | } | |
201 | ||
202 | static ssize_t | |
203 | bfad_debugfs_read(struct file *file, char __user *buf, | |
204 | size_t nbytes, loff_t *pos) | |
205 | { | |
206 | struct bfad_debug_info *debug = file->private_data; | |
207 | ||
208 | if (!debug || !debug->debug_buffer) | |
209 | return 0; | |
210 | ||
211 | return memory_read_from_buffer(buf, nbytes, pos, | |
212 | debug->debug_buffer, debug->buffer_len); | |
213 | } | |
214 | ||
215 | #define BFA_REG_CT_ADDRSZ (0x40000) | |
216 | #define BFA_REG_CB_ADDRSZ (0x20000) | |
217 | #define BFA_REG_ADDRSZ(__bfa) \ | |
218 | ((bfa_ioc_devid(&(__bfa)->ioc) == BFA_PCI_DEVICE_ID_CT) ? \ | |
219 | BFA_REG_CT_ADDRSZ : BFA_REG_CB_ADDRSZ) | |
220 | #define BFA_REG_ADDRMSK(__bfa) ((uint32_t)(BFA_REG_ADDRSZ(__bfa) - 1)) | |
221 | ||
222 | static bfa_status_t | |
223 | bfad_reg_offset_check(struct bfa_s *bfa, u32 offset, u32 len) | |
224 | { | |
225 | u8 area; | |
226 | ||
227 | /* check [16:15] */ | |
228 | area = (offset >> 15) & 0x7; | |
229 | if (area == 0) { | |
230 | /* PCIe core register */ | |
231 | if ((offset + (len<<2)) > 0x8000) /* 8k dwords or 32KB */ | |
232 | return BFA_STATUS_EINVAL; | |
233 | } else if (area == 0x1) { | |
234 | /* CB 32 KB memory page */ | |
235 | if ((offset + (len<<2)) > 0x10000) /* 8k dwords or 32KB */ | |
236 | return BFA_STATUS_EINVAL; | |
237 | } else { | |
238 | /* CB register space 64KB */ | |
239 | if ((offset + (len<<2)) > BFA_REG_ADDRMSK(bfa)) | |
240 | return BFA_STATUS_EINVAL; | |
241 | } | |
242 | return BFA_STATUS_OK; | |
243 | } | |
244 | ||
245 | static ssize_t | |
246 | bfad_debugfs_read_regrd(struct file *file, char __user *buf, | |
247 | size_t nbytes, loff_t *pos) | |
248 | { | |
249 | struct bfad_debug_info *regrd_debug = file->private_data; | |
250 | struct bfad_port_s *port = (struct bfad_port_s *)regrd_debug->i_private; | |
251 | struct bfad_s *bfad = port->bfad; | |
252 | ssize_t rc; | |
253 | ||
254 | if (!bfad->regdata) | |
255 | return 0; | |
256 | ||
257 | rc = memory_read_from_buffer(buf, nbytes, pos, | |
258 | bfad->regdata, bfad->reglen); | |
259 | ||
260 | if ((*pos + nbytes) >= bfad->reglen) { | |
261 | kfree(bfad->regdata); | |
262 | bfad->regdata = NULL; | |
263 | bfad->reglen = 0; | |
264 | } | |
265 | ||
266 | return rc; | |
267 | } | |
268 | ||
269 | static ssize_t | |
270 | bfad_debugfs_write_regrd(struct file *file, const char __user *buf, | |
271 | size_t nbytes, loff_t *ppos) | |
272 | { | |
273 | struct bfad_debug_info *regrd_debug = file->private_data; | |
274 | struct bfad_port_s *port = (struct bfad_port_s *)regrd_debug->i_private; | |
275 | struct bfad_s *bfad = port->bfad; | |
276 | struct bfa_s *bfa = &bfad->bfa; | |
277 | struct bfa_ioc_s *ioc = &bfa->ioc; | |
278 | int addr, len, rc, i; | |
279 | u32 *regbuf; | |
280 | void __iomem *rb, *reg_addr; | |
281 | unsigned long flags; | |
282 | ||
283 | rc = sscanf(buf, "%x:%x", &addr, &len); | |
284 | if (rc < 2) { | |
285 | printk(KERN_INFO | |
286 | "bfad[%d]: %s failed to read user buf\n", | |
287 | bfad->inst_no, __func__); | |
288 | return -EINVAL; | |
289 | } | |
290 | ||
291 | kfree(bfad->regdata); | |
292 | bfad->regdata = NULL; | |
293 | bfad->reglen = 0; | |
294 | ||
295 | bfad->regdata = kzalloc(len << 2, GFP_KERNEL); | |
296 | if (!bfad->regdata) { | |
297 | printk(KERN_INFO "bfad[%d]: Failed to allocate regrd buffer\n", | |
298 | bfad->inst_no); | |
299 | return -ENOMEM; | |
300 | } | |
301 | ||
302 | bfad->reglen = len << 2; | |
303 | rb = bfa_ioc_bar0(ioc); | |
304 | addr &= BFA_REG_ADDRMSK(bfa); | |
305 | ||
306 | /* offset and len sanity check */ | |
307 | rc = bfad_reg_offset_check(bfa, addr, len); | |
308 | if (rc) { | |
309 | printk(KERN_INFO "bfad[%d]: Failed reg offset check\n", | |
310 | bfad->inst_no); | |
311 | kfree(bfad->regdata); | |
312 | bfad->regdata = NULL; | |
313 | bfad->reglen = 0; | |
314 | return -EINVAL; | |
315 | } | |
316 | ||
317 | reg_addr = rb + addr; | |
318 | regbuf = (u32 *)bfad->regdata; | |
319 | spin_lock_irqsave(&bfad->bfad_lock, flags); | |
320 | for (i = 0; i < len; i++) { | |
321 | *regbuf = bfa_reg_read(reg_addr); | |
322 | regbuf++; | |
323 | reg_addr += sizeof(u32); | |
324 | } | |
325 | spin_unlock_irqrestore(&bfad->bfad_lock, flags); | |
326 | ||
327 | return nbytes; | |
328 | } | |
329 | ||
330 | static ssize_t | |
331 | bfad_debugfs_write_regwr(struct file *file, const char __user *buf, | |
332 | size_t nbytes, loff_t *ppos) | |
333 | { | |
334 | struct bfad_debug_info *debug = file->private_data; | |
335 | struct bfad_port_s *port = (struct bfad_port_s *)debug->i_private; | |
336 | struct bfad_s *bfad = port->bfad; | |
337 | struct bfa_s *bfa = &bfad->bfa; | |
338 | struct bfa_ioc_s *ioc = &bfa->ioc; | |
339 | int addr, val, rc; | |
340 | void __iomem *reg_addr; | |
341 | unsigned long flags; | |
342 | ||
343 | rc = sscanf(buf, "%x:%x", &addr, &val); | |
344 | if (rc < 2) { | |
345 | printk(KERN_INFO | |
346 | "bfad[%d]: %s failed to read user buf\n", | |
347 | bfad->inst_no, __func__); | |
348 | return -EINVAL; | |
349 | } | |
350 | ||
351 | addr &= BFA_REG_ADDRMSK(bfa); /* offset only 17 bit and word align */ | |
352 | ||
353 | /* offset and len sanity check */ | |
354 | rc = bfad_reg_offset_check(bfa, addr, 1); | |
355 | if (rc) { | |
356 | printk(KERN_INFO | |
357 | "bfad[%d]: Failed reg offset check\n", | |
358 | bfad->inst_no); | |
359 | return -EINVAL; | |
360 | } | |
361 | ||
362 | reg_addr = (uint32_t *) ((uint8_t *) bfa_ioc_bar0(ioc) + addr); | |
363 | spin_lock_irqsave(&bfad->bfad_lock, flags); | |
364 | bfa_reg_write(reg_addr, val); | |
365 | spin_unlock_irqrestore(&bfad->bfad_lock, flags); | |
366 | ||
367 | return nbytes; | |
368 | } | |
369 | ||
370 | static int | |
371 | bfad_debugfs_release(struct inode *inode, struct file *file) | |
372 | { | |
373 | struct bfad_debug_info *debug = file->private_data; | |
374 | ||
375 | if (!debug) | |
376 | return 0; | |
377 | ||
378 | file->private_data = NULL; | |
379 | kfree(debug); | |
380 | return 0; | |
381 | } | |
382 | ||
383 | static int | |
384 | bfad_debugfs_release_fwtrc(struct inode *inode, struct file *file) | |
385 | { | |
386 | struct bfad_debug_info *fw_debug = file->private_data; | |
387 | ||
388 | if (!fw_debug) | |
389 | return 0; | |
390 | ||
391 | if (fw_debug->debug_buffer) | |
392 | vfree(fw_debug->debug_buffer); | |
393 | ||
394 | file->private_data = NULL; | |
395 | kfree(fw_debug); | |
396 | return 0; | |
397 | } | |
398 | ||
399 | static const struct file_operations bfad_debugfs_op_drvtrc = { | |
400 | .owner = THIS_MODULE, | |
401 | .open = bfad_debugfs_open_drvtrc, | |
402 | .llseek = bfad_debugfs_lseek, | |
403 | .read = bfad_debugfs_read, | |
404 | .release = bfad_debugfs_release, | |
405 | }; | |
406 | ||
407 | static const struct file_operations bfad_debugfs_op_fwtrc = { | |
408 | .owner = THIS_MODULE, | |
409 | .open = bfad_debugfs_open_fwtrc, | |
410 | .llseek = bfad_debugfs_lseek, | |
411 | .read = bfad_debugfs_read, | |
412 | .release = bfad_debugfs_release_fwtrc, | |
413 | }; | |
414 | ||
415 | static const struct file_operations bfad_debugfs_op_fwsave = { | |
416 | .owner = THIS_MODULE, | |
417 | .open = bfad_debugfs_open_fwsave, | |
418 | .llseek = bfad_debugfs_lseek, | |
419 | .read = bfad_debugfs_read, | |
420 | .release = bfad_debugfs_release_fwtrc, | |
421 | }; | |
422 | ||
423 | static const struct file_operations bfad_debugfs_op_regrd = { | |
424 | .owner = THIS_MODULE, | |
425 | .open = bfad_debugfs_open_reg, | |
426 | .llseek = bfad_debugfs_lseek, | |
427 | .read = bfad_debugfs_read_regrd, | |
428 | .write = bfad_debugfs_write_regrd, | |
429 | .release = bfad_debugfs_release, | |
430 | }; | |
431 | ||
432 | static const struct file_operations bfad_debugfs_op_regwr = { | |
433 | .owner = THIS_MODULE, | |
434 | .open = bfad_debugfs_open_reg, | |
435 | .llseek = bfad_debugfs_lseek, | |
436 | .write = bfad_debugfs_write_regwr, | |
437 | .release = bfad_debugfs_release, | |
438 | }; | |
439 | ||
440 | struct bfad_debugfs_entry { | |
441 | const char *name; | |
442 | mode_t mode; | |
443 | const struct file_operations *fops; | |
444 | }; | |
445 | ||
446 | static const struct bfad_debugfs_entry bfad_debugfs_files[] = { | |
447 | { "drvtrc", S_IFREG|S_IRUGO, &bfad_debugfs_op_drvtrc, }, | |
448 | { "fwtrc", S_IFREG|S_IRUGO, &bfad_debugfs_op_fwtrc, }, | |
449 | { "fwsave", S_IFREG|S_IRUGO, &bfad_debugfs_op_fwsave, }, | |
450 | { "regrd", S_IFREG|S_IRUGO|S_IWUSR, &bfad_debugfs_op_regrd, }, | |
451 | { "regwr", S_IFREG|S_IWUSR, &bfad_debugfs_op_regwr, }, | |
452 | }; | |
453 | ||
454 | static struct dentry *bfa_debugfs_root; | |
455 | static atomic_t bfa_debugfs_port_count; | |
456 | ||
457 | inline void | |
458 | bfad_debugfs_init(struct bfad_port_s *port) | |
459 | { | |
460 | struct bfad_im_port_s *im_port = port->im_port; | |
461 | struct bfad_s *bfad = im_port->bfad; | |
462 | struct Scsi_Host *shost = im_port->shost; | |
463 | const struct bfad_debugfs_entry *file; | |
464 | char name[16]; | |
465 | int i; | |
466 | ||
467 | if (!bfa_debugfs_enable) | |
468 | return; | |
469 | ||
470 | /* Setup the BFA debugfs root directory*/ | |
471 | if (!bfa_debugfs_root) { | |
472 | bfa_debugfs_root = debugfs_create_dir("bfa", NULL); | |
473 | atomic_set(&bfa_debugfs_port_count, 0); | |
474 | if (!bfa_debugfs_root) { | |
475 | printk(KERN_WARNING | |
476 | "BFA debugfs root dir creation failed\n"); | |
477 | goto err; | |
478 | } | |
479 | } | |
480 | ||
481 | /* | |
482 | * Setup the host# directory for the port, | |
483 | * corresponds to the scsi_host num of this port. | |
484 | */ | |
485 | snprintf(name, sizeof(name), "host%d", shost->host_no); | |
486 | if (!port->port_debugfs_root) { | |
487 | port->port_debugfs_root = | |
488 | debugfs_create_dir(name, bfa_debugfs_root); | |
489 | if (!port->port_debugfs_root) { | |
490 | printk(KERN_WARNING | |
491 | "BFA host root dir creation failed\n"); | |
492 | goto err; | |
493 | } | |
494 | ||
495 | atomic_inc(&bfa_debugfs_port_count); | |
496 | ||
497 | for (i = 0; i < ARRAY_SIZE(bfad_debugfs_files); i++) { | |
498 | file = &bfad_debugfs_files[i]; | |
499 | bfad->bfad_dentry_files[i] = | |
500 | debugfs_create_file(file->name, | |
501 | file->mode, | |
502 | port->port_debugfs_root, | |
503 | port, | |
504 | file->fops); | |
505 | if (!bfad->bfad_dentry_files[i]) { | |
506 | printk(KERN_WARNING | |
507 | "BFA host%d: create %s entry failed\n", | |
508 | shost->host_no, file->name); | |
509 | goto err; | |
510 | } | |
511 | } | |
512 | } | |
513 | ||
514 | err: | |
515 | return; | |
516 | } | |
517 | ||
518 | inline void | |
519 | bfad_debugfs_exit(struct bfad_port_s *port) | |
520 | { | |
521 | struct bfad_im_port_s *im_port = port->im_port; | |
522 | struct bfad_s *bfad = im_port->bfad; | |
523 | int i; | |
524 | ||
525 | for (i = 0; i < ARRAY_SIZE(bfad_debugfs_files); i++) { | |
526 | if (bfad->bfad_dentry_files[i]) { | |
527 | debugfs_remove(bfad->bfad_dentry_files[i]); | |
528 | bfad->bfad_dentry_files[i] = NULL; | |
529 | } | |
530 | } | |
531 | ||
532 | /* | |
533 | * Remove the host# directory for the port, | |
534 | * corresponds to the scsi_host num of this port. | |
535 | */ | |
536 | if (port->port_debugfs_root) { | |
537 | debugfs_remove(port->port_debugfs_root); | |
538 | port->port_debugfs_root = NULL; | |
539 | atomic_dec(&bfa_debugfs_port_count); | |
540 | } | |
541 | ||
542 | /* Remove the BFA debugfs root directory */ | |
543 | if (atomic_read(&bfa_debugfs_port_count) == 0) { | |
544 | debugfs_remove(bfa_debugfs_root); | |
545 | bfa_debugfs_root = NULL; | |
546 | } | |
547 | } |