summaryrefslogtreecommitdiff
path: root/drivers/scsi/lpfc/lpfc_debugfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/lpfc/lpfc_debugfs.c')
-rw-r--r--drivers/scsi/lpfc/lpfc_debugfs.c508
1 files changed, 508 insertions, 0 deletions
diff --git a/drivers/scsi/lpfc/lpfc_debugfs.c b/drivers/scsi/lpfc/lpfc_debugfs.c
new file mode 100644
index 000000000000..673cfe11cc2b
--- /dev/null
+++ b/drivers/scsi/lpfc/lpfc_debugfs.c
@@ -0,0 +1,508 @@
+/*******************************************************************
+ * This file is part of the Emulex Linux Device Driver for *
+ * Fibre Channel Host Bus Adapters. *
+ * Copyright (C) 2007 Emulex. All rights reserved. *
+ * EMULEX and SLI are trademarks of Emulex. *
+ * www.emulex.com *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of version 2 of the GNU General *
+ * Public License as published by the Free Software Foundation. *
+ * This program is distributed in the hope that it will be useful. *
+ * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND *
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE *
+ * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD *
+ * TO BE LEGALLY INVALID. See the GNU General Public License for *
+ * more details, a copy of which can be found in the file COPYING *
+ * included with this package. *
+ *******************************************************************/
+
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/ctype.h>
+#include <linux/version.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_transport_fc.h>
+
+#include "lpfc_hw.h"
+#include "lpfc_sli.h"
+#include "lpfc_disc.h"
+#include "lpfc_scsi.h"
+#include "lpfc.h"
+#include "lpfc_logmsg.h"
+#include "lpfc_crtn.h"
+#include "lpfc_vport.h"
+#include "lpfc_version.h"
+#include "lpfc_vport.h"
+#include "lpfc_debugfs.h"
+
+#ifdef CONFIG_LPFC_DEBUG_FS
+/* debugfs interface
+ *
+ * To access this interface the user should:
+ * # mkdir /debug
+ * # mount -t debugfs none /debug
+ *
+ * The lpfc debugfs directory hierachy is:
+ * lpfc/lpfcX/vportY
+ * where X is the lpfc hba unique_id
+ * where Y is the vport VPI on that hba
+ *
+ * Debugging services available per vport:
+ * discovery_trace
+ * This is an ACSII readable file that contains a trace of the last
+ * lpfc_debugfs_max_disc_trc events that happened on a specific vport.
+ * See lpfc_debugfs.h for different categories of
+ * discovery events. To enable the discovery trace, the following
+ * module parameters must be set:
+ * lpfc_debugfs_enable=1 Turns on lpfc debugfs filesystem support
+ * lpfc_debugfs_max_disc_trc=X Where X is the event trace depth for
+ * EACH vport. X MUST also be a power of 2.
+ * lpfc_debugfs_mask_disc_trc=Y Where Y is an event mask as defined in
+ * lpfc_debugfs.h .
+ */
+static int lpfc_debugfs_enable = 0;
+module_param(lpfc_debugfs_enable, int, 0);
+MODULE_PARM_DESC(lpfc_debugfs_enable, "Enable debugfs services");
+
+static int lpfc_debugfs_max_disc_trc = 0; /* This MUST be a power of 2 */
+module_param(lpfc_debugfs_max_disc_trc, int, 0);
+MODULE_PARM_DESC(lpfc_debugfs_max_disc_trc,
+ "Set debugfs discovery trace depth");
+
+static int lpfc_debugfs_mask_disc_trc = 0;
+module_param(lpfc_debugfs_mask_disc_trc, int, 0);
+MODULE_PARM_DESC(lpfc_debugfs_mask_disc_trc,
+ "Set debugfs discovery trace mask");
+
+#include <linux/debugfs.h>
+
+/* size of discovery_trace output line */
+#define LPFC_DISC_TRC_ENTRY_SIZE 80
+
+/* nodelist output buffer size */
+#define LPFC_NODELIST_SIZE 8192
+#define LPFC_NODELIST_ENTRY_SIZE 120
+
+struct lpfc_debug {
+ char *buffer;
+ int len;
+};
+
+atomic_t lpfc_debugfs_disc_trc_cnt = ATOMIC_INIT(0);
+unsigned long lpfc_debugfs_start_time = 0L;
+
+static int
+lpfc_debugfs_disc_trc_data(struct lpfc_vport *vport, char *buf, int size)
+{
+ int i, index, len, enable;
+ uint32_t ms;
+ struct lpfc_disc_trc *dtp;
+ char buffer[80];
+
+
+ enable = lpfc_debugfs_enable;
+ lpfc_debugfs_enable = 0;
+
+ len = 0;
+ index = (atomic_read(&vport->disc_trc_cnt) + 1) &
+ (lpfc_debugfs_max_disc_trc - 1);
+ for (i = index; i < lpfc_debugfs_max_disc_trc; i++) {
+ dtp = vport->disc_trc + i;
+ if (!dtp->fmt)
+ continue;
+ ms = jiffies_to_msecs(dtp->jif - lpfc_debugfs_start_time);
+ snprintf(buffer, 80, "%010d:%010d ms:%s\n",
+ dtp->seq_cnt, ms, dtp->fmt);
+ len += snprintf(buf+len, size-len, buffer,
+ dtp->data1, dtp->data2, dtp->data3);
+ }
+ for (i = 0; i < index; i++) {
+ dtp = vport->disc_trc + i;
+ if (!dtp->fmt)
+ continue;
+ ms = jiffies_to_msecs(dtp->jif - lpfc_debugfs_start_time);
+ snprintf(buffer, 80, "%010d:%010d ms:%s\n",
+ dtp->seq_cnt, ms, dtp->fmt);
+ len += snprintf(buf+len, size-len, buffer,
+ dtp->data1, dtp->data2, dtp->data3);
+ }
+
+ lpfc_debugfs_enable = enable;
+ return len;
+}
+
+static int
+lpfc_debugfs_nodelist_data(struct lpfc_vport *vport, char *buf, int size)
+{
+ int len = 0;
+ int cnt;
+ struct Scsi_Host *shost = lpfc_shost_from_vport(vport);
+ struct lpfc_nodelist *ndlp;
+ unsigned char *statep, *name;
+
+ cnt = (LPFC_NODELIST_SIZE / LPFC_NODELIST_ENTRY_SIZE);
+
+ spin_lock_irq(shost->host_lock);
+ list_for_each_entry(ndlp, &vport->fc_nodes, nlp_listp) {
+ if (!cnt) {
+ len += snprintf(buf+len, size-len,
+ "Missing Nodelist Entries\n");
+ break;
+ }
+ cnt--;
+ switch (ndlp->nlp_state) {
+ case NLP_STE_UNUSED_NODE:
+ statep = "UNUSED";
+ break;
+ case NLP_STE_PLOGI_ISSUE:
+ statep = "PLOGI ";
+ break;
+ case NLP_STE_ADISC_ISSUE:
+ statep = "ADISC ";
+ break;
+ case NLP_STE_REG_LOGIN_ISSUE:
+ statep = "REGLOG";
+ break;
+ case NLP_STE_PRLI_ISSUE:
+ statep = "PRLI ";
+ break;
+ case NLP_STE_UNMAPPED_NODE:
+ statep = "UNMAP ";
+ break;
+ case NLP_STE_MAPPED_NODE:
+ statep = "MAPPED";
+ break;
+ case NLP_STE_NPR_NODE:
+ statep = "NPR ";
+ break;
+ default:
+ statep = "UNKNOWN";
+ }
+ len += snprintf(buf+len, size-len, "%s DID:x%06x ",
+ statep, ndlp->nlp_DID);
+ name = (unsigned char *)&ndlp->nlp_portname;
+ len += snprintf(buf+len, size-len,
+ "WWPN %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x ",
+ *name, *(name+1), *(name+2), *(name+3),
+ *(name+4), *(name+5), *(name+6), *(name+7));
+ name = (unsigned char *)&ndlp->nlp_nodename;
+ len += snprintf(buf+len, size-len,
+ "WWNN %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x ",
+ *name, *(name+1), *(name+2), *(name+3),
+ *(name+4), *(name+5), *(name+6), *(name+7));
+ len += snprintf(buf+len, size-len, "RPI:%03d flag:x%08x ",
+ ndlp->nlp_rpi, ndlp->nlp_flag);
+ if (!ndlp->nlp_type)
+ len += snprintf(buf+len, size-len, "UNKNOWN_TYPE");
+ if (ndlp->nlp_type & NLP_FC_NODE)
+ len += snprintf(buf+len, size-len, "FC_NODE ");
+ if (ndlp->nlp_type & NLP_FABRIC)
+ len += snprintf(buf+len, size-len, "FABRIC ");
+ if (ndlp->nlp_type & NLP_FCP_TARGET)
+ len += snprintf(buf+len, size-len, "FCP_TGT sid:%d ",
+ ndlp->nlp_sid);
+ if (ndlp->nlp_type & NLP_FCP_INITIATOR)
+ len += snprintf(buf+len, size-len, "FCP_INITIATOR");
+ len += snprintf(buf+len, size-len, "\n");
+ }
+ spin_unlock_irq(shost->host_lock);
+ return len;
+}
+#endif
+
+
+inline void
+lpfc_debugfs_disc_trc(struct lpfc_vport *vport, int mask, char *fmt,
+ uint32_t data1, uint32_t data2, uint32_t data3)
+{
+#ifdef CONFIG_LPFC_DEBUG_FS
+ struct lpfc_disc_trc *dtp;
+ int index;
+
+ if (!(lpfc_debugfs_mask_disc_trc & mask))
+ return;
+
+ if (!lpfc_debugfs_enable || !lpfc_debugfs_max_disc_trc ||
+ !vport || !vport->disc_trc)
+ return;
+
+ index = atomic_inc_return(&vport->disc_trc_cnt) &
+ (lpfc_debugfs_max_disc_trc - 1);
+ dtp = vport->disc_trc + index;
+ dtp->fmt = fmt;
+ dtp->data1 = data1;
+ dtp->data2 = data2;
+ dtp->data3 = data3;
+ dtp->seq_cnt = atomic_inc_return(&lpfc_debugfs_disc_trc_cnt);
+ dtp->jif = jiffies;
+#endif
+ return;
+}
+
+#ifdef CONFIG_LPFC_DEBUG_FS
+static int
+lpfc_debugfs_disc_trc_open(struct inode *inode, struct file *file)
+{
+ struct lpfc_vport *vport = inode->i_private;
+ struct lpfc_debug *debug;
+ int size;
+ int rc = -ENOMEM;
+
+ if (!lpfc_debugfs_max_disc_trc) {
+ rc = -ENOSPC;
+ goto out;
+ }
+
+ debug = kmalloc(sizeof(*debug), GFP_KERNEL);
+ if (!debug)
+ goto out;
+
+ /* Round to page boundry */
+ size = (lpfc_debugfs_max_disc_trc * LPFC_DISC_TRC_ENTRY_SIZE);
+ size = PAGE_ALIGN(size);
+
+ debug->buffer = kmalloc(size, GFP_KERNEL);
+ if (!debug->buffer) {
+ kfree(debug);
+ goto out;
+ }
+
+ debug->len = lpfc_debugfs_disc_trc_data(vport, debug->buffer, size);
+ file->private_data = debug;
+
+ rc = 0;
+out:
+ return rc;
+}
+
+static int
+lpfc_debugfs_nodelist_open(struct inode *inode, struct file *file)
+{
+ struct lpfc_vport *vport = inode->i_private;
+ struct lpfc_debug *debug;
+ int rc = -ENOMEM;
+
+ debug = kmalloc(sizeof(*debug), GFP_KERNEL);
+ if (!debug)
+ goto out;
+
+ /* Round to page boundry */
+ debug->buffer = kmalloc(LPFC_NODELIST_SIZE, GFP_KERNEL);
+ if (!debug->buffer) {
+ kfree(debug);
+ goto out;
+ }
+
+ debug->len = lpfc_debugfs_nodelist_data(vport, debug->buffer,
+ LPFC_NODELIST_SIZE);
+ file->private_data = debug;
+
+ rc = 0;
+out:
+ return rc;
+}
+
+static loff_t
+lpfc_debugfs_lseek(struct file *file, loff_t off, int whence)
+{
+ struct lpfc_debug *debug;
+ loff_t pos = -1;
+
+ debug = file->private_data;
+
+ switch (whence) {
+ case 0:
+ pos = off;
+ break;
+ case 1:
+ pos = file->f_pos + off;
+ break;
+ case 2:
+ pos = debug->len - off;
+ }
+ return (pos < 0 || pos > debug->len) ? -EINVAL : (file->f_pos = pos);
+}
+
+static ssize_t
+lpfc_debugfs_read(struct file *file, char __user *buf,
+ size_t nbytes, loff_t *ppos)
+{
+ struct lpfc_debug *debug = file->private_data;
+ return simple_read_from_buffer(buf, nbytes, ppos, debug->buffer,
+ debug->len);
+}
+
+static int
+lpfc_debugfs_release(struct inode *inode, struct file *file)
+{
+ struct lpfc_debug *debug = file->private_data;
+
+ kfree(debug->buffer);
+ kfree(debug);
+
+ return 0;
+}
+
+#undef lpfc_debugfs_op_disc_trc
+static struct file_operations lpfc_debugfs_op_disc_trc = {
+ .owner = THIS_MODULE,
+ .open = lpfc_debugfs_disc_trc_open,
+ .llseek = lpfc_debugfs_lseek,
+ .read = lpfc_debugfs_read,
+ .release = lpfc_debugfs_release,
+};
+
+#undef lpfc_debugfs_op_nodelist
+static struct file_operations lpfc_debugfs_op_nodelist = {
+ .owner = THIS_MODULE,
+ .open = lpfc_debugfs_nodelist_open,
+ .llseek = lpfc_debugfs_lseek,
+ .read = lpfc_debugfs_read,
+ .release = lpfc_debugfs_release,
+};
+
+static struct dentry *lpfc_debugfs_root = NULL;
+static atomic_t lpfc_debugfs_hba_count;
+#endif
+
+inline void
+lpfc_debugfs_initialize(struct lpfc_vport *vport)
+{
+#ifdef CONFIG_LPFC_DEBUG_FS
+ struct lpfc_hba *phba = vport->phba;
+ char name[64];
+ uint32_t num, i;
+
+ if (!lpfc_debugfs_enable)
+ return;
+
+ if (lpfc_debugfs_max_disc_trc) {
+ num = lpfc_debugfs_max_disc_trc - 1;
+ if (num & lpfc_debugfs_max_disc_trc) {
+ /* Change to be a power of 2 */
+ num = lpfc_debugfs_max_disc_trc;
+ i = 0;
+ while (num > 1) {
+ num = num >> 1;
+ i++;
+ }
+ lpfc_debugfs_max_disc_trc = (1 << i);
+ printk(KERN_ERR
+ "lpfc_debugfs_max_disc_trc changed to %d\n",
+ lpfc_debugfs_max_disc_trc);
+ }
+ }
+
+ if (!lpfc_debugfs_root) {
+ lpfc_debugfs_root = debugfs_create_dir("lpfc", NULL);
+ atomic_set(&lpfc_debugfs_hba_count, 0);
+ if (!lpfc_debugfs_root)
+ goto debug_failed;
+ }
+
+ snprintf(name, sizeof(name), "lpfc%d", phba->brd_no);
+ if (!phba->hba_debugfs_root) {
+ phba->hba_debugfs_root =
+ debugfs_create_dir(name, lpfc_debugfs_root);
+ if (!phba->hba_debugfs_root)
+ goto debug_failed;
+ atomic_inc(&lpfc_debugfs_hba_count);
+ atomic_set(&phba->debugfs_vport_count, 0);
+ }
+
+ snprintf(name, sizeof(name), "vport%d", vport->vpi);
+ if (!vport->vport_debugfs_root) {
+ vport->vport_debugfs_root =
+ debugfs_create_dir(name, phba->hba_debugfs_root);
+ if (!vport->vport_debugfs_root)
+ goto debug_failed;
+ atomic_inc(&phba->debugfs_vport_count);
+ }
+
+ if (!lpfc_debugfs_start_time)
+ lpfc_debugfs_start_time = jiffies;
+
+ vport->disc_trc = kmalloc(
+ (sizeof(struct lpfc_disc_trc) * lpfc_debugfs_max_disc_trc),
+ GFP_KERNEL);
+
+ if (!vport->disc_trc)
+ goto debug_failed;
+ memset(vport->disc_trc, 0,
+ (sizeof(struct lpfc_disc_trc) * lpfc_debugfs_max_disc_trc));
+
+ snprintf(name, sizeof(name), "discovery_trace");
+ vport->debug_disc_trc =
+ debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR,
+ vport->vport_debugfs_root,
+ vport, &lpfc_debugfs_op_disc_trc);
+ if (!vport->debug_disc_trc) {
+ lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+ "%d:0409 Cannot create debugfs",
+ phba->brd_no);
+ goto debug_failed;
+ }
+ snprintf(name, sizeof(name), "nodelist");
+ vport->debug_nodelist =
+ debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR,
+ vport->vport_debugfs_root,
+ vport, &lpfc_debugfs_op_nodelist);
+ if (!vport->debug_nodelist) {
+ lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+ "%d:0409 Cannot create debugfs",
+ phba->brd_no);
+ goto debug_failed;
+ }
+debug_failed:
+ return;
+#endif
+}
+
+
+inline void
+lpfc_debugfs_terminate(struct lpfc_vport *vport)
+{
+#ifdef CONFIG_LPFC_DEBUG_FS
+ struct lpfc_hba *phba = vport->phba;
+
+ if (vport->disc_trc) {
+ kfree(vport->disc_trc);
+ vport->disc_trc = NULL;
+ }
+ if (vport->debug_disc_trc) {
+ debugfs_remove(vport->debug_disc_trc); /* discovery_trace */
+ vport->debug_disc_trc = NULL;
+ }
+ if (vport->debug_nodelist) {
+ debugfs_remove(vport->debug_nodelist); /* nodelist */
+ vport->debug_nodelist = NULL;
+ }
+ if (vport->vport_debugfs_root) {
+ debugfs_remove(vport->vport_debugfs_root); /* vportX */
+ vport->vport_debugfs_root = NULL;
+ atomic_dec(&phba->debugfs_vport_count);
+ }
+ if (atomic_read(&phba->debugfs_vport_count) == 0) {
+ debugfs_remove(vport->phba->hba_debugfs_root); /* lpfcX */
+ vport->phba->hba_debugfs_root = NULL;
+ atomic_dec(&lpfc_debugfs_hba_count);
+ if (atomic_read(&lpfc_debugfs_hba_count) == 0) {
+ debugfs_remove(lpfc_debugfs_root); /* lpfc */
+ lpfc_debugfs_root = NULL;
+ }
+ }
+#endif
+}
+
+