diff options
Diffstat (limited to 'drivers/scsi/elx/efct/efct_xport.c')
-rw-r--r-- | drivers/scsi/elx/efct/efct_xport.c | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/drivers/scsi/elx/efct/efct_xport.c b/drivers/scsi/elx/efct/efct_xport.c new file mode 100644 index 000000000..9495cedcc --- /dev/null +++ b/drivers/scsi/elx/efct/efct_xport.c @@ -0,0 +1,1111 @@ +// 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 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(sizeof(*xport), GFP_KERNEL); + 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 = from_timer(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)) + del_timer(&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, +}; |