root/drivers/scsi/elx/efct/efct_xport.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Broadcom. All Rights Reserved. The term
 * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
 */

#include "efct_driver.h"
#include "efct_unsol.h"

static struct dentry *efct_debugfs_root;
static atomic_t efct_debugfs_count;

static const struct scsi_host_template efct_template = {
        .module                 = THIS_MODULE,
        .name                   = EFCT_DRIVER_NAME,
        .supported_mode         = MODE_TARGET,
};

/* globals */
static struct fc_function_template efct_xport_functions;
static struct fc_function_template efct_vport_functions;

static struct scsi_transport_template *efct_xport_fc_tt;
static struct scsi_transport_template *efct_vport_fc_tt;

struct efct_xport *
efct_xport_alloc(struct efct *efct)
{
        struct efct_xport *xport;

        xport = kzalloc_obj(*xport);
        if (!xport)
                return xport;

        xport->efct = efct;
        return xport;
}

static int
efct_xport_init_debugfs(struct efct *efct)
{
        /* Setup efct debugfs root directory */
        if (!efct_debugfs_root) {
                efct_debugfs_root = debugfs_create_dir("efct", NULL);
                atomic_set(&efct_debugfs_count, 0);
        }

        /* Create a directory for sessions in root */
        if (!efct->sess_debugfs_dir) {
                efct->sess_debugfs_dir = debugfs_create_dir("sessions",
                                                        efct_debugfs_root);
                if (IS_ERR(efct->sess_debugfs_dir)) {
                        efc_log_err(efct,
                                    "failed to create debugfs entry for sessions\n");
                        goto debugfs_fail;
                }
                atomic_inc(&efct_debugfs_count);
        }

        return 0;

debugfs_fail:
        return -EIO;
}

static void efct_xport_delete_debugfs(struct efct *efct)
{
        /* Remove session debugfs directory */
        debugfs_remove(efct->sess_debugfs_dir);
        efct->sess_debugfs_dir = NULL;
        atomic_dec(&efct_debugfs_count);

        if (atomic_read(&efct_debugfs_count) == 0) {
                /* remove root debugfs directory */
                debugfs_remove(efct_debugfs_root);
                efct_debugfs_root = NULL;
        }
}

int
efct_xport_attach(struct efct_xport *xport)
{
        struct efct *efct = xport->efct;
        int rc;

        rc = efct_hw_setup(&efct->hw, efct, efct->pci);
        if (rc) {
                efc_log_err(efct, "%s: Can't setup hardware\n", efct->desc);
                return rc;
        }

        efct_hw_parse_filter(&efct->hw, (void *)efct->filter_def);

        xport->io_pool = efct_io_pool_create(efct, efct->hw.config.n_sgl);
        if (!xport->io_pool) {
                efc_log_err(efct, "Can't allocate IO pool\n");
                return -ENOMEM;
        }

        return 0;
}

static void
efct_xport_link_stats_cb(int status, u32 num_counters,
                         struct efct_hw_link_stat_counts *counters, void *arg)
{
        union efct_xport_stats_u *result = arg;

        result->stats.link_stats.link_failure_error_count =
                counters[EFCT_HW_LINK_STAT_LINK_FAILURE_COUNT].counter;
        result->stats.link_stats.loss_of_sync_error_count =
                counters[EFCT_HW_LINK_STAT_LOSS_OF_SYNC_COUNT].counter;
        result->stats.link_stats.primitive_sequence_error_count =
                counters[EFCT_HW_LINK_STAT_PRIMITIVE_SEQ_COUNT].counter;
        result->stats.link_stats.invalid_transmission_word_error_count =
                counters[EFCT_HW_LINK_STAT_INVALID_XMIT_WORD_COUNT].counter;
        result->stats.link_stats.crc_error_count =
                counters[EFCT_HW_LINK_STAT_CRC_COUNT].counter;

        complete(&result->stats.done);
}

static void
efct_xport_host_stats_cb(int status, u32 num_counters,
                         struct efct_hw_host_stat_counts *counters, void *arg)
{
        union efct_xport_stats_u *result = arg;

        result->stats.host_stats.transmit_kbyte_count =
                counters[EFCT_HW_HOST_STAT_TX_KBYTE_COUNT].counter;
        result->stats.host_stats.receive_kbyte_count =
                counters[EFCT_HW_HOST_STAT_RX_KBYTE_COUNT].counter;
        result->stats.host_stats.transmit_frame_count =
                counters[EFCT_HW_HOST_STAT_TX_FRAME_COUNT].counter;
        result->stats.host_stats.receive_frame_count =
                counters[EFCT_HW_HOST_STAT_RX_FRAME_COUNT].counter;

