root/drivers/infiniband/hw/hfi1/debugfs.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright(c) 2015-2018 Intel Corporation.
 */

#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ratelimit.h>
#include <linux/fault-inject.h>

#include "hfi.h"
#include "trace.h"
#include "debugfs.h"
#include "device.h"
#include "qp.h"
#include "sdma.h"
#include "fault.h"

static struct dentry *hfi1_dbg_root;

#define private2dd(file) (file_inode(file)->i_private)
#define private2ppd(file) (file_inode(file)->i_private)

static void *_opcode_stats_seq_start(struct seq_file *s, loff_t *pos)
{
        struct hfi1_opcode_stats_perctx *opstats;

        if (*pos >= ARRAY_SIZE(opstats->stats))
                return NULL;
        return pos;
}

static void *_opcode_stats_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        struct hfi1_opcode_stats_perctx *opstats;

        ++*pos;
        if (*pos >= ARRAY_SIZE(opstats->stats))
                return NULL;
        return pos;
}

static void _opcode_stats_seq_stop(struct seq_file *s, void *v)
{
}

static int opcode_stats_show(struct seq_file *s, u8 i, u64 packets, u64 bytes)
{
        if (!packets && !bytes)
                return SEQ_SKIP;
        seq_printf(s, "%02x %llu/%llu\n", i,
                   (unsigned long long)packets,
                   (unsigned long long)bytes);

        return 0;
}

static int _opcode_stats_seq_show(struct seq_file *s, void *v)
{
        loff_t *spos = v;
        loff_t i = *spos, j;
        u64 n_packets = 0, n_bytes = 0;
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        struct hfi1_ctxtdata *rcd;

        for (j = 0; j < dd->first_dyn_alloc_ctxt; j++) {
                rcd = hfi1_rcd_get_by_index(dd, j);
                if (rcd) {
                        n_packets += rcd->opstats->stats[i].n_packets;
                        n_bytes += rcd->opstats->stats[i].n_bytes;
                }
                hfi1_rcd_put(rcd);
        }
        return opcode_stats_show(s, i, n_packets, n_bytes);
}

DEBUGFS_SEQ_FILE_OPS(opcode_stats);
DEBUGFS_SEQ_FILE_OPEN(opcode_stats)
DEBUGFS_FILE_OPS(opcode_stats);

static void *_tx_opcode_stats_seq_start(struct seq_file *s, loff_t *pos)
{
        return _opcode_stats_seq_start(s, pos);
}

static void *_tx_opcode_stats_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        return _opcode_stats_seq_next(s, v, pos);
}

static void _tx_opcode_stats_seq_stop(struct seq_file *s, void *v)
{
}

static int _tx_opcode_stats_seq_show(struct seq_file *s, void *v)
{
        loff_t *spos = v;
        loff_t i = *spos;
        int j;
        u64 n_packets = 0, n_bytes = 0;
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        for_each_possible_cpu(j) {
                struct hfi1_opcode_stats_perctx *s =
                        per_cpu_ptr(dd->tx_opstats, j);
                n_packets += s->stats[i].n_packets;
                n_bytes += s->stats[i].n_bytes;
        }
        return opcode_stats_show(s, i, n_packets, n_bytes);
}

DEBUGFS_SEQ_FILE_OPS(tx_opcode_stats);
DEBUGFS_SEQ_FILE_OPEN(tx_opcode_stats)
DEBUGFS_FILE_OPS(tx_opcode_stats);

static void *_ctx_stats_seq_start(struct seq_file *s, loff_t *pos)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        if (!*pos)
                return SEQ_START_TOKEN;
        if (*pos >= dd->first_dyn_alloc_ctxt)
                return NULL;
        return pos;
}

static void *_ctx_stats_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        if (v == SEQ_START_TOKEN)
                return pos;

        ++*pos;
        if (*pos >= dd->first_dyn_alloc_ctxt)
                return NULL;
        return pos;
}

static void _ctx_stats_seq_stop(struct seq_file *s, void *v)
{
        /* nothing allocated */
}

