This patch adds debugfs support to dump adapter log, adapter queues,
and fcoe DDP stats.

Signed-off-by: Praveen Madhavan <prave...@chelsio.com>
---
 drivers/net/ethernet/chelsio/cxgb4/t4_regs.h |    1 +
 drivers/scsi/csiostor/Makefile               |    2 +-
 drivers/scsi/csiostor/csio_debugfs.c         | 1100 ++++++++++++++++++++++++++
 3 files changed, 1102 insertions(+), 1 deletion(-)
 create mode 100644 drivers/scsi/csiostor/csio_debugfs.c

diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h 
b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
index af3462d..cb8b161 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
+++ b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
@@ -1442,6 +1442,7 @@
 #define TP_MIB_FCOE_DDP_0_A    0x48
 #define TP_MIB_FCOE_DROP_0_A   0x4c
 #define TP_MIB_FCOE_BYTE_0_HI_A        0x50
+#define TP_MIB_FCOE_BYTE_0_LO_A 0x51
 #define TP_MIB_OFD_VLN_DROP_0_A        0x58
 #define TP_MIB_USM_PKTS_A      0x5c
 #define TP_MIB_RQE_DFR_PKT_A   0x64
diff --git a/drivers/scsi/csiostor/Makefile b/drivers/scsi/csiostor/Makefile
index 3681a3f..7799537 100644
--- a/drivers/scsi/csiostor/Makefile
+++ b/drivers/scsi/csiostor/Makefile
@@ -9,4 +9,4 @@ obj-$(CONFIG_SCSI_CHELSIO_FCOE) += csiostor.o
 
 csiostor-objs := csio_attr.o csio_init.o csio_lnode.o csio_scsi.o \
                csio_hw.o csio_hw_t5.o csio_isr.o \