        complete(&result->stats.done);
}

static void
efct_xport_async_link_stats_cb(int status, u32 num_counters,
                               struct efct_hw_link_stat_counts *counters,
                               void *arg)
{
        union efct_xport_stats_u *result = arg;

        result->stats.link_stats.link_failure_error_count =
                counters[EFCT_HW_LINK_STAT_LINK_FAILURE_COUNT].counter;
        result->stats.link_stats.loss_of_sync_error_count =
                counters[EFCT_HW_LINK_STAT_LOSS_OF_SYNC_COUNT].counter;
        result->stats.link_stats.primitive_sequence_error_count =
                counters[EFCT_HW_LINK_STAT_PRIMITIVE_SEQ_COUNT].counter;
        result->stats.link_stats.invalid_transmission_word_error_count =
                counters[EFCT_HW_LINK_STAT_INVALID_XMIT_WORD_COUNT].counter;
        result->stats.link_stats.crc_error_count =
                counters[EFCT_HW_LINK_STAT_CRC_COUNT].counter;
}

static void
efct_xport_async_host_stats_cb(int status, u32 num_counters,
                               struct efct_hw_host_stat_counts *counters,
                               void *arg)
{
        union efct_xport_stats_u *result = arg;

        result->stats.host_stats.transmit_kbyte_count =
                counters[EFCT_HW_HOST_STAT_TX_KBYTE_COUNT].counter;
        result->stats.host_stats.receive_kbyte_count =
                counters[EFCT_HW_HOST_STAT_RX_KBYTE_COUNT].counter;
        result->stats.host_stats.transmit_frame_count =
                counters[EFCT_HW_HOST_STAT_TX_FRAME_COUNT].counter;
        result->stats.host_stats.receive_frame_count =
                counters[EFCT_HW_HOST_STAT_RX_FRAME_COUNT].counter;
}

static void
efct_xport_config_stats_timer(struct efct *efct);

static void
efct_xport_stats_timer_cb(struct timer_list *t)
{
        struct efct_xport *xport = timer_container_of(xport, t, stats_timer);
        struct efct *efct = xport->efct;

        efct_xport_config_stats_timer(efct);
}

static void
efct_xport_config_stats_timer(struct efct *efct)
{
        u32 timeout = 3 * 1000;
        struct efct_xport *xport = NULL;

        if (!efct) {
                pr_err("%s: failed to locate EFCT device\n", __func__);
                return;
        }

        xport = efct->xport;
        efct_hw_get_link_stats(&efct->hw, 0, 0, 0,
                               efct_xport_async_link_stats_cb,
                               &xport->fc_xport_stats);
        efct_hw_get_host_stats(&efct->hw, 0, efct_xport_async_host_stats_cb,
                               &xport->fc_xport_stats);

        timer_setup(&xport->stats_timer,
                    &efct_xport_stats_timer_cb, 0);
        mod_timer(&xport->stats_timer,
                  jiffies + msecs_to_jiffies(timeout));
}

int
efct_xport_initialize(struct efct_xport *xport)
{
        struct efct *efct = xport->efct;
        int rc = 0;

        /* Initialize io lists */
        spin_lock_init(&xport->io_pending_lock);
        INIT_LIST_HEAD(&xport->io_pending_list);
        atomic_set(&xport->io_active_count, 0);
        atomic_set(&xport->io_pending_count, 0);
        atomic_set(&xport->io_total_free, 0);
        atomic_set(&xport->io_total_pending, 0);
        atomic_set(&xport->io_alloc_failed_count, 0);
        atomic_set(&xport->io_pending_recursing, 0);

        rc = efct_hw_init(&efct->hw);
        if (rc) {
                efc_log_err(efct, "efct_hw_init failure\n");
                goto out;
        }

        rc = efct_scsi_tgt_new_device(efct);
        if (rc) {
                efc_log_err(efct, "failed to initialize target\n");
                goto hw_init_out;
        }

        rc = efct_scsi_new_device(efct);
        if (rc) {
                efc_log_err(efct, "failed to initialize initiator\n");
                goto tgt_dev_out;
        }

        /* Get FC link and host statistics perodically*/
        efct_xport_config_stats_timer(efct);

        efct_xport_init_debugfs(efct);

        return rc;

tgt_dev_out:
        efct_scsi_tgt_del_device(efct);

hw_init_out:
        efct_hw_teardown(&efct->hw);
out:
        return rc;
}