static int _ctx_stats_seq_show(struct seq_file *s, void *v)
{
        loff_t *spos;
        loff_t i, j;
        u64 n_packets = 0;
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        struct hfi1_ctxtdata *rcd;

        if (v == SEQ_START_TOKEN) {
                seq_puts(s, "Ctx:npkts\n");
                return 0;
        }

        spos = v;
        i = *spos;

        rcd = hfi1_rcd_get_by_index_safe(dd, i);
        if (!rcd)
                return SEQ_SKIP;

        for (j = 0; j < ARRAY_SIZE(rcd->opstats->stats); j++)
                n_packets += rcd->opstats->stats[j].n_packets;

        hfi1_rcd_put(rcd);

        if (!n_packets)
                return SEQ_SKIP;

        seq_printf(s, "  %llu:%llu\n", i, n_packets);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(ctx_stats);
DEBUGFS_SEQ_FILE_OPEN(ctx_stats)
DEBUGFS_FILE_OPS(ctx_stats);

static void *_qp_stats_seq_start(struct seq_file *s, loff_t *pos)
        __acquires(RCU)
{
        struct rvt_qp_iter *iter;
        loff_t n = *pos;

        iter = rvt_qp_iter_init(s->private, 0, NULL);

        /* stop calls rcu_read_unlock */
        rcu_read_lock();

        if (!iter)
                return NULL;

        do {
                if (rvt_qp_iter_next(iter)) {
                        kfree(iter);
                        return NULL;
                }
        } while (n--);

        return iter;
}

static void *_qp_stats_seq_next(struct seq_file *s, void *iter_ptr,
                                loff_t *pos)
        __must_hold(RCU)
{
        struct rvt_qp_iter *iter = iter_ptr;

        (*pos)++;

        if (rvt_qp_iter_next(iter)) {
                kfree(iter);
                return NULL;
        }

        return iter;
}

static void _qp_stats_seq_stop(struct seq_file *s, void *iter_ptr)
        __releases(RCU)
{
        rcu_read_unlock();
}

static int _qp_stats_seq_show(struct seq_file *s, void *iter_ptr)
{
        struct rvt_qp_iter *iter = iter_ptr;

        if (!iter)
                return 0;

        qp_iter_print(s, iter);

        return 0;
}

DEBUGFS_SEQ_FILE_OPS(qp_stats);
DEBUGFS_SEQ_FILE_OPEN(qp_stats)
DEBUGFS_FILE_OPS(qp_stats);

static void *_sdes_seq_start(struct seq_file *s, loff_t *pos)
{
        struct hfi1_ibdev *ibd;
        struct hfi1_devdata *dd;

        ibd = (struct hfi1_ibdev *)s->private;
        dd = dd_from_dev(ibd);
        if (!dd->per_sdma || *pos >= dd->num_sdma)
                return NULL;
        return pos;
}

static void *_sdes_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        ++*pos;
        if (!dd->per_sdma || *pos >= dd->num_sdma)
                return NULL;
        return pos;
}

static void _sdes_seq_stop(struct seq_file *s, void *v)
{
}

static int _sdes_seq_show(struct seq_file *s, void *v)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        loff_t *spos = v;
        loff_t i = *spos;

        sdma_seqfile_dump_sde(s, &dd->per_sdma[i]);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(sdes);
DEBUGFS_SEQ_FILE_OPEN(sdes)
DEBUGFS_FILE_OPS(sdes);

static void *_rcds_seq_start(struct seq_file *s, loff_t *pos)
{
        struct hfi1_ibdev *ibd;
        struct hfi1_devdata *dd;

        ibd = (struct hfi1_ibdev *)s->private;
        dd = dd_from_dev(ibd);
        if (!dd->rcd || *pos >= dd->n_krcv_queues)
                return NULL;
        return pos;
}

static void *_rcds_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        ++*pos;
        if (!dd->rcd || *pos >= dd->num_rcv_contexts)
                return NULL;
        return pos;
}

static void _rcds_seq_stop(struct seq_file *s, void *v)
{
}

static int _rcds_seq_show(struct seq_file *s, void *v)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        struct hfi1_ctxtdata *rcd;
        loff_t *spos = v;
        loff_t i = *spos;

        rcd = hfi1_rcd_get_by_index_safe(dd, i);
        if (rcd)
                seqfile_dump_rcd(s, rcd);
        hfi1_rcd_put(rcd);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(rcds);
DEBUGFS_SEQ_FILE_OPEN(rcds)
DEBUGFS_FILE_OPS(rcds);

static void *_pios_seq_start(struct seq_file *s, loff_t *pos)
{
        struct hfi1_ibdev *ibd;
        struct hfi1_devdata *dd;

        ibd = (struct hfi1_ibdev *)s->private;
        dd = dd_from_dev(ibd);
        if (!dd->send_contexts || *pos >= dd->num_send_contexts)
                return NULL;
        return pos;
}

static void *_pios_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);

        ++*pos;
        if (!dd->send_contexts || *pos >= dd->num_send_contexts)
                return NULL;
        return pos;
}

static void _pios_seq_stop(struct seq_file *s, void *v)
{
}

static int _pios_seq_show(struct seq_file *s, void *v)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        struct send_context_info *sci;
        loff_t *spos = v;
        loff_t i = *spos;
        unsigned long flags;

        spin_lock_irqsave(&dd->sc_lock, flags);
        sci = &dd->send_contexts[i];
        if (sci && sci->type != SC_USER && sci->allocated && sci->sc)
                seqfile_dump_sci(s, i, sci);
        spin_unlock_irqrestore(&dd->sc_lock, flags);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(pios);
DEBUGFS_SEQ_FILE_OPEN(pios)
DEBUGFS_FILE_OPS(pios);

