mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-13 14:24:11 +08:00
2e4b02fad0
The kref_put() function will call nport->release if the refcount drops to
zero. The nport->release release function is _efc_nport_free() which frees
"nport". But then we dereference "nport" on the next line which is a use
after free. Re-order these lines to avoid the use after free.
Fixes: fcd427303e
("scsi: elx: libefc: SLI and FC PORT state machine interfaces")
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Link: https://lore.kernel.org/r/b666ab26-6581-4213-9a3d-32a9147f0399@stanley.mountain
Reviewed-by: Daniel Wagner <dwagner@suse.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
778 lines
18 KiB
C
778 lines
18 KiB
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(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) {
|
|
/* 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(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;
|
|
}
|