int
efct_xport_status(struct efct_xport *xport, enum efct_xport_status cmd,
                  union efct_xport_stats_u *result)
{
        int rc = 0;
        struct efct *efct = NULL;
        union efct_xport_stats_u value;

        efct = xport->efct;

        switch (cmd) {
        case EFCT_XPORT_CONFIG_PORT_STATUS:
                if (xport->configured_link_state == 0) {
                        /*
                         * Initial state is offline. configured_link_state is
                         * set to online explicitly when port is brought online
                         */
                        xport->configured_link_state = EFCT_XPORT_PORT_OFFLINE;
                }
                result->value = xport->configured_link_state;
                break;

        case EFCT_XPORT_PORT_STATUS:
                /* Determine port status based on link speed. */
                value.value = efct_hw_get_link_speed(&efct->hw);
                if (value.value == 0)
                        result->value = EFCT_XPORT_PORT_OFFLINE;
                else
                        result->value = EFCT_XPORT_PORT_ONLINE;
                break;

        case EFCT_XPORT_LINK_SPEED:
                result->value = efct_hw_get_link_speed(&efct->hw);
                break;

        case EFCT_XPORT_LINK_STATISTICS:
                memcpy((void *)result, &efct->xport->fc_xport_stats,
                       sizeof(union efct_xport_stats_u));
                break;
        case EFCT_XPORT_LINK_STAT_RESET: {
                /* Create a completion to synchronize the stat reset process */
                init_completion(&result->stats.done);

                /* First reset the link stats */
                rc = efct_hw_get_link_stats(&efct->hw, 0, 1, 1,
                                            efct_xport_link_stats_cb, result);
                if (rc)
                        break;

                /* Wait for completion to be signaled when the cmd completes */
                if (wait_for_completion_interruptible(&result->stats.done)) {
                        /* Undefined failure */
                        efc_log_debug(efct, "sem wait failed\n");
                        rc = -EIO;
                        break;
                }

                /* Next reset the host stats */
                rc = efct_hw_get_host_stats(&efct->hw, 1,
                                            efct_xport_host_stats_cb, result);

                if (rc)
                        break;

                /* Wait for completion to be signaled when the cmd completes */
                if (wait_for_completion_interruptible(&result->stats.done)) {
                        /* Undefined failure */
                        efc_log_debug(efct, "sem wait failed\n");
                        rc = -EIO;
                        break;
                }
                break;
        }
        default:
                rc = -EIO;
                break;
        }

        return rc;
}

static int
efct_get_link_supported_speeds(struct efct *efct)
{
        u32 supported_speeds = 0;
        u32 link_module_type, i;
        struct {
                u32 lmt_speed;
                u32 speed;
        } supported_speed_list[] = {
                {SLI4_LINK_MODULE_TYPE_1GB, FC_PORTSPEED_1GBIT},
                {SLI4_LINK_MODULE_TYPE_2GB, FC_PORTSPEED_2GBIT},
                {SLI4_LINK_MODULE_TYPE_4GB, FC_PORTSPEED_4GBIT},
                {SLI4_LINK_MODULE_TYPE_8GB, FC_PORTSPEED_8GBIT},
                {SLI4_LINK_MODULE_TYPE_16GB, FC_PORTSPEED_16GBIT},
                {SLI4_LINK_MODULE_TYPE_32GB, FC_PORTSPEED_32GBIT},
                {SLI4_LINK_MODULE_TYPE_64GB, FC_PORTSPEED_64GBIT},
                {SLI4_LINK_MODULE_TYPE_128GB, FC_PORTSPEED_128GBIT},
        };

        link_module_type = sli_get_lmt(&efct->hw.sli);

        /* populate link supported speeds */
        for (i = 0; i < ARRAY_SIZE(supported_speed_list); i++) {
                if (link_module_type & supported_speed_list[i].lmt_speed)
                        supported_speeds |= supported_speed_list[i].speed;
        }

        return supported_speeds;
}