/* read the per-device counters */
static ssize_t dev_counters_read(struct file *file, char __user *buf,
                                 size_t count, loff_t *ppos)
{
        u64 *counters;
        size_t avail;
        struct hfi1_devdata *dd;
        ssize_t rval;

        dd = private2dd(file);
        avail = hfi1_read_cntrs(dd, NULL, &counters);
        rval =  simple_read_from_buffer(buf, count, ppos, counters, avail);
        return rval;
}

/* read the per-device counters */
static ssize_t dev_names_read(struct file *file, char __user *buf,
                              size_t count, loff_t *ppos)
{
        char *names;
        size_t avail;
        struct hfi1_devdata *dd;
        ssize_t rval;

        dd = private2dd(file);
        avail = hfi1_read_cntrs(dd, &names, NULL);
        rval =  simple_read_from_buffer(buf, count, ppos, names, avail);
        return rval;
}

struct counter_info {
        char *name;
        const struct file_operations ops;
};

/*
 * Could use file_inode(file)->i_ino to figure out which file,
 * instead of separate routine for each, but for now, this works...
 */

/* read the per-port names (same for each port) */
static ssize_t portnames_read(struct file *file, char __user *buf,
                              size_t count, loff_t *ppos)
{
        char *names;
        size_t avail;
        struct hfi1_devdata *dd;
        ssize_t rval;

        dd = private2dd(file);
        avail = hfi1_read_portcntrs(dd->pport, &names, NULL);
        rval = simple_read_from_buffer(buf, count, ppos, names, avail);
        return rval;
}

/* read the per-port counters */
static ssize_t portcntrs_debugfs_read(struct file *file, char __user *buf,
                                      size_t count, loff_t *ppos)
{
        u64 *counters;
        size_t avail;
        struct hfi1_pportdata *ppd;
        ssize_t rval;

        ppd = private2ppd(file);
        avail = hfi1_read_portcntrs(ppd, NULL, &counters);
        rval = simple_read_from_buffer(buf, count, ppos, counters, avail);
        return rval;
}

static void check_dyn_flag(u64 scratch0, char *p, int size, int *used,
                           int this_hfi, int hfi, u32 flag, const char *what)
{
        u32 mask;

        mask = flag << (hfi ? CR_DYN_SHIFT : 0);
        if (scratch0 & mask) {
                *used += scnprintf(p + *used, size - *used,
                                   "  0x%08x - HFI%d %s in use, %s device\n",
                                   mask, hfi, what,
                                   this_hfi == hfi ? "this" : "other");
        }
}

static ssize_t asic_flags_read(struct file *file, char __user *buf,
                               size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd;
        struct hfi1_devdata *dd;
        u64 scratch0;
        char *tmp;
        int ret = 0;
        int size;
        int used;
        int i;

        ppd = private2ppd(file);
        dd = ppd->dd;
        size = PAGE_SIZE;
        used = 0;
        tmp = kmalloc(size, GFP_KERNEL);
        if (!tmp)
                return -ENOMEM;

        scratch0 = read_csr(dd, ASIC_CFG_SCRATCH);
        used += scnprintf(tmp + used, size - used,
                          "Resource flags: 0x%016llx\n", scratch0);

        /* check permanent flag */
        if (scratch0 & CR_THERM_INIT) {
                used += scnprintf(tmp + used, size - used,
                                  "  0x%08x - thermal monitoring initialized\n",
                                  (u32)CR_THERM_INIT);
        }

        /* check each dynamic flag on each HFI */
        for (i = 0; i < 2; i++) {
                check_dyn_flag(scratch0, tmp, size, &used, dd->hfi1_id, i,
                               CR_SBUS, "SBus");
                check_dyn_flag(scratch0, tmp, size, &used, dd->hfi1_id, i,
                               CR_EPROM, "EPROM");
                check_dyn_flag(scratch0, tmp, size, &used, dd->hfi1_id, i,
                               CR_I2C1, "i2c chain 1");
                check_dyn_flag(scratch0, tmp, size, &used, dd->hfi1_id, i,
                               CR_I2C2, "i2c chain 2");
        }
        used += scnprintf(tmp + used, size - used, "Write bits to clear\n");

        ret = simple_read_from_buffer(buf, count, ppos, tmp, used);
        kfree(tmp);
        return ret;
}

static ssize_t asic_flags_write(struct file *file, const char __user *buf,
                                size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd;
        struct hfi1_devdata *dd;
        char *buff;
        int ret;
        unsigned long long value;
        u64 scratch0;
        u64 clear;

        ppd = private2ppd(file);
        dd = ppd->dd;

        /* zero terminate and read the expected integer */
        buff = memdup_user_nul(buf, count);
        if (IS_ERR(buff))
                return PTR_ERR(buff);

        ret = kstrtoull(buff, 0, &value);
        if (ret)
                goto do_free;
        clear = value;

        /* obtain exclusive access */
        mutex_lock(&dd->asic_data->asic_resource_mutex);
        acquire_hw_mutex(dd);

        scratch0 = read_csr(dd, ASIC_CFG_SCRATCH);
        scratch0 &= ~clear;
        write_csr(dd, ASIC_CFG_SCRATCH, scratch0);
        /* force write to be visible to other HFI on another OS */
        (void)read_csr(dd, ASIC_CFG_SCRATCH);

        release_hw_mutex(dd);
        mutex_unlock(&dd->asic_data->asic_resource_mutex);

        /* return the number of bytes written */
        ret = count;

 do_free:
        kfree(buff);
        return ret;
}