-               csio_mb.o csio_rnode.o csio_wr.o
+               csio_mb.o csio_rnode.o csio_wr.o csio_debugfs.o
diff --git a/drivers/scsi/csiostor/csio_debugfs.c 
b/drivers/scsi/csiostor/csio_debugfs.c
new file mode 100644
index 0000000..1ed6235
--- /dev/null
+++ b/drivers/scsi/csiostor/csio_debugfs.c
@@ -0,0 +1,1100 @@
+/*
+ * This file is part of the Chelsio FCoE driver for Linux.
+ *
+ * Copyright (c) 2008-2015 Chelsio Communications, Inc. All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses.  You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ *     Redistribution and use in source and binary forms, with or
+ *     without modification, are permitted provided that the following
+ *     conditions are met:
+ *
+ *      - Redistributions of source code must retain the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer.
+ *
+ *      - Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials
+ *        provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/aer.h>
+#include <linux/mm.h>
+#include <linux/notifier.h>
+#include <linux/kdebug.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/export.h>
+#include <linux/delay.h>
+
+#include "t4fw_api.h"
+#include "csio_init.h"
+#include "csio_defs.h"
+#include "csio_hw.h"
+
+static struct dentry *csio_debugfs_root;
+
+#define DEFINE_SIMPLE_DEBUGFS_FILE(name) \
+static int name##_open(struct inode *inode, struct file *file) \
+{ \
+       return single_open(file, name##_show, inode->i_private); \
+} \
+static const struct file_operations name##_fops = { \
+       .owner   = THIS_MODULE, \
+       .open    = name##_open, \
+       .read    = seq_read, \
+       .llseek  = seq_lseek, \
+       .release = single_release \
+}
+
+struct csio_debugfs_entry {
+       const char *name;
+       const struct file_operations *ops;
+       umode_t mode;
+       unsigned char data;
+};
+
+struct seq_tab {
+       int (*show)(struct seq_file *seq, void *v, int idx);
+       unsigned int rows;        /* # of entries */
+       unsigned char width;      /* size in bytes of each entry */
+       unsigned char skip_first; /* whether the first line is a header */
+       char data[0];             /* the table data */
+};
+
+struct tp_fcoe_stats {
+       uint32_t frames_ddp;
+       uint32_t frames_drop;
+       uint64_t octets_ddp;
+};
+
+/*
+ * Information gathered by Device Log Open routine for the display routine.
+ */
+struct devlog_info {
+       unsigned int nentries;          /* number of entries in log[] */
+       unsigned int first;             /* first [temporal] entry in log[] */
+       struct fw_devlog_e log[0];      /* Firmware Device Log */
+};
+
+/*
+ * Firmware Device Log dump.
+ */
+static const char * const devlog_level_strings[] = {
+       [FW_DEVLOG_LEVEL_EMERG]         = "EMERG",
+       [FW_DEVLOG_LEVEL_CRIT]          = "CRIT",
+       [FW_DEVLOG_LEVEL_ERR]           = "ERR",
+       [FW_DEVLOG_LEVEL_NOTICE]        = "NOTICE",
+       [FW_DEVLOG_LEVEL_INFO]          = "INFO",
+       [FW_DEVLOG_LEVEL_DEBUG]         = "DEBUG"
+};
+
+static const char * const devlog_facility_strings[] = {
+       [FW_DEVLOG_FACILITY_CORE]       = "CORE",
+       [FW_DEVLOG_FACILITY_SCHED]      = "SCHED",
+       [FW_DEVLOG_FACILITY_TIMER]      = "TIMER",
+       [FW_DEVLOG_FACILITY_RES]        = "RES",
+       [FW_DEVLOG_FACILITY_HW]         = "HW",
+       [FW_DEVLOG_FACILITY_FLR]        = "FLR",
+       [FW_DEVLOG_FACILITY_DMAQ]       = "DMAQ",
+       [FW_DEVLOG_FACILITY_PHY]        = "PHY",
+       [FW_DEVLOG_FACILITY_MAC]        = "MAC",
+       [FW_DEVLOG_FACILITY_PORT]       = "PORT",
+       [FW_DEVLOG_FACILITY_VI]         = "VI",
+       [FW_DEVLOG_FACILITY_FILTER]     = "FILTER",
+       [FW_DEVLOG_FACILITY_ACL]        = "ACL",
+       [FW_DEVLOG_FACILITY_TM]         = "TM",
+       [FW_DEVLOG_FACILITY_QFC]        = "QFC",
+       [FW_DEVLOG_FACILITY_DCB]        = "DCB",
+       [FW_DEVLOG_FACILITY_ETH]        = "ETH",
+       [FW_DEVLOG_FACILITY_OFLD]       = "OFLD",
+       [FW_DEVLOG_FACILITY_RI]         = "RI",
+       [FW_DEVLOG_FACILITY_ISCSI]      = "ISCSI",
+       [FW_DEVLOG_FACILITY_FCOE]       = "FCOE",
+       [FW_DEVLOG_FACILITY_FOISCSI]    = "FOISCSI",
+       [FW_DEVLOG_FACILITY_FOFCOE]     = "FOFCOE"
+};
+
+/* generic seq_file support for showing a table of size rows x width. */
+static void *seq_tab_get_idx(struct seq_tab *tb, loff_t pos)
+{
+       pos -= tb->skip_first;
+       return pos >= tb->rows ? NULL : &tb->data[pos * tb->width];
+}
+
+static void *seq_tab_start(struct seq_file *seq, loff_t *pos)
+{
+       struct seq_tab *tb = seq->private;
+
+       if (tb->skip_first && *pos == 0)
+               return SEQ_START_TOKEN;
+
+       return seq_tab_get_idx(tb, *pos);
+}
+
+static void *seq_tab_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+       v = seq_tab_get_idx(seq->private, *pos + 1);
+       if (v)
+               ++*pos;
+       return v;
+}
+
+static void seq_tab_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int seq_tab_show(struct seq_file *seq, void *v)
+{
+       const struct seq_tab *tb = seq->private;
+
+       return tb->show(seq, v, ((char *)v - tb->data) / tb->width);
+}
+
+static const struct seq_operations seq_tab_ops = {
+       .start = seq_tab_start,
+       .next  = seq_tab_next,
+       .stop  = seq_tab_stop,
+       .show  = seq_tab_show
+};
+
+static struct seq_tab *seq_open_tab(struct file *f, unsigned int rows,
+                            unsigned int width, unsigned int have_header,
+                            int (*show)(struct seq_file *seq, void *v, int i))
+{
+       struct seq_tab *p;
+
+       p = __seq_open_private(f, &seq_tab_ops, sizeof(*p) + rows * width);
+       if (p) {
+               p->show = show;
+               p->rows = rows;
+               p->width = width;
+               p->skip_first = have_header != 0;
+       }
+       return p;
+}
+
+/*
+ * Trim the size of a seq_tab to the supplied number of rows.  The operation is
+ * irreversible.
+ */
+static int seq_tab_trim(struct seq_tab *p, unsigned int new_rows)
+{
+       if (new_rows > p->rows)
+               return -EINVAL;
+       p->rows = new_rows;
+               return 0;
+}
+
+static inline int csio_hw_wait_op_done(struct csio_hw *hw, int reg,
+               uint32_t mask, int polarity, int attempts, int delay)
+{
+       return csio_hw_wait_op_done_val(hw, reg, mask, polarity, attempts,
+                                       delay, NULL);
+}
+
+/**
+ *     csio_hw_read_indirect - read indirectly addressed registers
+ *     @hw: the adapter
+ *     @addr_reg: register holding the indirect address
+ *     @data_reg: register holding the value of the indirect register
+ *     @vals: where the read register values are stored
+ *     @nregs: how many indirect registers to read
+ *     @start_idx: index of first indirect register to read
+ *
+ *     Reads registers that are accessed indirectly through an address/data
+ *     register pair.
+ */
+static void csio_hw_read_indirect(struct csio_hw *hw, unsigned int addr_reg,
+                            unsigned int data_reg, uint32_t *vals,
+                            unsigned int nregs, unsigned int start_idx)
+{
+       while (nregs--) {
+               csio_wr_reg32(hw, start_idx, addr_reg);
+               *vals++ = csio_rd_reg32(hw, data_reg);
+               start_idx++;
+       }
+}
+
+/**
+ *     csio_hw_mem_read - read EDC 0, EDC 1 or MC into buffer
+ *     @hw: the HW module
+ *     @mtype: memory type: MEM_EDC0, MEM_EDC1 or MEM_MC
+ *     @addr: address within indicated memory type
+ *     @len: amount of memory to read
+ *     @buf: host memory buffer
+ *
+ *     Reads an [almost] arbitrary memory region in the firmware: the
+ *     firmware memory address, length and host buffer must be aligned on
+ *     32-bit boudaries.  The memory is returned as a raw byte sequence from
+ *     the firmware's memory.  If this memory contains data structures which
+ *     contain multi-byte integers, it's the callers responsibility to
+ *     perform appropriate byte order conversions.
+ */
+static int
+csio_hw_mem_read(struct csio_hw *hw, int mtype, uint32_t addr, uint32_t len,
+               uint32_t *buf)
+{
+       uint32_t pos, start, end, offset;
+       int ret;
+
+       /*
+        * Argument sanity checks ...
+        */
+       if ((addr & 0x3) || (len & 0x3))
+               return -EINVAL;
+
+       /*
+        * The underlaying EDC/MC read routines read 64 bytes at a time so we
+        * need to round down the start and round up the end.  We'll start
+        * copying out of the first line at (addr - start) a word at a time.
+        */
+       start = addr & ~(64-1);
+       end = (addr + len + 64-1) & ~(64-1);
+       offset = (addr - start)/sizeof(__be32);
+
+       for (pos = start; pos < end; pos += 64, offset = 0) {
+               uint32_t data[16];
+
+               /*
+                * Read the chip's memory block and bail if there's an error.
+                */
+               if (mtype == MEM_MC) {
+                       ret = hw->chip_ops->chip_mc_read(hw, 0, pos,
+                                               (__be32 *)data, NULL);
+               } else {
+                       ret = hw->chip_ops->chip_edc_read(hw, mtype, pos,
+                                               (__be32 *)data, NULL);
+               }
+
+               if (ret)
+                       return ret;
+
+               /*
+                * Copy the data into the caller's memory buffer.
+                */
+               while (offset < 16 && len > 0) {
+                       *buf++ = data[offset++];
+                       len -= sizeof(uint32_t);
+               }
+       }
+
+       return 0;
+}
+
+/**
+ *     csio_hw_read_cimq_cfg - read CIM queue configuration
+ *     @hw: the adapter
+ *     @base: holds the queue base addresses in bytes
+ *     @size: holds the queue sizes in bytes
+ *     @thres: holds the queue full thresholds in bytes
+ *
+ *     Returns the current configuration of the CIM queues, starting with
+ *     the IBQs, then the OBQs.
+ */
+static void csio_hw_read_cimq_cfg(struct csio_hw *hw, uint16_t *base,
+                               uint16_t *size, uint16_t *thres)
+{
+       unsigned int i, v;
+       int cim_num_obq = CIM_NUM_OBQ_T5;
+
+       for (i = 0; i < CIM_NUM_IBQ; i++) {
+               csio_wr_reg32(hw, IBQSELECT_F | QUENUMSELECT_V(i),
+                               CIM_QUEUE_CONFIG_REF_A);
+               v = csio_rd_reg32(hw, CIM_QUEUE_CONFIG_CTRL_A);
+               /* value is in 256-byte units */
+               *base++ = CIMQBASE_G(v) * 256;
+               *size++ = CIMQSIZE_G(v) * 256;
+               *thres++ = QUEFULLTHRSH_G(v) * 8; /* 8-byte unit */
+       }
+       for (i = 0; i < cim_num_obq; i++) {
+               csio_wr_reg32(hw, OBQSELECT_F | QUENUMSELECT_V(i),
+                               CIM_QUEUE_CONFIG_REF_A);
+               v = csio_rd_reg32(hw, CIM_QUEUE_CONFIG_CTRL_A);
+               /* value is in 256-byte units */
+               *base++ = CIMQBASE_G(v) * 256;
+               *size++ = CIMQSIZE_G(v) * 256;
+       }
+}
+
+/**
+ *     csio_hw_read_cim_ibq - read the contents of a CIM inbound queue
+ *     @hw: the adapter
+ *     @qid: the queue index
+ *     @data: where to store the queue contents
+ *     @n: capacity of @data in 32-bit words
+ *
+ *     Reads the contents of the selected CIM queue starting at address 0 up
+ *     to the capacity of @data.  @n must be a multiple of 4.  Returns < 0 on
+ *     error and the number of 32-bit words actually read on success.
+ */
+static int csio_hw_read_cim_ibq(struct csio_hw *hw, unsigned int qid,
+                               uint32_t *data, size_t n)
+{
+       int i, err, attempts;
+       unsigned int addr;
+       const unsigned int nwords = CIM_IBQ_SIZE * 4;
+
+       if (qid > 5 || (n & 3))
+               return -EINVAL;
+
+       addr = qid * nwords;
+       if (n > nwords)
+               n = nwords;
+
+       /* It might take 3-10ms before the IBQ debug read access is allowed.
+        * Wait for 1 Sec with a delay of 1 usec.
+        */
+       attempts = 1000000;
+
+       for (i = 0; i < n; i++, addr++) {
+               csio_wr_reg32(hw, IBQDBGADDR_V(addr) | IBQDBGEN_F,
+                               CIM_IBQ_DBG_CFG_A);
+               err = csio_hw_wait_op_done(hw, CIM_IBQ_DBG_CFG_A, IBQDBGBUSY_F,
+                                       0, attempts, 1);
+               if (err)
+                       return err;
+               *data++ = csio_rd_reg32(hw, CIM_IBQ_DBG_DATA_A);
+       }
+       csio_wr_reg32(hw, 0, CIM_IBQ_DBG_CFG_A);
+       return i;
+}
+
+/**
+ *     csio_hw_read_cim_obq - read the contents of a CIM outbound queue
+ *     @hw: the adapter
+ *     @qid: the queue index
+ *     @data: where to store the queue contents
+ *     @n: capacity of @data in 32-bit words
+ *
+ *     Reads the contents of the selected CIM queue starting at address 0 up
+ *     to the capacity of @data.  @n must be a multiple of 4.  Returns < 0 on
+ *     error and the number of 32-bit words actually read on success.
+ */
+static int csio_hw_read_cim_obq(struct csio_hw *hw, unsigned int qid,
+                               uint32_t *data, size_t n)
+{
+       int i, err;
+       unsigned int addr, v, nwords;
+       int cim_num_obq = CIM_NUM_OBQ_T5;
+
+       if ((qid > (cim_num_obq - 1)) || (n & 3))
+               return -EINVAL;
+
+       csio_wr_reg32(hw, OBQSELECT_F | QUENUMSELECT_V(qid),
+                       CIM_QUEUE_CONFIG_REF_A);
+       v = csio_rd_reg32(hw, CIM_QUEUE_CONFIG_CTRL_A);
+
+       addr = CIMQBASE_G(v) * 64;    /* muliple of 256 -> muliple of 4 */
+       nwords = CIMQSIZE_G(v) * 64;  /* same */
+       if (n > nwords)
+               n = nwords;
+
+       for (i = 0; i < n; i++, addr++) {
+               csio_wr_reg32(hw, OBQDBGADDR_V(addr) | OBQDBGEN_F,
+                               CIM_OBQ_DBG_CFG_A);
+               err = csio_hw_wait_op_done(hw, CIM_OBQ_DBG_CFG_A, OBQDBGBUSY_F,
+                                       0, 2, 1);
+               if (err)
+                       return err;
+               *data++ = csio_rd_reg32(hw, CIM_OBQ_DBG_DATA_A);
+       }
+       csio_wr_reg32(hw, 0, CIM_OBQ_DBG_CFG_A);
+       return i;
+}
+
+/**
+ *     csio_hw_cim_read - read a block from CIM internal address space
+ *     @hw: the adapter
+ *     @addr: the start address within the CIM address space
+ *     @n: number of words to read
+ *     @valp: where to store the result
+ *
+ *     Reads a block of 4-byte words from the CIM intenal address space.
+ */
+static int csio_hw_cim_read(struct csio_hw *hw, unsigned int addr,
+                       unsigned int n, unsigned int *valp)
+{
+       int ret = 0;
+
+       if (csio_rd_reg32(hw, CIM_HOST_ACC_CTRL_A) & HOSTBUSY_F)
+               return -EBUSY;
+
+       for ( ; !ret && n--; addr += 4) {
+               csio_wr_reg32(hw, addr, CIM_HOST_ACC_CTRL_A);
+               ret = csio_hw_wait_op_done(hw, CIM_HOST_ACC_CTRL_A, HOSTBUSY_F,
+                                       0, 5, 2);
+
+               if (!ret)
+                       *valp++ = csio_rd_reg32(hw, CIM_HOST_ACC_DATA_A);
+       }
+
+       return ret;
+}
+
+/**
+ *     csio_hw_cim_write - write a block into CIM internal address space
+ *     @hw: the adapter
+ *     @addr: the start address within the CIM address space
+ *     @n: number of words to write
+ *     @valp: set of values to write
+ *
+ *     Writes a block of 4-byte words into the CIM intenal address space.
+ */
+static int csio_hw_cim_write(struct csio_hw *hw, unsigned int addr,
+                       unsigned int n, const unsigned int *valp)
+{
+       int ret = 0;
+
+       if (csio_rd_reg32(hw, CIM_HOST_ACC_CTRL_A) & HOSTBUSY_F)
+               return -EBUSY;
+
+       for ( ; !ret && n--; addr += 4) {
+               csio_wr_reg32(hw, *valp++, CIM_HOST_ACC_DATA_A);
+               csio_wr_reg32(hw, addr | HOSTWRITE_F, CIM_HOST_ACC_CTRL_A);
+               ret = csio_hw_wait_op_done(hw, CIM_HOST_ACC_CTRL_A, HOSTBUSY_F,
+                                     0, 5, 2);
+       }
+       return ret;
+}
+
+static int csio_hw_cim_write1(struct csio_hw *hw, unsigned int addr,
+                        unsigned int val)
+{
+       return csio_hw_cim_write(hw, addr, 1, &val);
+}
+
+/**
+ *     csio_hw_cim_read_la - read CIM LA capture buffer
+ *     @hw: the adapter
+ *     @la_buf: where to store the LA data
+ *     @wrptr: the HW write pointer within the capture buffer
+ *
+ *     Reads the contents of the CIM LA buffer with the most recent entry at
+ *     the end of the returned data and with the entry at @wrptr first.
+ *     We try to leave the LA in the running state we find it in.
+ */
+static int csio_hw_cim_read_la(struct csio_hw *hw, uint32_t *la_buf,
+                       unsigned int *wrptr)
+{
+       int i, ret;
+       unsigned int cfg, val, idx;
+
+       ret = csio_hw_cim_read(hw, UP_UP_DBG_LA_CFG_A, 1, &cfg);
+       if (ret)
+               return ret;
+
+       if (cfg & UPDBGLAEN_F) {        /* LA is running, freeze it */
+               ret = csio_hw_cim_write1(hw, UP_UP_DBG_LA_CFG_A, 0);
+               if (ret)
+                       return ret;
+       }
+
+       ret = csio_hw_cim_read(hw, UP_UP_DBG_LA_CFG_A, 1, &val);
+       if (ret)
+               goto restart;
+
+       idx = UPDBGLAWRPTR_G(val);
+       if (wrptr)
+               *wrptr = idx;
+
+       for (i = 0; i < hw->params.cim_la_size; i++) {
+               ret = csio_hw_cim_write1(hw, UP_UP_DBG_LA_CFG_A,
+                                   UPDBGLARDPTR_V(idx) | UPDBGLARDEN_F);
+               if (ret)
+                       break;
+               ret = csio_hw_cim_read(hw, UP_UP_DBG_LA_CFG_A, 1, &val);
+               if (ret)
+                       break;
+               if (val & UPDBGLARDEN_F) {
+                       ret = -ETIMEDOUT;
+                       break;
+               }
+               ret = csio_hw_cim_read(hw, UP_UP_DBG_LA_DATA_A, 1, &la_buf[i]);
+               if (ret)
+                       break;
+               idx = (idx + 1) & UPDBGLARDPTR_M;
+       }
+restart:
+       if (cfg & UPDBGLAEN_F) {
+               int r = csio_hw_cim_write1(hw, UP_UP_DBG_LA_CFG_A,
+                                     cfg & ~UPDBGLARDEN_F);
+               if (!ret)
+                       ret = r;
+       }
+       return ret;
+}
+
+static int cim_la_show(struct seq_file *seq, void *v, int idx)
+{
+       if (v == SEQ_START_TOKEN)
+               seq_puts(seq, "Status   Data      PC     LS0Stat  LS0Addr "
+                       "            LS0Data\n");
+       else {
+               const uint32_t *p = v;
+
+               seq_printf(seq,
+                       "  %02x  %x%07x %x%07x  %08x %08x %08x%08x%08x%08x\n",
+                       (p[0] >> 4) & 0xff, p[0] & 0xf, p[1] >> 4,
+                       p[1] & 0xf, p[2] >> 4, p[2] & 0xf, p[3], p[4], p[5],
+                       p[6], p[7]);
+       }
+       return 0;
+}
+
+static int cim_la_show_3in1(struct seq_file *seq, void *v, int idx)
+{
+       if (v == SEQ_START_TOKEN) {
+               seq_puts(seq, "Status   Data      PC\n");
+       } else {
+               const uint32_t *p = v;
+
+               seq_printf(seq, "  %02x   %08x %08x\n", p[5] & 0xff, p[6],
+                       p[7]);
+               seq_printf(seq, "  %02x   %02x%06x %02x%06x\n",
+                       (p[3] >> 8) & 0xff, p[3] & 0xff, p[4] >> 8,
+                       p[4] & 0xff, p[5] >> 8);
+               seq_printf(seq, "  %02x   %x%07x %x%07x\n", (p[0] >> 4) & 0xff,
+                       p[0] & 0xf, p[1] >> 4, p[1] & 0xf, p[2] >> 4);
+       }
+       return 0;
+}
+
+static int cim_la_open(struct inode *inode, struct file *file)
+{
+       int ret;
+       unsigned int cfg;
+       struct seq_tab *p;
+       struct csio_hw *hw = inode->i_private;
+
+       ret = csio_hw_cim_read(hw, UP_UP_DBG_LA_CFG_A, 1, &cfg);
+       if (ret)
+               return ret;
+
+       p = seq_open_tab(file, hw->params.cim_la_size / 8, 8 * sizeof(uint32_t),
+               1, cfg & UPDBGLACAPTPCONLY_F ? cim_la_show_3in1 : cim_la_show);
+       if (!p)
+               return -ENOMEM;
+
+       ret = csio_hw_cim_read_la(hw, (uint32_t *)p->data, NULL);
+       if (ret)
+               seq_release_private(inode, file);
+       return ret;
+}
+
+static const struct file_operations cim_la_fops = {
+       .owner   = THIS_MODULE,
+       .open    = cim_la_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release_private
+};
+
+static int cim_qcfg_show(struct seq_file *seq, void *v)
+{
+       static const char * const qname[] = {
+               "TP0", "TP1", "ULP", "SGE0", "SGE1", "NC-SI",
+               "ULP0", "ULP1", "ULP2", "ULP3", "SGE", "NC-SI",
+               "SGE0-RX", "SGE1-RX"
+       };
+
+       int i;
+       struct csio_hw *hw = seq->private;
+       uint16_t base[CIM_NUM_IBQ + CIM_NUM_OBQ_T5];
+       uint16_t size[CIM_NUM_IBQ + CIM_NUM_OBQ_T5];
+       uint32_t stat[(4 * (CIM_NUM_IBQ + CIM_NUM_OBQ_T5))];
+       uint16_t thres[CIM_NUM_IBQ];
+       uint32_t obq_wr_t5[2 * CIM_NUM_OBQ_T5], *wr;
+       uint32_t *p = stat;
+       int cim_num_obq = CIM_NUM_OBQ_T5;
+
+       i = csio_hw_cim_read(hw, UP_IBQ_0_SHADOW_RDADDR_A, ARRAY_SIZE(stat),
+                               stat);
+       if (!i) {
+               i = csio_hw_cim_read(hw, UP_OBQ_0_SHADOW_REALADDR_A,
+                               ARRAY_SIZE(obq_wr_t5), obq_wr_t5);
+               wr = obq_wr_t5;
+       }
+       if (i)
+               return i;
+
+       csio_hw_read_cimq_cfg(hw, base, size, thres);
+
+       seq_printf(seq,
+                  "  Queue  Base  Size Thres  RdPtr WrPtr  SOP  EOP Avail\n");
+       for (i = 0; i < CIM_NUM_IBQ; i++, p += 4)
+               seq_printf(seq, "%7s %5x %5u %5u %6x  %4x %4u %4u %5u\n",
+                          qname[i], base[i], size[i], thres[i],
+                          IBQRDADDR_G(p[0]), IBQWRADDR_G(p[1]),
+                          QUESOPCNT_G(p[3]), QUEEOPCNT_G(p[3]),
+                          QUEREMFLITS_G(p[2]) * 16);
+       for ( ; i < CIM_NUM_IBQ + cim_num_obq; i++, p += 4, wr += 2)
+               seq_printf(seq, "%7s %5x %5u %12x  %4x %4u %4u %5u\n",
+                          qname[i], base[i], size[i],
+                          QUERDADDR_G(p[0]) & 0x3fff, wr[0] - base[i],
+                          QUESOPCNT_G(p[3]), QUEEOPCNT_G(p[3]),
+                          QUEREMFLITS_G(p[2]) * 16);
+       return 0;
+}
+
+DEFINE_SIMPLE_DEBUGFS_FILE(cim_qcfg);
+
+static int cimq_show(struct seq_file *seq, void *v, int idx)
+{
+       const uint32_t *p = v;
+
+       seq_printf(seq, "%#06x: %08x %08x %08x %08x\n", idx * 16, p[0], p[1],
+                  p[2], p[3]);
+       return 0;
+}
+
+static int cim_ibq_open(struct inode *inode, struct file *file)
+{
+       int ret;
+       struct seq_tab *p;
+       unsigned int qid = (uintptr_t)inode->i_private & 7;
+       struct csio_hw *hw = inode->i_private - qid;
+
+       p = seq_open_tab(file, CIM_IBQ_SIZE, 4 * sizeof(uint32_t),
+                       0, cimq_show);
+       if (!p)
+               return -ENOMEM;
+
+       ret = csio_hw_read_cim_ibq(hw, qid, (uint32_t *)p->data,
+                       CIM_IBQ_SIZE * 4);
+       if (ret < 0)
+               seq_release_private(inode, file);
+       else
+               ret = 0;
+       return ret;
+}
+
+static const struct file_operations cim_ibq_fops = {
+       .owner   = THIS_MODULE,
+       .open    = cim_ibq_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release_private
+};
+
+static int cim_obq_open(struct inode *inode, struct file *file)
+{
+       int ret;
+       struct seq_tab *p;
+       unsigned int qid = (uintptr_t)inode->i_private & 7;
+       struct csio_hw *hw = inode->i_private - qid;
+
+       p = seq_open_tab(file, 6 * CIM_OBQ_SIZE, 4 * sizeof(uint32_t),
+                       0, cimq_show);
+       if (!p)
+               return -ENOMEM;
+
+       ret = csio_hw_read_cim_obq(hw, qid, (uint32_t *)p->data,
+                       6 * CIM_OBQ_SIZE * 4);
+       if (ret < 0) {
+               seq_release_private(inode, file);
+       } else {
+               seq_tab_trim(p, ret / 4);
+               ret = 0;
+       }
+       return ret;
+}
+
+static const struct file_operations cim_obq_fops = {
+       .owner   = THIS_MODULE,
+       .open    = cim_obq_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release_private
+};
+
+/**
+ *      csio_hw_get_fcoe_stats - read TP's FCoE MIB counters for a port
+ *      @adap: the adapter
+ *      @idx: the port index
+ *      @st: holds the counter values
+ *
+ *      Returns the values of TP's FCoE counters for the selected port.
+ */
+static void csio_hw_get_fcoe_stats(struct csio_hw *hw, unsigned int idx,
+                               struct tp_fcoe_stats *st)
+{
+       uint32_t val[2];
+
+       csio_hw_read_indirect(hw, TP_MIB_INDEX_A, TP_MIB_DATA_A,
+                       &st->frames_ddp, 1, TP_MIB_FCOE_DDP_0_A + idx);
+       csio_hw_read_indirect(hw, TP_MIB_INDEX_A, TP_MIB_DATA_A,
+                       &st->frames_drop, 1, TP_MIB_FCOE_DROP_0_A + idx);
+       csio_hw_read_indirect(hw, TP_MIB_INDEX_A, TP_MIB_DATA_A, val,
+                       2, TP_MIB_FCOE_BYTE_0_HI_A + 2 * idx);
+       st->octets_ddp = ((uint64_t)val[0] << 32) | val[1];
+}
+
+static int fcoe_stats_show(struct seq_file *seq, void *v)
+{
+       struct tp_fcoe_stats stats[4];
+       struct csio_hw *hw = seq->private;
+
+       spin_lock(&hw->stats_lock);
+       csio_hw_get_fcoe_stats(hw, 0, &stats[0]);
+       csio_hw_get_fcoe_stats(hw, 1, &stats[1]);
+       csio_hw_get_fcoe_stats(hw, 2, &stats[2]);
+       csio_hw_get_fcoe_stats(hw, 3, &stats[3]);
+       spin_unlock(&hw->stats_lock);
+
+       seq_puts(seq, "                   channel 0        "
+               "channel 1        channel 2        channel 3\n");
+       seq_printf(seq, "octetsDDP:  %16llu %16llu %16llu %16llu\n",
+                       stats[0].octets_ddp, stats[1].octets_ddp,
+                       stats[2].octets_ddp, stats[3].octets_ddp);
+       seq_printf(seq, "framesDDP:  %16u %16u %16u %16u\n",
+                       stats[0].frames_ddp, stats[1].frames_ddp,
+                       stats[2].frames_ddp, stats[3].frames_ddp);
+       seq_printf(seq, "framesDrop: %16u %16u %16u %16u\n",
+                       stats[0].frames_drop, stats[1].frames_drop,
+                       stats[2].frames_drop, stats[3].frames_drop);
+       return 0;
+}
+
+DEFINE_SIMPLE_DEBUGFS_FILE(fcoe_stats);
+
+/*
+ * Sequential File Operations for Device Log.
+ */
+static inline void *devlog_get_idx(struct devlog_info *dinfo, loff_t pos)
+{
+       if (pos > dinfo->nentries)
+               return NULL;
+
+       return (void *)(uintptr_t)(pos + 1);
+}
+
+static void *devlog_start(struct seq_file *seq, loff_t *pos)
+{
+       struct devlog_info *dinfo = seq->private;
+
+       return (*pos
+               ? devlog_get_idx(dinfo, *pos)
+               : SEQ_START_TOKEN);
+}
+
+static void *devlog_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+       struct devlog_info *dinfo = seq->private;
+
+       (*pos)++;
+       return devlog_get_idx(dinfo, *pos);
+}
+
+static void devlog_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int devlog_show(struct seq_file *seq, void *v)
+{
+       if (v == SEQ_START_TOKEN)
+               seq_printf(seq, "%10s  %15s  %8s  %8s  %s\n",
+                          "Seq#", "Tstamp", "Level", "Facility", "Message");
+       else {
+               struct devlog_info *dinfo = seq->private;
+               int fidx = (uintptr_t)v - 2;
+               unsigned long index;
+               struct fw_devlog_e *e;
+
+               /*
+                * Get a pointer to the log entry to display.  Skip unused log
+                * entries.
+                */
+               index = dinfo->first + fidx;
+               if (index >= dinfo->nentries)
+                       index -= dinfo->nentries;
+               e = &dinfo->log[index];
+               if (e->timestamp == 0)
+                       return 0;
+
+               /*
+                * Print the message.  This depends on the firmware using
+                * exactly the same formating strings as the kernel so we may
+                * eventually have to put a format interpreter in here ...
+                */
+               seq_printf(seq, "%10d  %15llu  %8s  %8s  ",
+                          e->seqno, e->timestamp,
+                          (e->level < ARRAY_SIZE(devlog_level_strings)
+                           ? devlog_level_strings[e->level]
+                           : "UNKNOWN"),
+                          (e->facility < ARRAY_SIZE(devlog_facility_strings)
+                           ? devlog_facility_strings[e->facility]
+                           : "UNKNOWN"));
+               seq_printf(seq, e->fmt, e->params[0], e->params[1],
+                          e->params[2], e->params[3], e->params[4],
+                          e->params[5], e->params[6], e->params[7]);
+       }
+
+       return 0;
+}
+
+static const struct seq_operations devlog_seq_ops = {
+       .start = devlog_start,
+       .next  = devlog_next,
+       .stop  = devlog_stop,
+       .show  = devlog_show
+};
+
+/*
+ * Set up for reading the firmware's device log.  We read the entire log here
+ * and then display it incrementally in devlog_show().
+ */
+static int devlog_open(struct inode *inode, struct file *file)
+{
+       struct csio_hw *hw = inode->i_private;
+       struct devlog_params *dparams = &hw->devlog;
+       struct devlog_info *dinfo;
+       unsigned int index;
+       u32 fseqno;
+       int ret;
+
+       /*
+        * If we don't know where the log is we can't do anything.
+        */
+       if (dparams->start == 0)
+               return -ENXIO;
+
+       /*
+        * Allocate the space to read in the firmware's device log and set up
+        * for the iterated call to our display function.
+        */
+       dinfo = __seq_open_private(file, &devlog_seq_ops,
+                                  sizeof(*dinfo) + dparams->size);
+       if (dinfo == NULL)
+               return -ENOMEM;
+
+       /*
+        * Record the basic log buffer information and read in the raw log.
+        */
+       dinfo->nentries = (dparams->size / sizeof(struct fw_devlog_e));
+       dinfo->first = 0;
+       ret = csio_hw_mem_read(hw, dparams->memtype, dparams->start,
+                         dparams->size, (unsigned int *)dinfo->log);
+       if (ret < 0) {
+               seq_release_private(inode, file);
+               return ret;
+       }
+
+       /*
+        * Translate log multi-byte integral elements into host native format
+        * and determine where the first entry in the log is.
+        */
+       for (fseqno = ~((u32)0), index = 0; index < dinfo->nentries; index++) {
+               struct fw_devlog_e *e = &dinfo->log[index];
+               int i;
+               __u32 seqno;
+
+               if (e->timestamp == 0)
+                       continue;
+
+               e->timestamp = (__force __be64)be64_to_cpu(e->timestamp);
+               seqno = be32_to_cpu(e->seqno);
+               for (i = 0; i < 8; i++)
+                       e->params[i] =
+                               (__force __be32)be32_to_cpu(e->params[i]);
+
+               if (seqno < fseqno) {
+                       fseqno = seqno;
+                       dinfo->first = index;
+               }
+       }
+
+       return 0;
+}
+
+static const struct file_operations devlog_fops = {
+       .owner   = THIS_MODULE,
+       .open    = devlog_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release_private
+};
+
+static ssize_t
+csio_mem_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+       loff_t pos = *ppos;
+       loff_t avail = file_inode(file)->i_size;
+       unsigned int mem = (uintptr_t)file->private_data & 3;
+       struct csio_hw *hw = file->private_data - mem;
+
+       if (pos < 0)
+               return -EINVAL;
+       if (pos >= avail)
+               return 0;
+       if (count > avail - pos)
+               count = avail - pos;
+
+       while (count) {
+               size_t len;
+               int ret, ofst;
+               __be32 data[16];
+
+               if (mem == MEM_MC)
+                       ret = hw->chip_ops->chip_mc_read(hw, 0, pos,
+                                                        data, NULL);
+               else
+                       ret = hw->chip_ops->chip_edc_read(hw, mem, pos,
+                                                         data, NULL);
+               if (ret)
+                       return ret;
+
+               ofst = pos % sizeof(data);
+               len = min(count, sizeof(data) - ofst);
+               if (copy_to_user(buf, (u8 *)data + ofst, len))
+                       return -EFAULT;
+
+               buf += len;
+               pos += len;
+               count -= len;
+       }
+       count = pos - *ppos;
+       *ppos = pos;
+       return count;
+}
+
+static const struct file_operations csio_mem_debugfs_fops = {
+       .owner   = THIS_MODULE,
+       .open    = simple_open,
+       .read    = csio_mem_read,
+       .llseek  = default_llseek,
+};
+
+static void
+csio_crt_debugfs_file(struct csio_hw *hw, struct csio_debugfs_entry entry)
+{
+       debugfs_create_file_size(entry.name, entry.mode, hw->debugfs_root,
+                       (void *)hw + entry.data, entry.ops, (5 << 20));
+}
+
+static void
+csio_add_debugfs_files(struct csio_hw *hw)
+{
+       int i;
+
+       static struct csio_debugfs_entry csio_debugfs_files[] = {
+               { "cim_la", &cim_la_fops, S_IRUSR, 0 },
+               { "cim_qcfg", &cim_qcfg_fops, S_IRUSR, 0 },
+               { "devlog", &devlog_fops, S_IRUSR, 0 },
+               { "ibq_tp0",  &cim_ibq_fops, S_IRUSR, 0 },
+               { "ibq_tp1",  &cim_ibq_fops, S_IRUSR, 1 },
+               { "ibq_ulp",  &cim_ibq_fops, S_IRUSR, 2 },
+               { "ibq_sge0", &cim_ibq_fops, S_IRUSR, 3 },
+               { "ibq_sge1", &cim_ibq_fops, S_IRUSR, 4 },
+               { "ibq_ncsi", &cim_ibq_fops, S_IRUSR, 5 },
+               { "obq_ulp0", &cim_obq_fops, S_IRUSR, 0 },
+               { "obq_ulp1", &cim_obq_fops, S_IRUSR, 1 },
+               { "obq_ulp2", &cim_obq_fops, S_IRUSR, 2 },
+               { "obq_ulp3", &cim_obq_fops, S_IRUSR, 3 },
+               { "obq_sge",  &cim_obq_fops, S_IRUSR, 4 },
+               { "obq_ncsi", &cim_obq_fops, S_IRUSR, 5 },
+               { "obq_sge_rx_q0", &cim_obq_fops, S_IRUSR, 6 },
+               { "obq_sge_rx_q1", &cim_obq_fops, S_IRUSR, 7 },
+               { "fcoe_stats", &fcoe_stats_fops, S_IRUSR, 0 },
+       };
+
+       for (i = 0; i < ARRAY_SIZE(csio_debugfs_files); i++)
+               csio_crt_debugfs_file(hw, csio_debugfs_files[i]);
+}
+
+void csio_add_debugfs_mem(struct csio_hw *hw, const char *name,
+                                unsigned int idx, unsigned int size_mb)
+{
+       debugfs_create_file_size(name, S_IRUSR, hw->debugfs_root,
+                                (void *)hw + idx, &csio_mem_debugfs_fops,
+                                size_mb << 20);
+}
+
+static int csio_setup_debugfs(struct csio_hw *hw)
+{
+       int i;
+
+       if (IS_ERR_OR_NULL(hw->debugfs_root))
+               return -1;
+
+       /* debugfs support is best effort */
+       csio_add_debugfs_files(hw);
+
+       i = csio_rd_reg32(hw, MA_TARGET_MEM_ENABLE_A);
+       if (i & EDRAM0_ENABLE_F)
+               csio_add_debugfs_mem(hw, "edc0", MEM_EDC0, 5);
+       if (i & EDRAM1_ENABLE_F)
+               csio_add_debugfs_mem(hw, "edc1", MEM_EDC1, 5);
+
+       hw->chip_ops->chip_dfs_create_ext_mem(hw);
+       return 0;
+}
+
+/*
+ * csio_dfs_create - Creates and sets up per-hw debugfs.
+ *
+ */
+int
+csio_dfs_create(struct csio_hw *hw)
+{
+       if (csio_debugfs_root) {
+               hw->debugfs_root = debugfs_create_dir(pci_name(hw->pdev),
+                                                       csio_debugfs_root);
+               csio_setup_debugfs(hw);
+       }
+
+       return 0;
+}
+
+/*
+ * csio_dfs_destroy - Destroys per-hw debugfs.
+ */
+int
+csio_dfs_destroy(struct csio_hw *hw)
+{
+       debugfs_remove_recursive(hw->debugfs_root);
+
+       return 0;
+}
+
+/*
+ * csio_dfs_init - Debug filesystem initialization for the module.
+ *
+ */
+int
+csio_dfs_init(void)
+{
+       csio_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+       if (!csio_debugfs_root)
+               pr_warn("Could not create debugfs entry, continuing\n");
+
+       return 0;
+}
+
+/*
+ * csio_dfs_exit - debugfs cleanup for the module.
+ */
+void
+csio_dfs_exit(void)
+{
+       debugfs_remove(csio_debugfs_root);
+}
-- 
2.0.2

--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to