int
efct_scsi_new_device(struct efct *efct)
{
        struct Scsi_Host *shost = NULL;
        int error = 0;
        struct efct_vport *vport = NULL;

        shost = scsi_host_alloc(&efct_template, sizeof(*vport));
        if (!shost) {
                efc_log_err(efct, "failed to allocate Scsi_Host struct\n");
                return -ENOMEM;
        }

        /* save shost to initiator-client context */
        efct->shost = shost;

        /* save efct information to shost LLD-specific space */
        vport = (struct efct_vport *)shost->hostdata;
        vport->efct = efct;

        /*
         * Set initial can_queue value to the max SCSI IOs. This is the maximum
         * global queue depth (as opposed to the per-LUN queue depth --
         * .cmd_per_lun This may need to be adjusted for I+T mode.
         */
        shost->can_queue = efct->hw.config.n_io;
        shost->max_cmd_len = 16; /* 16-byte CDBs */
        shost->max_id = 0xffff;
        shost->max_lun = 0xffffffff;

        /*
         * can only accept (from mid-layer) as many SGEs as we've
         * pre-registered
         */
        shost->sg_tablesize = sli_get_max_sgl(&efct->hw.sli);

        /* attach FC Transport template to shost */
        shost->transportt = efct_xport_fc_tt;
        efc_log_debug(efct, "transport template=%p\n", efct_xport_fc_tt);

        /* get pci_dev structure and add host to SCSI ML */
        error = scsi_add_host_with_dma(shost, &efct->pci->dev,
                                       &efct->pci->dev);
        if (error) {
                efc_log_debug(efct, "failed scsi_add_host_with_dma\n");
                return -EIO;
        }

        /* Set symbolic name for host port */
        snprintf(fc_host_symbolic_name(shost),
                 sizeof(fc_host_symbolic_name(shost)),
                     "Emulex %s FV%s DV%s", efct->model,
                     efct->hw.sli.fw_name[0], EFCT_DRIVER_VERSION);

        /* Set host port supported classes */
        fc_host_supported_classes(shost) = FC_COS_CLASS3;

        fc_host_supported_speeds(shost) = efct_get_link_supported_speeds(efct);

        fc_host_node_name(shost) = efct_get_wwnn(&efct->hw);
        fc_host_port_name(shost) = efct_get_wwpn(&efct->hw);
        fc_host_max_npiv_vports(shost) = 128;

        return 0;
}

struct scsi_transport_template *
efct_attach_fc_transport(void)
{
        struct scsi_transport_template *efct_fc_template = NULL;

        efct_fc_template = fc_attach_transport(&efct_xport_functions);

        if (!efct_fc_template)
                pr_err("failed to attach EFCT with fc transport\n");

        return efct_fc_template;
}

struct scsi_transport_template *
efct_attach_vport_fc_transport(void)
{
        struct scsi_transport_template *efct_fc_template = NULL;

        efct_fc_template = fc_attach_transport(&efct_vport_functions);

        if (!efct_fc_template)
                pr_err("failed to attach EFCT with fc transport\n");

        return efct_fc_template;
}

int
efct_scsi_reg_fc_transport(void)
{
        /* attach to appropriate scsi_tranport_* module */
        efct_xport_fc_tt = efct_attach_fc_transport();
        if (!efct_xport_fc_tt) {
                pr_err("%s: failed to attach to scsi_transport_*", __func__);
                return -EIO;
        }

        efct_vport_fc_tt = efct_attach_vport_fc_transport();
        if (!efct_vport_fc_tt) {
                pr_err("%s: failed to attach to scsi_transport_*", __func__);
                efct_release_fc_transport(efct_xport_fc_tt);
                efct_xport_fc_tt = NULL;
                return -EIO;
        }

        return 0;
}

void
efct_scsi_release_fc_transport(void)
{
        /* detach from scsi_transport_* */
        efct_release_fc_transport(efct_xport_fc_tt);
        efct_xport_fc_tt = NULL;
        if (efct_vport_fc_tt)
                efct_release_fc_transport(efct_vport_fc_tt);

        efct_vport_fc_tt = NULL;
}

void
efct_xport_detach(struct efct_xport *xport)
{
        struct efct *efct = xport->efct;

        /* free resources associated with target-server and initiator-client */
        efct_scsi_tgt_del_device(efct);

        efct_scsi_del_device(efct);

        /*Shutdown FC Statistics timer*/
        if (timer_pending(&xport->stats_timer))
                timer_delete(&xport->stats_timer);

        efct_hw_teardown(&efct->hw);

        efct_xport_delete_debugfs(efct);
}

static void
efct_xport_domain_free_cb(struct efc *efc, void *arg)
{
        struct completion *done = arg;

        complete(done);
}