/* read the dc8051 memory */
static ssize_t dc8051_memory_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd = private2ppd(file);
        ssize_t rval;
        void *tmp;
        loff_t start, end;

        /* the checks below expect the position to be positive */
        if (*ppos < 0)
                return -EINVAL;

        tmp = kzalloc(DC8051_DATA_MEM_SIZE, GFP_KERNEL);
        if (!tmp)
                return -ENOMEM;

        /*
         * Fill in the requested portion of the temporary buffer from the
         * 8051 memory.  The 8051 memory read is done in terms of 8 bytes.
         * Adjust start and end to fit.  Skip reading anything if out of
         * range.
         */
        start = *ppos & ~0x7;   /* round down */
        if (start < DC8051_DATA_MEM_SIZE) {
                end = (*ppos + count + 7) & ~0x7; /* round up */
                if (end > DC8051_DATA_MEM_SIZE)
                        end = DC8051_DATA_MEM_SIZE;
                rval = read_8051_data(ppd->dd, start, end - start,
                                      (u64 *)(tmp + start));
                if (rval)
                        goto done;
        }

        rval = simple_read_from_buffer(buf, count, ppos, tmp,
                                       DC8051_DATA_MEM_SIZE);
done:
        kfree(tmp);
        return rval;
}

static ssize_t debugfs_lcb_read(struct file *file, char __user *buf,
                                size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd = private2ppd(file);
        struct hfi1_devdata *dd = ppd->dd;
        unsigned long total, csr_off;
        u64 data;

        if (*ppos < 0)
                return -EINVAL;
        /* only read 8 byte quantities */
        if ((count % 8) != 0)
                return -EINVAL;
        /* offset must be 8-byte aligned */
        if ((*ppos % 8) != 0)
                return -EINVAL;
        /* do nothing if out of range or zero count */
        if (*ppos >= (LCB_END - LCB_START) || !count)
                return 0;
        /* reduce count if needed */
        if (*ppos + count > LCB_END - LCB_START)
                count = (LCB_END - LCB_START) - *ppos;

        csr_off = LCB_START + *ppos;
        for (total = 0; total < count; total += 8, csr_off += 8) {
                if (read_lcb_csr(dd, csr_off, (u64 *)&data))
                        break; /* failed */
                if (put_user(data, (unsigned long __user *)(buf + total)))
                        break;
        }
        *ppos += total;
        return total;
}

static ssize_t debugfs_lcb_write(struct file *file, const char __user *buf,
                                 size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd = private2ppd(file);
        struct hfi1_devdata *dd = ppd->dd;
        unsigned long total, csr_off, data;

        if (*ppos < 0)
                return -EINVAL;
        /* only write 8 byte quantities */
        if ((count % 8) != 0)
                return -EINVAL;
        /* offset must be 8-byte aligned */
        if ((*ppos % 8) != 0)
                return -EINVAL;
        /* do nothing if out of range or zero count */
        if (*ppos >= (LCB_END - LCB_START) || !count)
                return 0;
        /* reduce count if needed */
        if (*ppos + count > LCB_END - LCB_START)
                count = (LCB_END - LCB_START) - *ppos;

        csr_off = LCB_START + *ppos;
        for (total = 0; total < count; total += 8, csr_off += 8) {
                if (get_user(data, (unsigned long __user *)(buf + total)))
                        break;
                if (write_lcb_csr(dd, csr_off, data))
                        break; /* failed */
        }
        *ppos += total;
        return total;
}

/*
 * read the per-port QSFP data for ppd
 */
static ssize_t qsfp_debugfs_dump(struct file *file, char __user *buf,
                                 size_t count, loff_t *ppos)
{
        struct hfi1_pportdata *ppd;
        char *tmp;
        int ret;

        ppd = private2ppd(file);
        tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
        if (!tmp)
                return -ENOMEM;

        ret = qsfp_dump(ppd, tmp, PAGE_SIZE);
        if (ret > 0)
                ret = simple_read_from_buffer(buf, count, ppos, tmp, ret);
        kfree(tmp);
        return ret;
}

