/******************************************************************* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* 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 }