int
efct_xport_control(struct efct_xport *xport, enum efct_xport_ctrl cmd, ...)
{
        u32 rc = 0;
        struct efct *efct = NULL;
        va_list argp;

        efct = xport->efct;

        switch (cmd) {
        case EFCT_XPORT_PORT_ONLINE: {
                /* Bring the port on-line */
                rc = efct_hw_port_control(&efct->hw, EFCT_HW_PORT_INIT, 0,
                                          NULL, NULL);
                if (rc)
                        efc_log_err(efct,
                                    "%s: Can't init port\n", efct->desc);
                else
                        xport->configured_link_state = cmd;
                break;
        }
        case EFCT_XPORT_PORT_OFFLINE: {
                if (efct_hw_port_control(&efct->hw, EFCT_HW_PORT_SHUTDOWN, 0,
                                         NULL, NULL))
                        efc_log_err(efct, "port shutdown failed\n");
                else
                        xport->configured_link_state = cmd;
                break;
        }

        case EFCT_XPORT_SHUTDOWN: {
                struct completion done;
                unsigned long timeout;

                /* if a PHYSDEV reset was performed (e.g. hw dump), will affect
                 * all PCI functions; orderly shutdown won't work,
                 * just force free
                 */
                if (sli_reset_required(&efct->hw.sli)) {
                        struct efc_domain *domain = efct->efcport->domain;

                        if (domain)
                                efc_domain_cb(efct->efcport, EFC_HW_DOMAIN_LOST,
                                              domain);
                } else {
                        efct_hw_port_control(&efct->hw, EFCT_HW_PORT_SHUTDOWN,
                                             0, NULL, NULL);
                }

                init_completion(&done);

                efc_register_domain_free_cb(efct->efcport,
                                            efct_xport_domain_free_cb, &done);

                efc_log_debug(efct, "Waiting %d seconds for domain shutdown\n",
                              (EFC_SHUTDOWN_TIMEOUT_USEC / 1000000));

                timeout = usecs_to_jiffies(EFC_SHUTDOWN_TIMEOUT_USEC);
                if (!wait_for_completion_timeout(&done, timeout)) {
                        efc_log_err(efct, "Domain shutdown timed out!!\n");
                        WARN_ON(1);
                }

                efc_register_domain_free_cb(efct->efcport, NULL, NULL);

                /* Free up any saved virtual ports */
                efc_vport_del_all(efct->efcport);
                break;
        }

        /*
         * Set wwnn for the port. This will be used instead of the default
         * provided by FW.
         */
        case EFCT_XPORT_WWNN_SET: {
                u64 wwnn;

                /* Retrieve arguments */
                va_start(argp, cmd);
                wwnn = va_arg(argp, uint64_t);
                va_end(argp);

                efc_log_debug(efct, " WWNN %016llx\n", wwnn);
                xport->req_wwnn = wwnn;

                break;
        }
        /*
         * Set wwpn for the port. This will be used instead of the default
         * provided by FW.
         */
        case EFCT_XPORT_WWPN_SET: {
                u64 wwpn;

                /* Retrieve arguments */
                va_start(argp, cmd);
                wwpn = va_arg(argp, uint64_t);
                va_end(argp);

                efc_log_debug(efct, " WWPN %016llx\n", wwpn);
                xport->req_wwpn = wwpn;

                break;
        }

        default:
                break;
        }
        return rc;
}

void
efct_xport_free(struct efct_xport *xport)
{
        if (xport) {
                efct_io_pool_free(xport->io_pool);

                kfree(xport);
        }
}

void
efct_release_fc_transport(struct scsi_transport_template *transport_template)
{
        if (transport_template)
                pr_err("releasing transport layer\n");

        /* Releasing FC transport */
        fc_release_transport(transport_template);
}

static void
efct_xport_remove_host(struct Scsi_Host *shost)
{
        fc_remove_host(shost);
}

void
efct_scsi_del_device(struct efct *efct)
{
        if (!efct->shost)
                return;

        efc_log_debug(efct, "Unregistering with Transport Layer\n");
        efct_xport_remove_host(efct->shost);
        efc_log_debug(efct, "Unregistering with SCSI Midlayer\n");
        scsi_remove_host(efct->shost);
        scsi_host_put(efct->shost);
        efct->shost = NULL;
}

static void
efct_get_host_port_id(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        struct efc *efc = efct->efcport;
        struct efc_nport *nport;

        if (efc->domain && efc->domain->nport) {
                nport = efc->domain->nport;
                fc_host_port_id(shost) = nport->fc_id;
        }
}

static void
efct_get_host_port_type(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        struct efc *efc = efct->efcport;
        int type = FC_PORTTYPE_UNKNOWN;

        if (efc->domain && efc->domain->nport) {
                if (efc->domain->is_loop) {
                        type = FC_PORTTYPE_LPORT;
                } else {
                        struct efc_nport *nport = efc->domain->nport;

                        if (nport->is_vport)
                                type = FC_PORTTYPE_NPIV;
                        else if (nport->topology == EFC_NPORT_TOPO_P2P)
                                type = FC_PORTTYPE_PTP;
                        else if (nport->topology == EFC_NPORT_TOPO_UNKNOWN)
                                type = FC_PORTTYPE_UNKNOWN;
                        else
                                type = FC_PORTTYPE_NPORT;
                }
        }
        fc_host_port_type(shost) = type;
}