/* Do an i2c write operation on the chain for the given HFI. */
static ssize_t __i2c_debugfs_write(struct file *file, const char __user *buf,
                                   size_t count, loff_t *ppos, u32 target)
{
        struct hfi1_pportdata *ppd;
        char *buff;
        int ret;
        int i2c_addr;
        int offset;
        int total_written;

        ppd = private2ppd(file);

        /* byte offset format: [offsetSize][i2cAddr][offsetHigh][offsetLow] */
        i2c_addr = (*ppos >> 16) & 0xffff;
        offset = *ppos & 0xffff;

        /* explicitly reject invalid address 0 to catch cp and cat */
        if (i2c_addr == 0)
                return -EINVAL;

        buff = memdup_user(buf, count);
        if (IS_ERR(buff))
                return PTR_ERR(buff);

        total_written = i2c_write(ppd, target, i2c_addr, offset, buff, count);
        if (total_written < 0) {
                ret = total_written;
                goto _free;
        }

        *ppos += total_written;

        ret = total_written;

 _free:
        kfree(buff);
        return ret;
}

/* Do an i2c write operation on chain for HFI 0. */
static ssize_t i2c1_debugfs_write(struct file *file, const char __user *buf,
                                  size_t count, loff_t *ppos)
{
        return __i2c_debugfs_write(file, buf, count, ppos, 0);
}

/* Do an i2c write operation on chain for HFI 1. */
static ssize_t i2c2_debugfs_write(struct file *file, const char __user *buf,
                                  size_t count, loff_t *ppos)
{
        return __i2c_debugfs_write(file, buf, count, ppos, 1);
}

/* Do an i2c read operation on the chain for the given HFI. */
static ssize_t __i2c_debugfs_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos, u32 target)
{
        struct hfi1_pportdata *ppd;
        char *buff;
        int ret;
        int i2c_addr;
        int offset;
        int total_read;

        ppd = private2ppd(file);

        /* byte offset format: [offsetSize][i2cAddr][offsetHigh][offsetLow] */
        i2c_addr = (*ppos >> 16) & 0xffff;
        offset = *ppos & 0xffff;

        /* explicitly reject invalid address 0 to catch cp and cat */
        if (i2c_addr == 0)
                return -EINVAL;

        buff = kmalloc(count, GFP_KERNEL);
        if (!buff)
                return -ENOMEM;

        total_read = i2c_read(ppd, target, i2c_addr, offset, buff, count);
        if (total_read < 0) {
                ret = total_read;
                goto _free;
        }

        *ppos += total_read;

        ret = copy_to_user(buf, buff, total_read);
        if (ret > 0) {
                ret = -EFAULT;
                goto _free;
        }

        ret = total_read;

 _free:
        kfree(buff);
        return ret;
}

/* Do an i2c read operation on chain for HFI 0. */
static ssize_t i2c1_debugfs_read(struct file *file, char __user *buf,
                                 size_t count, loff_t *ppos)
{
        return __i2c_debugfs_read(file, buf, count, ppos, 0);
}

/* Do an i2c read operation on chain for HFI 1. */
static ssize_t i2c2_debugfs_read(struct file *file, char __user *buf,
                                 size_t count, loff_t *ppos)
{
        return __i2c_debugfs_read(file, buf, count, ppos, 1);
}

/* Do a QSFP write operation on the i2c chain for the given HFI. */
static ssize_t __qsfp_debugfs_write(struct file *file, const char __user *buf,
                                    size_t count, loff_t *ppos, u32 target)
{
        struct hfi1_pportdata *ppd;
        char *buff;
        int ret;
        int total_written;

        if (*ppos + count > QSFP_PAGESIZE * 4) /* base page + page00-page03 */
                return -EINVAL;

        ppd = private2ppd(file);

        buff = memdup_user(buf, count);
        if (IS_ERR(buff))
                return PTR_ERR(buff);

        total_written = qsfp_write(ppd, target, *ppos, buff, count);
        if (total_written < 0) {
                ret = total_written;
                goto _free;
        }

        *ppos += total_written;

        ret = total_written;

 _free:
        kfree(buff);
        return ret;
}

/* Do a QSFP write operation on i2c chain for HFI 0. */
static ssize_t qsfp1_debugfs_write(struct file *file, const char __user *buf,
                                   size_t count, loff_t *ppos)
{
        return __qsfp_debugfs_write(file, buf, count, ppos, 0);
}

/* Do a QSFP write operation on i2c chain for HFI 1. */
static ssize_t qsfp2_debugfs_write(struct file *file, const char __user *buf,
                                   size_t count, loff_t *ppos)
{
        return __qsfp_debugfs_write(file, buf, count, ppos, 1);
}

/* Do a QSFP read operation on the i2c chain for the given HFI. */
static ssize_t __qsfp_debugfs_read(struct file *file, char __user *buf,
                                   size_t count, loff_t *ppos, u32 target)
{
        struct hfi1_pportdata *ppd;
        char *buff;
        int ret;
        int total_read;

        if (*ppos + count > QSFP_PAGESIZE * 4) { /* base page + page00-page03 */
                ret = -EINVAL;
                goto _return;
        }

        ppd = private2ppd(file);

        buff = kmalloc(count, GFP_KERNEL);
        if (!buff) {
                ret = -ENOMEM;
                goto _return;
        }

        total_read = qsfp_read(ppd, target, *ppos, buff, count);
        if (total_read < 0) {
                ret = total_read;
                goto _free;
        }

        *ppos += total_read;

        ret = copy_to_user(buf, buff, total_read);
        if (ret > 0) {
                ret = -EFAULT;
                goto _free;
        }

        ret = total_read;

 _free:
        kfree(buff);
 _return:
        return ret;
}

