+/* External call to request an attach for a sport, given an FC_ID */
+int
+efc_sport_attach(struct efc_sli_port *sport, u32 fc_id)
+{
+ int rc;
+ struct efc_node *node;
+ struct efc *efc = sport->efc;
+
+ /* Set our lookup */
+ rc = xa_err(xa_store(&sport->domain->lookup, fc_id, sport, GFP_ATOMIC));
+ if (rc) {
+ efc_log_err(efc, "Sport lookup store failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Update our display_name */
+ efc_node_fcid_display(fc_id, sport->display_name,
+ sizeof(sport->display_name));
+
+ list_for_each_entry(node, &sport->node_list, list_entry) {
+ efc_node_update_display_name(node);
+ }
+
+ efc_log_debug(sport->efc, "[%s] attach sport: fc_id x%06x\n",
+ sport->display_name, fc_id);
+
+ rc = efc->tt.hw_port_attach(efc, sport, fc_id);
+ if (rc != EFC_HW_RTN_SUCCESS) {
+ efc_log_err(sport->efc,
+ "efc_hw_port_attach failed: %d\n", rc);
+ return EFC_FAIL;
+ }
+ return EFC_SUCCESS;
+}
+
+static void
+efc_sport_shutdown(struct efc_sli_port *sport)
+{
+ struct efc *efc = sport->efc;
+ struct efc_node *node;
+ struct efc_node *node_next;
+
+ list_for_each_entry_safe(node, node_next,
+ &sport->node_list, list_entry) {
+ if (!(node->rnode.fc_id == FC_FID_FLOGI && sport->is_vport)) {
+ efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL);
+ continue;
+ }
+
+ /*
+ * If this is a vport, logout of the fabric
+ * controller so that it deletes the vport
+ * on the switch.
+ */
+ /* if link is down, don't send logo */
+ if (efc->link_status == EFC_LINK_STATUS_DOWN) {
+ efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL);
+ } else {
+ efc_log_debug(efc,
+ "[%s] sport shutdown vport, sending logo to node\n",
+ node->display_name);
+
+ if (efc->tt.els_send(efc, node, ELS_LOGO,
+ EFC_FC_FLOGI_TIMEOUT_SEC,
+ EFC_FC_ELS_DEFAULT_RETRIES)) {
+ /* sent LOGO, wait for response */
+ efc_node_transition(node,
+ __efc_d_wait_logo_rsp,
+ NULL);
+ continue;
+ }
+
+ /*
+ * failed to send LOGO,
+ * go ahead and cleanup node anyways
+ */
+ node_printf(node, "Failed to send LOGO\n");
+ efc_node_post_event(node,
+ EFC_EVT_SHUTDOWN_EXPLICIT_LOGO,
+ NULL);
+ }
+ }
+}
+
+/* Clear the sport reference in the vport specification */
+static void
+efc_vport_link_down(struct efc_sli_port *sport)
+{
+ struct efc *efc = sport->efc;
+ struct efc_vport_spec *vport;
+
+ list_for_each_entry(vport, &efc->vport_list, list_entry) {
+ if (vport->sport == sport) {
+ vport->sport = NULL;
+ break;
+ }
+ }
+}
+
+static void *
+__efc_sport_common(const char *funcname, struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc_domain *domain = sport->domain;
+ struct efc *efc = sport->efc;
+
+ switch (evt) {
+ case EFC_EVT_ENTER:
+ case EFC_EVT_REENTER:
+ case EFC_EVT_EXIT:
+ case EFC_EVT_ALL_CHILD_NODES_FREE:
+ break;
+ case EFC_EVT_SPORT_ATTACH_OK:
+ efc_sm_transition(ctx, __efc_sport_attached, NULL);
+ break;
+ case EFC_EVT_SHUTDOWN: {
+ int node_list_empty;
+
+ /* Flag this sport as shutting down */
+ sport->shutting_down = true;
+
+ if (sport->is_vport)
+ efc_vport_link_down(sport);
+
+ node_list_empty = list_empty(&sport->node_list);
+
+ if (node_list_empty) {
+ /* Remove the sport from the domain's lookup table */
+ xa_erase(&domain->lookup, sport->fc_id);
+ efc_sm_transition(ctx, __efc_sport_wait_port_free,
+ NULL);
+ if (efc->tt.hw_port_free(efc, sport)) {
+ efc_log_test(sport->efc,
+ "efc_hw_port_free failed\n");
+ /* Not much we can do, free the sport anyways */
+ efc_sport_free(sport);
+ }
+ } else {
+ /* sm: node list is not empty / shutdown nodes */
+ efc_sm_transition(ctx,
+ __efc_sport_wait_shutdown, NULL);
+ efc_sport_shutdown(sport);
+ }
+ break;
+ }
+ default:
+ efc_log_test(sport->efc, "[%s] %-20s %-20s not handled\n",
+ sport->display_name, funcname,
+ efc_sm_event_name(evt));
+ break;
+ }
+
+ return NULL;
+}
+
+/* SLI port state machine: Physical sport allocated */
+void *
+__efc_sport_allocated(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc_domain *domain = sport->domain;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ /* the physical sport is attached */
+ case EFC_EVT_SPORT_ATTACH_OK:
+ WARN_ON(sport != domain->sport);
+ efc_sm_transition(ctx, __efc_sport_attached, NULL);
+ break;
+
+ case EFC_EVT_SPORT_ALLOC_OK:
+ /* ignore */
+ break;
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+/* SLI port state machine: Handle initial virtual port events */
+void *
+__efc_sport_vport_init(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc *efc = sport->efc;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_ENTER: {
+ __be64 be_wwpn = cpu_to_be64(sport->wwpn);
+
+ if (sport->wwpn == 0)
+ efc_log_debug(efc, "vport: letting f/w select WWN\n");
+
+ if (sport->fc_id != U32_MAX) {
+ efc_log_debug(efc, "vport: hard coding port id: %x\n",
+ sport->fc_id);
+ }
+
+ efc_sm_transition(ctx, __efc_sport_vport_wait_alloc, NULL);
+ /* If wwpn is zero, then we'll let the f/w */
+ if (efc->tt.hw_port_alloc(efc, sport, sport->domain,
+ sport->wwpn == 0 ? NULL :
+ (uint8_t *)&be_wwpn)) {
+ efc_log_err(efc, "Can't allocate port\n");
+ break;
+ }
+
+ break;
+ }
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+/**
+ * SLI port state machine:
+ * Wait for the HW SLI port allocation to complete
+ */
+void *
+__efc_sport_vport_wait_alloc(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc *efc = sport->efc;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_SPORT_ALLOC_OK: {
+ struct fc_els_flogi *sp;
+ struct efc_node *fabric;
+
+ sp = (struct fc_els_flogi *)sport->service_params;
+ /*
+ * If we let f/w assign wwn's,
+ * then sport wwn's with those returned by hw
+ */
+ if (sport->wwnn == 0) {
+ sport->wwnn = be64_to_cpu(sport->sli_wwnn);
+ sport->wwpn = be64_to_cpu(sport->sli_wwpn);
+ snprintf(sport->wwnn_str, sizeof(sport->wwnn_str),
+ "%016llX", sport->wwpn);
+ }
+
+ /* Update the sport's service parameters */
+ sp->fl_wwpn = cpu_to_be64(sport->wwpn);
+ sp->fl_wwnn = cpu_to_be64(sport->wwnn);
+
+ /*
+ * if sport->fc_id is uninitialized,
+ * then request that the fabric node use FDISC
+ * to find an fc_id.
+ * Otherwise we're restoring vports, or we're in
+ * fabric emulation mode, so attach the fc_id
+ */
+ if (sport->fc_id == U32_MAX) {
+ fabric = efc_node_alloc(sport, FC_FID_FLOGI, false,
+ false);
+ if (!fabric) {
+ efc_log_err(efc, "efc_node_alloc() failed\n");
+ return NULL;
+ }
+ efc_node_transition(fabric, __efc_vport_fabric_init,
+ NULL);
+ } else {
+ snprintf(sport->wwnn_str, sizeof(sport->wwnn_str),
+ "%016llX", sport->wwpn);
+ efc_sport_attach(sport, sport->fc_id);
+ }
+ efc_sm_transition(ctx, __efc_sport_vport_allocated, NULL);
+ break;
+ }
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+/**
+ * SLI port state machine: virtual sport allocated.
+ *
+ * This state is entered after the sport is allocated;
+ * it then waits for a fabric node
+ * FDISC to complete, which requests a sport attach.
+ * The sport attach complete is handled in this state.
+ */
+
+void *
+__efc_sport_vport_allocated(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc *efc = sport->efc;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_SPORT_ATTACH_OK: {
+ struct efc_node *node;
+
+ /* Find our fabric node, and forward this event */
+ node = efc_node_find(sport, FC_FID_FLOGI);
+ if (!node) {
+ efc_log_test(efc, "can't find node %06x\n",
+ FC_FID_FLOGI);
+ break;
+ }
+ /* sm: / forward sport attach to fabric node */
+ efc_node_post_event(node, evt, NULL);
+ efc_sm_transition(ctx, __efc_sport_attached, NULL);
+ break;
+ }
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+static void
+efc_vport_update_spec(struct efc_sli_port *sport)
+{
+ struct efc *efc = sport->efc;
+ struct efc_vport_spec *vport;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&efc->vport_lock, flags);
+ list_for_each_entry(vport, &efc->vport_list, list_entry) {
+ if (vport->sport == sport) {
+ vport->wwnn = sport->wwnn;
+ vport->wwpn = sport->wwpn;
+ vport->tgt_data = sport->tgt_data;
+ vport->ini_data = sport->ini_data;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+}
+
+/* State entered after the sport attach has completed */
+void *
+__efc_sport_attached(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc *efc = sport->efc;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_ENTER: {
+ struct efc_node *node;
+
+ efc_log_debug(efc,
+ "[%s] SPORT attached WWPN %016llX WWNN %016llX\n",
+ sport->display_name,
+ sport->wwpn, sport->wwnn);
+
+ list_for_each_entry(node, &sport->node_list, list_entry) {
+ efc_node_update_display_name(node);
+ }
+
+ sport->tgt_id = sport->fc_id;
+
+ efc->tt.new_sport(efc, sport);
+
+ /*
+ * Update the vport (if its not the physical sport)
+ * parameters
+ */
+ if (sport->is_vport)
+ efc_vport_update_spec(sport);
+ break;
+ }
+
+ case EFC_EVT_EXIT:
+ efc_log_debug(efc,
+ "[%s] SPORT deattached WWPN %016llX WWNN %016llX\n",
+ sport->display_name,
+ sport->wwpn, sport->wwnn);
+
+ efc->tt.del_sport(efc, sport);
+ break;
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+
+/* SLI port state machine: Wait for the node shutdowns to complete */
+void *
+__efc_sport_wait_shutdown(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+ struct efc_domain *domain = sport->domain;
+ struct efc *efc = sport->efc;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_SPORT_ALLOC_OK:
+ case EFC_EVT_SPORT_ALLOC_FAIL:
+ case EFC_EVT_SPORT_ATTACH_OK:
+ case EFC_EVT_SPORT_ATTACH_FAIL:
+ /* ignore these events - just wait for the all free event */
+ break;
+
+ case EFC_EVT_ALL_CHILD_NODES_FREE: {
+ /*
+ * Remove the sport from the domain's
+ * sparse vector lookup table
+ */
+ xa_erase(&domain->lookup, sport->fc_id);
+ efc_sm_transition(ctx, __efc_sport_wait_port_free, NULL);
+ if (efc->tt.hw_port_free(efc, sport)) {
+ efc_log_err(sport->efc, "efc_hw_port_free failed\n");
+ /* Not much we can do, free the sport anyways */
+ efc_sport_free(sport);
+ }
+ break;
+ }
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+/* SLI port state machine: Wait for the HW's port free to complete */
+void *
+__efc_sport_wait_port_free(struct efc_sm_ctx *ctx,
+ enum efc_sm_event evt, void *arg)
+{
+ struct efc_sli_port *sport = ctx->app;
+
+ sport_sm_trace(sport);
+
+ switch (evt) {
+ case EFC_EVT_SPORT_ATTACH_OK:
+ /* Ignore as we are waiting for the free CB */
+ break;
+ case EFC_EVT_SPORT_FREE_OK: {
+ /* All done, free myself */
+ /* sm: / efc_sport_free */
+ efc_sport_free(sport);
+ break;
+ }
+ default:
+ __efc_sport_common(__func__, ctx, evt, arg);
+ return NULL;
+ }
+ return NULL;
+}
+
+static int
+efc_vport_sport_alloc(struct efc_domain *domain, struct efc_vport_spec *vport)
+{
+ struct efc_sli_port *sport;
+
+ sport = efc_sport_alloc(domain, vport->wwpn,
+ vport->wwnn, vport->fc_id,
+ vport->enable_ini,
+ vport->enable_tgt);
+ vport->sport = sport;
+ if (!sport)
+ return EFC_FAIL;
+
+ sport->is_vport = true;
+ sport->tgt_data = vport->tgt_data;
+ sport->ini_data = vport->ini_data;
+
+ efc_sm_transition(&sport->sm, __efc_sport_vport_init, NULL);
+
+ return EFC_SUCCESS;
+}
+
+/* Use the vport specification to find the associated vports and start them */
+int
+efc_vport_start(struct efc_domain *domain)
+{
+ struct efc *efc = domain->efc;
+ struct efc_vport_spec *vport;
+ struct efc_vport_spec *next;
+ int rc = EFC_SUCCESS;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&efc->vport_lock, flags);
+ list_for_each_entry_safe(vport, next, &efc->vport_list, list_entry) {
+ if (!vport->sport) {
+ if (efc_vport_sport_alloc(domain, vport))
+ rc = EFC_FAIL;
+ }
+ }
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+
+ return rc;
+}
+
+/* Allocate a new virtual SLI port */
+int
+efc_sport_vport_new(struct efc_domain *domain, uint64_t wwpn, uint64_t wwnn,
+ u32 fc_id, bool ini, bool tgt, void *tgt_data,
+ void *ini_data)
+{
+ struct efc *efc = domain->efc;
+ struct efc_vport_spec *vport;
+ int rc = EFC_SUCCESS;
+ unsigned long flags = 0;
+
+ if (ini && domain->efc->enable_ini == 0) {
+ efc_log_debug(efc,
+ "driver initiator functionality not enabled\n");
+ return EFC_FAIL;
+ }
+
+ if (tgt && domain->efc->enable_tgt == 0) {
+ efc_log_debug(efc,
+ "driver target functionality not enabled\n");
+ return EFC_FAIL;
+ }
+
+ /*
+ * Create a vport spec if we need to recreate
+ * this vport after a link up event
+ */
+ vport = efc_vport_create_spec(domain->efc, wwnn, wwpn, fc_id, ini, tgt,
+ tgt_data, ini_data);
+ if (!vport) {
+ efc_log_err(efc, "failed to create vport object entry\n");
+ return EFC_FAIL;
+ }
+
+ spin_lock_irqsave(&efc->lock, flags);
+ rc = efc_vport_sport_alloc(domain, vport);
+ spin_unlock_irqrestore(&efc->lock, flags);
+
+ return rc;
+}
+
+/* Remove a previously-allocated virtual port */
+int
+efc_sport_vport_del(struct efc *efc, struct efc_domain *domain,
+ u64 wwpn, uint64_t wwnn)
+{
+ struct efc_sli_port *sport;
+ int found = 0;
+ struct efc_vport_spec *vport;
+ struct efc_vport_spec *next;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&efc->vport_lock, flags);
+ /* walk the efc_vport_list and remove from there */
+ list_for_each_entry_safe(vport, next, &efc->vport_list, list_entry) {
+ if (vport->wwpn == wwpn && vport->wwnn == wwnn) {
+ list_del(&vport->list_entry);
+ kfree(vport);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+
+ if (!domain) {
+ /* No domain means no sport to look for */
+ return EFC_SUCCESS;
+ }
+
+ spin_lock_irqsave(&efc->lock, flags);
+ list_for_each_entry(sport, &domain->sport_list, list_entry) {
+ if (sport->wwpn == wwpn && sport->wwnn == wwnn) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ /* Shutdown this SPORT */
+ efc_sm_post_event(&sport->sm, EFC_EVT_SHUTDOWN, NULL);
+ }
+ spin_unlock_irqrestore(&efc->lock, flags);
+ return EFC_SUCCESS;
+}
+
+/* Force free all saved vports */
+void
+efc_vport_del_all(struct efc *efc)
+{
+ struct efc_vport_spec *vport;
+ struct efc_vport_spec *next;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&efc->vport_lock, flags);
+ list_for_each_entry_safe(vport, next, &efc->vport_list, list_entry) {
+ list_del(&vport->list_entry);
+ kfree(vport);
+ }
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+}
+
+/**
+ * Create a saved vport entry.
+ *
+ * A saved vport entry is added to the vport list,
+ * which is restored following a link up.
+ * This function is used to allow vports to be created the first time
+ * the link comes up without having to go through the ioctl() API.
+ */
+
+struct efc_vport_spec *
+efc_vport_create_spec(struct efc *efc, uint64_t wwnn, uint64_t wwpn,
+ u32 fc_id, bool enable_ini,
+ bool enable_tgt, void *tgt_data, void *ini_data)
+{
+ struct efc_vport_spec *vport;
+ unsigned long flags = 0;
+
+ /*
+ * walk the efc_vport_list and return failure
+ * if a valid(vport with non zero WWPN and WWNN) vport entry
+ * is already created
+ */
+ spin_lock_irqsave(&efc->vport_lock, flags);
+ list_for_each_entry(vport, &efc->vport_list, list_entry) {
+ if ((wwpn && vport->wwpn == wwpn) &&
+ (wwnn && vport->wwnn == wwnn)) {
+ efc_log_err(efc,
+ "Failed: VPORT %016llX %016llX already allocated\n",
+ wwnn, wwpn);
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+ return NULL;
+ }
+ }
+
+ vport = kzalloc(sizeof(*vport), GFP_ATOMIC);
+ if (!vport) {
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+ return NULL;
+ }
+
+ vport->wwnn = wwnn;
+ vport->wwpn = wwpn;
+ vport->fc_id = fc_id;
+ vport->enable_tgt = enable_tgt;
+ vport->enable_ini = enable_ini;
+ vport->tgt_data = tgt_data;
+ vport->ini_data = ini_data;
+
+ INIT_LIST_HEAD(&vport->list_entry);
+ list_add_tail(&vport->list_entry, &efc->vport_list);
+ spin_unlock_irqrestore(&efc->vport_lock, flags);
+ return vport;