static void
efct_get_host_vport_type(struct Scsi_Host *shost)
{
        fc_host_port_type(shost) = FC_PORTTYPE_NPIV;
}

static void
efct_get_host_port_state(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        union efct_xport_stats_u status;
        int rc;

        rc = efct_xport_status(efct->xport, EFCT_XPORT_PORT_STATUS, &status);
        if ((!rc) && (status.value == EFCT_XPORT_PORT_ONLINE))
                fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
        else
                fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
}

static void
efct_get_host_speed(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        struct efc *efc = efct->efcport;
        union efct_xport_stats_u speed;
        u32 fc_speed = FC_PORTSPEED_UNKNOWN;
        int rc;

        if (!efc->domain || !efc->domain->nport) {
                fc_host_speed(shost) = fc_speed;
                return;
        }

        rc = efct_xport_status(efct->xport, EFCT_XPORT_LINK_SPEED, &speed);
        if (!rc) {
                switch (speed.value) {
                case 1000:
                        fc_speed = FC_PORTSPEED_1GBIT;
                        break;
                case 2000:
                        fc_speed = FC_PORTSPEED_2GBIT;
                        break;
                case 4000:
                        fc_speed = FC_PORTSPEED_4GBIT;
                        break;
                case 8000:
                        fc_speed = FC_PORTSPEED_8GBIT;
                        break;
                case 10000:
                        fc_speed = FC_PORTSPEED_10GBIT;
                        break;
                case 16000:
                        fc_speed = FC_PORTSPEED_16GBIT;
                        break;
                case 32000:
                        fc_speed = FC_PORTSPEED_32GBIT;
                        break;
                case 64000:
                        fc_speed = FC_PORTSPEED_64GBIT;
                        break;
                case 128000:
                        fc_speed = FC_PORTSPEED_128GBIT;
                        break;
                }
        }

        fc_host_speed(shost) = fc_speed;
}

static void
efct_get_host_fabric_name(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        struct efc *efc = efct->efcport;

        if (efc->domain) {
                struct fc_els_flogi  *sp =
                        (struct fc_els_flogi  *)
                                efc->domain->flogi_service_params;

                fc_host_fabric_name(shost) = be64_to_cpu(sp->fl_wwnn);
        }
}

static struct fc_host_statistics *
efct_get_stats(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        union efct_xport_stats_u stats;
        struct efct_xport *xport = efct->xport;
        int rc = 0;

        rc = efct_xport_status(xport, EFCT_XPORT_LINK_STATISTICS, &stats);
        if (rc) {
                pr_err("efct_xport_status returned non 0 - %d\n", rc);
                return NULL;
        }

        vport->fc_host_stats.loss_of_sync_count =
                stats.stats.link_stats.loss_of_sync_error_count;
        vport->fc_host_stats.link_failure_count =
                stats.stats.link_stats.link_failure_error_count;
        vport->fc_host_stats.prim_seq_protocol_err_count =
                stats.stats.link_stats.primitive_sequence_error_count;
        vport->fc_host_stats.invalid_tx_word_count =
                stats.stats.link_stats.invalid_transmission_word_error_count;
        vport->fc_host_stats.invalid_crc_count =
                stats.stats.link_stats.crc_error_count;
        /* mbox returns kbyte count so we need to convert to words */
        vport->fc_host_stats.tx_words =
                stats.stats.host_stats.transmit_kbyte_count * 256;
        /* mbox returns kbyte count so we need to convert to words */
        vport->fc_host_stats.rx_words =
                stats.stats.host_stats.receive_kbyte_count * 256;
        vport->fc_host_stats.tx_frames =
                stats.stats.host_stats.transmit_frame_count;
        vport->fc_host_stats.rx_frames =
                stats.stats.host_stats.receive_frame_count;

        vport->fc_host_stats.fcp_input_requests =
                        xport->fcp_stats.input_requests;
        vport->fc_host_stats.fcp_output_requests =
                        xport->fcp_stats.output_requests;
        vport->fc_host_stats.fcp_output_megabytes =
                        xport->fcp_stats.output_bytes >> 20;
        vport->fc_host_stats.fcp_input_megabytes =
                        xport->fcp_stats.input_bytes >> 20;
        vport->fc_host_stats.fcp_control_requests =
                        xport->fcp_stats.control_requests;

        return &vport->fc_host_stats;
}