/* Do a QSFP read operation on i2c chain for HFI 0. */
static ssize_t qsfp1_debugfs_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
{
        return __qsfp_debugfs_read(file, buf, count, ppos, 0);
}

/* Do a QSFP read operation on i2c chain for HFI 1. */
static ssize_t qsfp2_debugfs_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
{
        return __qsfp_debugfs_read(file, buf, count, ppos, 1);
}

static int __i2c_debugfs_open(struct inode *in, struct file *fp, u32 target)
{
        struct hfi1_pportdata *ppd;

        ppd = private2ppd(fp);

        return acquire_chip_resource(ppd->dd, i2c_target(target), 0);
}

static int i2c1_debugfs_open(struct inode *in, struct file *fp)
{
        return __i2c_debugfs_open(in, fp, 0);
}

static int i2c2_debugfs_open(struct inode *in, struct file *fp)
{
        return __i2c_debugfs_open(in, fp, 1);
}

static int __i2c_debugfs_release(struct inode *in, struct file *fp, u32 target)
{
        struct hfi1_pportdata *ppd;

        ppd = private2ppd(fp);

        release_chip_resource(ppd->dd, i2c_target(target));

        return 0;
}

static int i2c1_debugfs_release(struct inode *in, struct file *fp)
{
        return __i2c_debugfs_release(in, fp, 0);
}

static int i2c2_debugfs_release(struct inode *in, struct file *fp)
{
        return __i2c_debugfs_release(in, fp, 1);
}

static int __qsfp_debugfs_open(struct inode *in, struct file *fp, u32 target)
{
        struct hfi1_pportdata *ppd;

        ppd = private2ppd(fp);

        return acquire_chip_resource(ppd->dd, i2c_target(target), 0);
}

static int qsfp1_debugfs_open(struct inode *in, struct file *fp)
{
        return __qsfp_debugfs_open(in, fp, 0);
}

static int qsfp2_debugfs_open(struct inode *in, struct file *fp)
{
        return __qsfp_debugfs_open(in, fp, 1);
}

static int __qsfp_debugfs_release(struct inode *in, struct file *fp, u32 target)
{
        struct hfi1_pportdata *ppd;

        ppd = private2ppd(fp);

        release_chip_resource(ppd->dd, i2c_target(target));

        return 0;
}

static int qsfp1_debugfs_release(struct inode *in, struct file *fp)
{
        return __qsfp_debugfs_release(in, fp, 0);
}

static int qsfp2_debugfs_release(struct inode *in, struct file *fp)
{
        return __qsfp_debugfs_release(in, fp, 1);
}

#define EXPROM_WRITE_ENABLE BIT_ULL(14)

static bool exprom_wp_disabled;

static int exprom_wp_set(struct hfi1_devdata *dd, bool disable)
{
        u64 gpio_val = 0;

        if (disable) {
                gpio_val = EXPROM_WRITE_ENABLE;
                exprom_wp_disabled = true;
                dd_dev_info(dd, "Disable Expansion ROM Write Protection\n");
        } else {
                exprom_wp_disabled = false;
                dd_dev_info(dd, "Enable Expansion ROM Write Protection\n");
        }

        write_csr(dd, ASIC_GPIO_OUT, gpio_val);
        write_csr(dd, ASIC_GPIO_OE, gpio_val);

        return 0;
}

static ssize_t exprom_wp_debugfs_read(struct file *file, char __user *buf,
                                      size_t count, loff_t *ppos)
{
        return 0;
}

static ssize_t exprom_wp_debugfs_write(struct file *file,
                                       const char __user *buf, size_t count,
                                       loff_t *ppos)
{
        struct hfi1_pportdata *ppd = private2ppd(file);
        char cdata;

        if (count != 1)
                return -EINVAL;
        if (get_user(cdata, buf))
                return -EFAULT;
        if (cdata == '0')
                exprom_wp_set(ppd->dd, false);
        else if (cdata == '1')
                exprom_wp_set(ppd->dd, true);
        else
                return -EINVAL;

        return 1;
}

static unsigned long exprom_in_use;

static int exprom_wp_debugfs_open(struct inode *in, struct file *fp)
{
        if (test_and_set_bit(0, &exprom_in_use))
                return -EBUSY;

        return 0;
}

static int exprom_wp_debugfs_release(struct inode *in, struct file *fp)
{
        struct hfi1_pportdata *ppd = private2ppd(fp);

        if (exprom_wp_disabled)
                exprom_wp_set(ppd->dd, false);
        clear_bit(0, &exprom_in_use);

        return 0;
}

