// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Broadcom. All Rights Reserved. The term * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. */ /* * NPORT * * Port object for physical port and NPIV ports. */ /* * NPORT REFERENCE COUNTING * * A nport reference should be taken when: * - an nport is allocated * - a vport populates associated nport * - a remote node is allocated * - a unsolicited frame is processed * The reference should be dropped when: * - the unsolicited frame processesing is done * - the remote node is removed * - the vport is removed * - the nport is removed */ #include "efc.h" void efc_nport_cb(void *arg, int event, void *data) { struct efc *efc = arg; struct efc_nport *nport = data; unsigned long flags = 0; efc_log_debug(efc, "nport event: %s\n", efc_sm_event_name(event)); spin_lock_irqsave(&efc->lock, flags); efc_sm_post_event(&nport->sm, event, NULL); spin_unlock_irqrestore(&efc->lock, flags); } static struct efc_nport * efc_nport_find_wwn(struct efc_domain *domain, uint64_t wwnn, uint64_t wwpn) { struct efc_nport *nport = NULL; /* Find a nport, given the WWNN and WWPN */ list_for_each_entry(nport, &domain->nport_list, list_entry) { if (nport->wwnn == wwnn && nport->wwpn == wwpn) return nport; } return NULL; } static void _efc_nport_free(struct kref *arg) { struct efc_nport *nport = container_of(arg, struct efc_nport, ref); kfree(nport); } struct efc_nport * efc_nport_alloc(struct efc_domain *domain, uint64_t wwpn, uint64_t wwnn, u32 fc_id, bool enable_ini, bool enable_tgt) { struct efc_nport *nport; if (domain->efc->enable_ini) enable_ini = 0; /* Return a failure if this nport has already been allocated */ if ((wwpn != 0) || (wwnn != 0)) { nport = efc_nport_find_wwn(domain, wwnn, wwpn); if (nport) { efc_log_err(domain->efc, "NPORT %016llX %016llX already allocated\n", wwnn, wwpn); return NULL; } } nport = kzalloc(sizeof(*nport), GFP_ATOMIC); if (!nport) return nport; /* initialize refcount */ kref_init(&nport->ref); nport->release = _efc_nport_free; nport->efc = domain->efc; snprintf(nport->display_name, sizeof(nport->display_name), "------"); nport->domain = domain; xa_init(&nport->lookup); nport->instance_index = domain->nport_count++; nport->sm.app = nport; nport->enable_ini = enable_ini; nport->enable_tgt = enable_tgt; nport->enable_rscn = (nport->enable_ini || (nport->enable_tgt && enable_target_rscn(nport->efc))); /* Copy service parameters from domain */ memcpy(nport->service_params, domain->service_params, sizeof(struct fc_els_flogi)); /* Update requested fc_id */ nport->fc_id = fc_id; /* Update the nport's service parameters for the new wwn's */ nport->wwpn = wwpn; nport->wwnn = wwnn; snprintf(nport->wwnn_str, sizeof(nport->wwnn_str), "%016llX", (unsigned long long)wwnn); /* * if this is the "first" nport of the domain, * then make it the "phys" nport */ if (list_empty(&domain->nport_list)) domain->nport = nport; INIT_LIST_HEAD(&nport->list_entry); list_add_tail(&nport->list_entry, &domain->nport_list); kref_get(&domain->ref); efc_log_debug(domain->efc, "New Nport [%s]\n", nport->display_name); return nport; } void efc_nport_free(struct efc_nport *nport) { struct efc_domain *domain; if (!nport) return; domain = nport->domain; efc_log_debug(domain->efc, "[%s] free nport\n", nport->display_name); list_del(&nport->list_entry); /* * if this is the physical nport, * then clear it out of the domain */ if (nport == domain->nport) domain->nport = NULL; xa_destroy(&nport->lookup); xa_erase(&domain->lookup, nport->fc_id); if (list_empty(&domain->nport_list)) efc_domain_post_event(domain, EFC_EVT_ALL_CHILD_NODES_FREE, NULL); kref_put(&domain->ref, domain->release); kref_put(&nport->ref, nport->release); } struct efc_nport * efc_nport_find(struct efc_domain *domain, u32 d_id) { struct efc_nport *nport; /* Find a nport object, given an FC_ID */ nport = xa_load(&domain->lookup, d_id); if (!nport || !kref_get_unless_zero(&nport->ref)) return NULL; return nport; } int efc_nport_attach(struct efc_nport *nport, u32 fc_id) { int rc; struct efc_node *node; struct efc *efc = nport->efc; unsigned long index; /* Set our lookup */ rc = xa_err(xa_store(&nport->domain->lookup, fc_id, nport, 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, nport->display_name, sizeof(nport->display_name)); xa_for_each(&nport->lookup, index, node) { efc_node_update_display_name(node); } efc_log_debug(nport->efc, "[%s] attach nport: fc_id x%06x\n", nport->display_name, fc_id); /* Register a nport, given an FC_ID */ rc = efc_cmd_nport_attach(efc, nport, fc_id); if (rc < 0) { efc_log_err(nport->efc, "efc_hw_port_attach failed: %d\n", rc); return -EIO; } return 0; } static void efc_nport_shutdown(struct efc_nport *nport) { struct efc *efc = nport->efc; struct efc_node *node; unsigned long index; xa_for_each(&nport->lookup, index, node) { if (!(node->rnode.fc_id == FC_FID_FLOGI && nport->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); continue; } efc_log_debug(efc, "[%s] nport shutdown vport, send logo\n", node->display_name); if (!efc_send_logo(node)) { /* 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); } } static void efc_vport_link_down(struct efc_nport *nport) { struct efc *efc = nport->efc; struct efc_vport *vport; /* Clear the nport reference in the vport specification */ list_for_each_entry(vport, &efc->vport_list, list_entry) { if (vport->nport == nport) { kref_put(&nport->ref, nport->release); vport->nport = NULL; break; } } } static void __efc_nport_common(const char *funcname, struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc_domain *domain = nport->domain; struct efc *efc = nport->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_NPORT_ATTACH_OK: efc_sm_transition(ctx, __efc_nport_attached, NULL); break; case EFC_EVT_SHUTDOWN: /* Flag this nport as shutting down */ nport->shutting_down = true; if (nport->is_vport) efc_vport_link_down(nport); if (xa_empty(&nport->lookup)) { /* Remove the nport from the domain's lookup table */ xa_erase(&domain->lookup, nport->fc_id); efc_sm_transition(ctx, __efc_nport_wait_port_free, NULL); if (efc_cmd_nport_free(efc, nport)) { efc_log_debug(nport->efc, "efc_hw_port_free failed\n"); /* Not much we can do, free the nport anyways */ efc_nport_free(nport); } } else { /* sm: node list is not empty / shutdown nodes */ efc_sm_transition(ctx, __efc_nport_wait_shutdown, NULL); efc_nport_shutdown(nport); } break; default: efc_log_debug(nport->efc, "[%s] %-20s %-20s not handled\n", nport->display_name, funcname, efc_sm_event_name(evt)); } } void __efc_nport_allocated(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc_domain *domain = nport->domain; nport_sm_trace(nport); switch (evt) { /* the physical nport is attached */ case EFC_EVT_NPORT_ATTACH_OK: WARN_ON(nport != domain->nport); efc_sm_transition(ctx, __efc_nport_attached, NULL); break; case EFC_EVT_NPORT_ALLOC_OK: /* ignore */ break; default: __efc_nport_common(__func__, ctx, evt, arg); } } void __efc_nport_vport_init(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc *efc = nport->efc; nport_sm_trace(nport); switch (evt) { case EFC_EVT_ENTER: { __be64 be_wwpn = cpu_to_be64(nport->wwpn); if (nport->wwpn == 0) efc_log_debug(efc, "vport: letting f/w select WWN\n"); if (nport->fc_id != U32_MAX) { efc_log_debug(efc, "vport: hard coding port id: %x\n", nport->fc_id); } efc_sm_transition(ctx, __efc_nport_vport_wait_alloc, NULL); /* If wwpn is zero, then we'll let the f/w assign wwpn*/ if (efc_cmd_nport_alloc(efc, nport, nport->domain, nport->wwpn == 0 ? NULL : (uint8_t *)&be_wwpn)) { efc_log_err(efc, "Can't allocate port\n"); break; } break; } default: __efc_nport_common(__func__, ctx, evt, arg); } } void __efc_nport_vport_wait_alloc(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc *efc = nport->efc; nport_sm_trace(nport); switch (evt) { case EFC_EVT_NPORT_ALLOC_OK: { struct fc_els_flogi *sp; sp = (struct fc_els_flogi *)nport->service_params; if (nport->wwnn == 0) { nport->wwnn = be64_to_cpu(nport->sli_wwnn); nport->wwpn = be64_to_cpu(nport->sli_wwpn); snprintf(nport->wwnn_str, sizeof(nport->wwnn_str), "%016llX", nport->wwpn); } /* Update the nport's service parameters */ sp->fl_wwpn = cpu_to_be64(nport->wwpn); sp->fl_wwnn = cpu_to_be64(nport->wwnn); /* * if nport->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 (nport->fc_id == U32_MAX) { struct efc_node *fabric; fabric = efc_node_alloc(nport, FC_FID_FLOGI, false, false); if (!fabric) { efc_log_err(efc, "efc_node_alloc() failed\n"); return; } efc_node_transition(fabric, __efc_vport_fabric_init, NULL); } else { snprintf(nport->wwnn_str, sizeof(nport->wwnn_str), "%016llX", nport->wwpn); efc_nport_attach(nport, nport->fc_id); } efc_sm_transition(ctx, __efc_nport_vport_allocated, NULL); break; } default: __efc_nport_common(__func__, ctx, evt, arg); } } void __efc_nport_vport_allocated(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc *efc = nport->efc; nport_sm_trace(nport); /* * This state is entered after the nport is allocated; * it then waits for a fabric node * FDISC to complete, which requests a nport attach. * The nport attach complete is handled in this state. */ switch (evt) { case EFC_EVT_NPORT_ATTACH_OK: { struct efc_node *node; /* Find our fabric node, and forward this event */ node = efc_node_find(nport, FC_FID_FLOGI); if (!node) { efc_log_debug(efc, "can't find node %06x\n", FC_FID_FLOGI); break; } /* sm: / forward nport attach to fabric node */ efc_node_post_event(node, evt, NULL); efc_sm_transition(ctx, __efc_nport_attached, NULL); break; } default: __efc_nport_common(__func__, ctx, evt, arg); } } static void efc_vport_update_spec(struct efc_nport *nport) { struct efc *efc = nport->efc; struct efc_vport *vport; unsigned long flags = 0; spin_lock_irqsave(&efc->vport_lock, flags); list_for_each_entry(vport, &efc->vport_list, list_entry) { if (vport->nport == nport) { vport->wwnn = nport->wwnn; vport->wwpn = nport->wwpn; vport->tgt_data = nport->tgt_data; vport->ini_data = nport->ini_data; break; } } spin_unlock_irqrestore(&efc->vport_lock, flags); } void __efc_nport_attached(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc *efc = nport->efc; nport_sm_trace(nport); switch (evt) { case EFC_EVT_ENTER: { struct efc_node *node; unsigned long index; efc_log_debug(efc, "[%s] NPORT attached WWPN %016llX WWNN %016llX\n", nport->display_name, nport->wwpn, nport->wwnn); xa_for_each(&nport->lookup, index, node) efc_node_update_display_name(node); efc->tt.new_nport(efc, nport); /* * Update the vport (if its not the physical nport) * parameters */ if (nport->is_vport) efc_vport_update_spec(nport); break; } case EFC_EVT_EXIT: efc_log_debug(efc, "[%s] NPORT deattached WWPN %016llX WWNN %016llX\n", nport->display_name, nport->wwpn, nport->wwnn); efc->tt.del_nport(efc, nport); break; default: __efc_nport_common(__func__, ctx, evt, arg); } } void __efc_nport_wait_shutdown(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; struct efc_domain *domain = nport->domain; struct efc *efc = nport->efc; nport_sm_trace(nport); switch (evt) { case EFC_EVT_NPORT_ALLOC_OK: case EFC_EVT_NPORT_ALLOC_FAIL: case EFC_EVT_NPORT_ATTACH_OK: case EFC_EVT_NPORT_ATTACH_FAIL: /* ignore these events - just wait for the all free event */ break; case EFC_EVT_ALL_CHILD_NODES_FREE: { /* * Remove the nport from the domain's * sparse vector lookup table */ xa_erase(&domain->lookup, nport->fc_id); efc_sm_transition(ctx, __efc_nport_wait_port_free, NULL); if (efc_cmd_nport_free(efc, nport)) { efc_log_err(nport->efc, "efc_hw_port_free failed\n"); /* Not much we can do, free the nport anyways */ efc_nport_free(nport); } break; } default: __efc_nport_common(__func__, ctx, evt, arg); } } void __efc_nport_wait_port_free(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_nport *nport = ctx->app; nport_sm_trace(nport); switch (evt) { case EFC_EVT_NPORT_ATTACH_OK: /* Ignore as we are waiting for the free CB */ break; case EFC_EVT_NPORT_FREE_OK: { /* All done, free myself */ efc_nport_free(nport); break; } default: __efc_nport_common(__func__, ctx, evt, arg); } } static int efc_vport_nport_alloc(struct efc_domain *domain, struct efc_vport *vport) { struct efc_nport *nport; lockdep_assert_held(&domain->efc->lock); nport = efc_nport_alloc(domain, vport->wwpn, vport->wwnn, vport->fc_id, vport->enable_ini, vport->enable_tgt); vport->nport = nport; if (!nport) return -EIO; kref_get(&nport->ref); nport->is_vport = true; nport->tgt_data = vport->tgt_data; nport->ini_data = vport->ini_data; efc_sm_transition(&nport->sm, __efc_nport_vport_init, NULL); return 0; } int efc_vport_start(struct efc_domain *domain) { struct efc *efc = domain->efc; struct efc_vport *vport; struct efc_vport *next; int rc = 0; unsigned long flags = 0; /* Use the vport spec to find the associated vports and start them */ spin_lock_irqsave(&efc->vport_lock, flags); list_for_each_entry_safe(vport, next, &efc->vport_list, list_entry) { if (!vport->nport) { if (efc_vport_nport_alloc(domain, vport)) rc = -EIO; } } spin_unlock_irqrestore(&efc->vport_lock, flags); return rc; } int efc_nport_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 *vport; int rc = 0; unsigned long flags = 0; if (ini && domain->efc->enable_ini == 0) { efc_log_debug(efc, "driver initiator mode not enabled\n"); return -EIO; } if (tgt && domain->efc->enable_tgt == 0) { efc_log_debug(efc, "driver target mode not enabled\n"); return -EIO; } /* * 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 -EIO; } spin_lock_irqsave(&efc->lock, flags); rc = efc_vport_nport_alloc(domain, vport); spin_unlock_irqrestore(&efc->lock, flags); return rc; } int efc_nport_vport_del(struct efc *efc, struct efc_domain *domain, u64 wwpn, uint64_t wwnn) { struct efc_nport *nport; struct efc_vport *vport; struct efc_vport *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 nport to look for */ return 0; } spin_lock_irqsave(&efc->lock, flags); list_for_each_entry(nport, &domain->nport_list, list_entry) { if (nport->wwpn == wwpn && nport->wwnn == wwnn) { kref_put(&nport->ref, nport->release); /* Shutdown this NPORT */ efc_sm_post_event(&nport->sm, EFC_EVT_SHUTDOWN, NULL); break; } } spin_unlock_irqrestore(&efc->lock, flags); return 0; } void efc_vport_del_all(struct efc *efc) { struct efc_vport *vport; struct efc_vport *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); } struct efc_vport * 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 *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, "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; }