static void
efct_reset_stats(struct Scsi_Host *shost)
{
        struct efct_vport *vport = (struct efct_vport *)shost->hostdata;
        struct efct *efct = vport->efct;
        /* argument has no purpose for this action */
        union efct_xport_stats_u dummy;
        int rc;

        rc = efct_xport_status(efct->xport, EFCT_XPORT_LINK_STAT_RESET, &dummy);
        if (rc)
                pr_err("efct_xport_status returned non 0 - %d\n", rc);
}

static int
efct_issue_lip(struct Scsi_Host *shost)
{
        struct efct_vport *vport =
                        shost ? (struct efct_vport *)shost->hostdata : NULL;
        struct efct *efct = vport ? vport->efct : NULL;

        if (!shost || !vport || !efct) {
                pr_err("%s: shost=%p vport=%p efct=%p\n", __func__,
                       shost, vport, efct);
                return -EPERM;
        }

        /*
         * Bring the link down gracefully then re-init the link.
         * The firmware will re-initialize the Fibre Channel interface as
         * required. It does not issue a LIP.
         */

        if (efct_xport_control(efct->xport, EFCT_XPORT_PORT_OFFLINE))
                efc_log_debug(efct, "EFCT_XPORT_PORT_OFFLINE failed\n");

        if (efct_xport_control(efct->xport, EFCT_XPORT_PORT_ONLINE))
                efc_log_debug(efct, "EFCT_XPORT_PORT_ONLINE failed\n");

        return 0;
}

struct efct_vport *
efct_scsi_new_vport(struct efct *efct, struct device *dev)
{
        struct Scsi_Host *shost = NULL;
        int error = 0;
        struct efct_vport *vport = NULL;

        shost = scsi_host_alloc(&efct_template, sizeof(*vport));
        if (!shost) {
                efc_log_err(efct, "failed to allocate Scsi_Host struct\n");
                return NULL;
        }

        /* save efct information to shost LLD-specific space */
        vport = (struct efct_vport *)shost->hostdata;
        vport->efct = efct;
        vport->is_vport = true;

        shost->can_queue = efct->hw.config.n_io;
        shost->max_cmd_len = 16; /* 16-byte CDBs */
        shost->max_id = 0xffff;
        shost->max_lun = 0xffffffff;

        /* can only accept (from mid-layer) as many SGEs as we've pre-regited*/
        shost->sg_tablesize = sli_get_max_sgl(&efct->hw.sli);

        /* attach FC Transport template to shost */
        shost->transportt = efct_vport_fc_tt;
        efc_log_debug(efct, "vport transport template=%p\n",
                      efct_vport_fc_tt);

        /* get pci_dev structure and add host to SCSI ML */
        error = scsi_add_host_with_dma(shost, dev, &efct->pci->dev);
        if (error) {
                efc_log_debug(efct, "failed scsi_add_host_with_dma\n");
                return NULL;
        }

        /* Set symbolic name for host port */
        snprintf(fc_host_symbolic_name(shost),
                 sizeof(fc_host_symbolic_name(shost)),
                 "Emulex %s FV%s DV%s", efct->model, efct->hw.sli.fw_name[0],
                 EFCT_DRIVER_VERSION);

        /* Set host port supported classes */
        fc_host_supported_classes(shost) = FC_COS_CLASS3;

        fc_host_supported_speeds(shost) = efct_get_link_supported_speeds(efct);
        vport->shost = shost;

        return vport;
}

int efct_scsi_del_vport(struct efct *efct, struct Scsi_Host *shost)
{
        if (shost) {
                efc_log_debug(efct,
                              "Unregistering vport with Transport Layer\n");
                efct_xport_remove_host(shost);
                efc_log_debug(efct, "Unregistering vport with SCSI Midlayer\n");
                scsi_remove_host(shost);
                scsi_host_put(shost);
                return 0;
        }
        return -EIO;
}