#define DEBUGFS_OPS(nm, readroutine, writeroutine)      \
{ \
        .name = nm, \
        .ops = { \
                .owner = THIS_MODULE, \
                .read = readroutine, \
                .write = writeroutine, \
                .llseek = generic_file_llseek, \
        }, \
}

#define DEBUGFS_XOPS(nm, readf, writef, openf, releasef) \
{ \
        .name = nm, \
        .ops = { \
                .owner = THIS_MODULE, \
                .read = readf, \
                .write = writef, \
                .llseek = generic_file_llseek, \
                .open = openf, \
                .release = releasef \
        }, \
}

static const struct counter_info cntr_ops[] = {
        DEBUGFS_OPS("counter_names", dev_names_read, NULL),
        DEBUGFS_OPS("counters", dev_counters_read, NULL),
        DEBUGFS_OPS("portcounter_names", portnames_read, NULL),
};

static const struct counter_info port_cntr_ops[] = {
        DEBUGFS_OPS("port%dcounters", portcntrs_debugfs_read, NULL),
        DEBUGFS_XOPS("i2c1", i2c1_debugfs_read, i2c1_debugfs_write,
                     i2c1_debugfs_open, i2c1_debugfs_release),
        DEBUGFS_XOPS("i2c2", i2c2_debugfs_read, i2c2_debugfs_write,
                     i2c2_debugfs_open, i2c2_debugfs_release),
        DEBUGFS_OPS("qsfp_dump%d", qsfp_debugfs_dump, NULL),
        DEBUGFS_XOPS("qsfp1", qsfp1_debugfs_read, qsfp1_debugfs_write,
                     qsfp1_debugfs_open, qsfp1_debugfs_release),
        DEBUGFS_XOPS("qsfp2", qsfp2_debugfs_read, qsfp2_debugfs_write,
                     qsfp2_debugfs_open, qsfp2_debugfs_release),
        DEBUGFS_XOPS("exprom_wp", exprom_wp_debugfs_read,
                     exprom_wp_debugfs_write, exprom_wp_debugfs_open,
                     exprom_wp_debugfs_release),
        DEBUGFS_OPS("asic_flags", asic_flags_read, asic_flags_write),
        DEBUGFS_OPS("dc8051_memory", dc8051_memory_read, NULL),
        DEBUGFS_OPS("lcb", debugfs_lcb_read, debugfs_lcb_write),
};

static void *_sdma_cpu_list_seq_start(struct seq_file *s, loff_t *pos)
{
        if (*pos >= num_online_cpus())
                return NULL;

        return pos;
}

static void *_sdma_cpu_list_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        ++*pos;
        if (*pos >= num_online_cpus())
                return NULL;

        return pos;
}

static void _sdma_cpu_list_seq_stop(struct seq_file *s, void *v)
{
        /* nothing allocated */
}

