root/drivers/scsi/elx/libefc/efc_nport.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.
 */

/*
 * 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_obj(*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) {
                        /* Shutdown this NPORT */
                        efc_sm_post_event(&nport->sm, EFC_EVT_SHUTDOWN, NULL);
                        kref_put(&nport->ref, nport->release);
                        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_obj(*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;
}