static int
efct_vport_create(struct fc_vport *fc_vport, bool disable)
{
        struct Scsi_Host *shost = fc_vport ? fc_vport->shost : NULL;
        struct efct_vport *pport = shost ?
                                        (struct efct_vport *)shost->hostdata :
                                        NULL;
        struct efct *efct = pport ? pport->efct : NULL;
        struct efct_vport *vport = NULL;

        if (!fc_vport || !shost || !efct)
                goto fail;

        vport = efct_scsi_new_vport(efct, &fc_vport->dev);
        if (!vport) {
                efc_log_err(efct, "failed to create vport\n");
                goto fail;
        }

        vport->fc_vport = fc_vport;
        vport->npiv_wwpn = fc_vport->port_name;
        vport->npiv_wwnn = fc_vport->node_name;
        fc_host_node_name(vport->shost) = vport->npiv_wwnn;
        fc_host_port_name(vport->shost) = vport->npiv_wwpn;
        *(struct efct_vport **)fc_vport->dd_data = vport;

        return 0;

fail:
        return -EIO;
}

static int
efct_vport_delete(struct fc_vport *fc_vport)
{
        struct efct_vport *vport = *(struct efct_vport **)fc_vport->dd_data;
        struct Scsi_Host *shost = vport ? vport->shost : NULL;
        struct efct *efct = vport ? vport->efct : NULL;
        int rc;

        rc = efct_scsi_del_vport(efct, shost);

        if (rc)
                pr_err("%s: vport delete failed\n", __func__);

        return rc;
}

static int
efct_vport_disable(struct fc_vport *fc_vport, bool disable)
{
        return 0;
}

static struct fc_function_template efct_xport_functions = {
        .get_host_port_id = efct_get_host_port_id,
        .get_host_port_type = efct_get_host_port_type,
        .get_host_port_state = efct_get_host_port_state,
        .get_host_speed = efct_get_host_speed,
        .get_host_fabric_name = efct_get_host_fabric_name,

        .get_fc_host_stats = efct_get_stats,
        .reset_fc_host_stats = efct_reset_stats,

        .issue_fc_host_lip = efct_issue_lip,

        .vport_disable = efct_vport_disable,

        /* allocation lengths for host-specific data */
        .dd_fcrport_size = sizeof(struct efct_rport_data),
        .dd_fcvport_size = 128, /* should be sizeof(...) */

        /* remote port fixed attributes */
        .show_rport_maxframe_size = 1,
        .show_rport_supported_classes = 1,
        .show_rport_dev_loss_tmo = 1,

        /* target dynamic attributes */
        .show_starget_node_name = 1,
        .show_starget_port_name = 1,
        .show_starget_port_id = 1,

        /* host fixed attributes */
        .show_host_node_name = 1,
        .show_host_port_name = 1,
        .show_host_supported_classes = 1,
        .show_host_supported_fc4s = 1,
        .show_host_supported_speeds = 1,
        .show_host_maxframe_size = 1,

        /* host dynamic attributes */
        .show_host_port_id = 1,
        .show_host_port_type = 1,
        .show_host_port_state = 1,
        /* active_fc4s is shown but doesn't change (thus no get function) */
        .show_host_active_fc4s = 1,
        .show_host_speed = 1,
        .show_host_fabric_name = 1,
        .show_host_symbolic_name = 1,
        .vport_create = efct_vport_create,
        .vport_delete = efct_vport_delete,
};

static struct fc_function_template efct_vport_functions = {
        .get_host_port_id = efct_get_host_port_id,
        .get_host_port_type = efct_get_host_vport_type,
        .get_host_port_state = efct_get_host_port_state,
        .get_host_speed = efct_get_host_speed,
        .get_host_fabric_name = efct_get_host_fabric_name,

        .get_fc_host_stats = efct_get_stats,
        .reset_fc_host_stats = efct_reset_stats,

        .issue_fc_host_lip = efct_issue_lip,

        /* allocation lengths for host-specific data */
        .dd_fcrport_size = sizeof(struct efct_rport_data),
        .dd_fcvport_size = 128, /* should be sizeof(...) */

        /* remote port fixed attributes */
        .show_rport_maxframe_size = 1,
        .show_rport_supported_classes = 1,
        .show_rport_dev_loss_tmo = 1,

        /* target dynamic attributes */
        .show_starget_node_name = 1,
        .show_starget_port_name = 1,
        .show_starget_port_id = 1,

        /* host fixed attributes */
        .show_host_node_name = 1,
        .show_host_port_name = 1,
        .show_host_supported_classes = 1,
        .show_host_supported_fc4s = 1,
        .show_host_supported_speeds = 1,
        .show_host_maxframe_size = 1,

        /* host dynamic attributes */
        .show_host_port_id = 1,
        .show_host_port_type = 1,
        .show_host_port_state = 1,
        /* active_fc4s is shown but doesn't change (thus no get function) */
        .show_host_active_fc4s = 1,
        .show_host_speed = 1,
        .show_host_fabric_name = 1,
        .show_host_symbolic_name = 1,
};