static int _sdma_cpu_list_seq_show(struct seq_file *s, void *v)
{
        struct hfi1_ibdev *ibd = (struct hfi1_ibdev *)s->private;
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        loff_t *spos = v;
        loff_t i = *spos;

        sdma_seqfile_dump_cpu_list(s, dd, (unsigned long)i);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(sdma_cpu_list);
DEBUGFS_SEQ_FILE_OPEN(sdma_cpu_list)
DEBUGFS_FILE_OPS(sdma_cpu_list);

void hfi1_dbg_ibdev_init(struct hfi1_ibdev *ibd)
{
        char name[sizeof("port0counters") + 1];
        char link[10];
        struct hfi1_devdata *dd = dd_from_dev(ibd);
        struct hfi1_pportdata *ppd;
        struct dentry *root;
        int unit = dd->unit;
        int i, j;

        if (!hfi1_dbg_root)
                return;
        snprintf(name, sizeof(name), "%s_%d", class_name(), unit);
        snprintf(link, sizeof(link), "%d", unit);
        root = debugfs_create_dir(name, hfi1_dbg_root);
        ibd->hfi1_ibdev_dbg = root;

        ibd->hfi1_ibdev_link =
                debugfs_create_symlink(link, hfi1_dbg_root, name);

        debugfs_create_file("opcode_stats", 0444, root, ibd,
                            &_opcode_stats_file_ops);
        debugfs_create_file("tx_opcode_stats", 0444, root, ibd,
                            &_tx_opcode_stats_file_ops);
        debugfs_create_file("ctx_stats", 0444, root, ibd, &_ctx_stats_file_ops);
        debugfs_create_file("qp_stats", 0444, root, ibd, &_qp_stats_file_ops);
        debugfs_create_file("sdes", 0444, root, ibd, &_sdes_file_ops);
        debugfs_create_file("rcds", 0444, root, ibd, &_rcds_file_ops);
        debugfs_create_file("pios", 0444, root, ibd, &_pios_file_ops);
        debugfs_create_file("sdma_cpu_list", 0444, root, ibd,
                            &_sdma_cpu_list_file_ops);

        /* dev counter files */
        for (i = 0; i < ARRAY_SIZE(cntr_ops); i++)
                debugfs_create_file(cntr_ops[i].name, 0444, root, dd,
                                    &cntr_ops[i].ops);

        /* per port files */
        for (ppd = dd->pport, j = 0; j < dd->num_pports; j++, ppd++)
                for (i = 0; i < ARRAY_SIZE(port_cntr_ops); i++) {
                        snprintf(name,
                                 sizeof(name),
                                 port_cntr_ops[i].name,
                                 j + 1);
                        debugfs_create_file(name,
                                            !port_cntr_ops[i].ops.write ?
                                                    S_IRUGO :
                                                    S_IRUGO | S_IWUSR,
                                            root, ppd, &port_cntr_ops[i].ops);
                }

        hfi1_fault_init_debugfs(ibd);
}

void hfi1_dbg_ibdev_exit(struct hfi1_ibdev *ibd)
{
        if (!hfi1_dbg_root)
                goto out;
        hfi1_fault_exit_debugfs(ibd);
        debugfs_remove(ibd->hfi1_ibdev_link);
        debugfs_remove_recursive(ibd->hfi1_ibdev_dbg);
out:
        ibd->hfi1_ibdev_dbg = NULL;
}

/*
 * driver stats field names, one line per stat, single string.  Used by
 * programs like hfistats to print the stats in a way which works for
 * different versions of drivers, without changing program source.
 * if hfi1_ib_stats changes, this needs to change.  Names need to be
 * 12 chars or less (w/o newline), for proper display by hfistats utility.
 */
static const char * const hfi1_statnames[] = {
        /* must be element 0*/
        "KernIntr",
        "ErrorIntr",
        "Tx_Errs",
        "Rcv_Errs",
        "H/W_Errs",
        "NoPIOBufs",
        "CtxtsOpen",
        "RcvLen_Errs",
        "EgrBufFull",
        "EgrHdrFull"
};

static void *_driver_stats_names_seq_start(struct seq_file *s, loff_t *pos)
{
        if (*pos >= ARRAY_SIZE(hfi1_statnames))
                return NULL;
        return pos;
}

static void *_driver_stats_names_seq_next(
        struct seq_file *s,
        void *v,
        loff_t *pos)
{
        ++*pos;
        if (*pos >= ARRAY_SIZE(hfi1_statnames))
                return NULL;
        return pos;
}

static void _driver_stats_names_seq_stop(struct seq_file *s, void *v)
{
}

static int _driver_stats_names_seq_show(struct seq_file *s, void *v)
{
        loff_t *spos = v;

        seq_printf(s, "%s\n", hfi1_statnames[*spos]);
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(driver_stats_names);
DEBUGFS_SEQ_FILE_OPEN(driver_stats_names)
DEBUGFS_FILE_OPS(driver_stats_names);

static void *_driver_stats_seq_start(struct seq_file *s, loff_t *pos)
{
        if (*pos >= ARRAY_SIZE(hfi1_statnames))
                return NULL;
        return pos;
}

static void *_driver_stats_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        ++*pos;
        if (*pos >= ARRAY_SIZE(hfi1_statnames))
                return NULL;
        return pos;
}

static void _driver_stats_seq_stop(struct seq_file *s, void *v)
{
}

static void hfi1_sps_show_ints(struct seq_file *s)
{
        unsigned long index, flags;
        struct hfi1_devdata *dd;
        u64 sps_ints = 0;

        xa_lock_irqsave(&hfi1_dev_table, flags);
        xa_for_each(&hfi1_dev_table, index, dd) {
                sps_ints += get_all_cpu_total(dd->int_counter);
        }
        xa_unlock_irqrestore(&hfi1_dev_table, flags);
        seq_write(s, &sps_ints, sizeof(u64));
}

static int _driver_stats_seq_show(struct seq_file *s, void *v)
{
        loff_t *spos = v;
        u64 *stats = (u64 *)&hfi1_stats;

        /* special case for interrupts */
        if (*spos == 0)
                hfi1_sps_show_ints(s);
        else
                seq_write(s, stats + *spos, sizeof(u64));
        return 0;
}

DEBUGFS_SEQ_FILE_OPS(driver_stats);
DEBUGFS_SEQ_FILE_OPEN(driver_stats)
DEBUGFS_FILE_OPS(driver_stats);

void hfi1_dbg_init(void)
{
        hfi1_dbg_root  = debugfs_create_dir(DRIVER_NAME, NULL);
        debugfs_create_file("driver_stats_names", 0444, hfi1_dbg_root, NULL,
                            &_driver_stats_names_file_ops);
        debugfs_create_file("driver_stats", 0444, hfi1_dbg_root, NULL,
                            &_driver_stats_file_ops);
}

void hfi1_dbg_exit(void)
{
        debugfs_remove_recursive(hfi1_dbg_root);
        hfi1_dbg_root = NULL;
}