/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include disc_action_t fct_handle_local_port_event(fct_i_local_port_t *iport); disc_action_t fct_walk_discovery_queue(fct_i_local_port_t *iport); disc_action_t fct_process_els(fct_i_local_port_t *iport, fct_i_remote_port_t *irp); fct_status_t fct_send_accrjt(fct_cmd_t *cmd, uint8_t accrjt, uint8_t reason, uint8_t expl); disc_action_t fct_link_init_complete(fct_i_local_port_t *iport); fct_status_t fct_complete_previous_li_cmd(fct_i_local_port_t *iport); fct_status_t fct_sol_plogi(fct_i_local_port_t *iport, uint32_t id, fct_cmd_t **ret_ppcmd, int implicit); fct_status_t fct_sol_ct(fct_i_local_port_t *iport, uint32_t id, fct_cmd_t **ret_ppcmd, uint16_t opcode); fct_status_t fct_ns_scr(fct_i_local_port_t *iport, uint32_t id, fct_cmd_t **ret_ppcmd); static disc_action_t fct_check_cmdlist(fct_i_local_port_t *iport); static disc_action_t fct_check_solcmd_queue(fct_i_local_port_t *iport); static void fct_rscn_verify(fct_i_local_port_t *iport, uint8_t *rscn_req_payload, uint32_t rscn_req_size); void fct_gid_cb(fct_i_cmd_t *icmd); char *fct_els_names[] = { 0, "LS_RJT", "ACC", "PLOGI", "FLOGI", "LOGO", "ABTX", "RCS", "RES", "RSS", "RSI", "ESTS", "ESTC", "ADVC", "RTV", "RLS", /* 0x10 */ "ECHO", "TEST", "RRQ", "REC", "SRR", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ "PRLI", "PRLO", "SCN", "TPLS", "TPRLO", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ "PDISC", "FDISC", "ADISC", "RNC", "FARP", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ "FAN", "RSCN", "SCR", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ "LINIT", "LPC", "LSTS", 0, 0, 0, 0, 0, "RNID", "RLIR", "LIRR", 0, 0, 0, 0, 0 }; extern uint32_t fct_rscn_options; /* * NOTE: if anybody drops the iport_worker_lock then they should not return * DISC_ACTION_NO_WORK. Which also means, dont drop the lock if you have * nothing to do. Or else return DISC_ACTION_RESCAN or DISC_ACTION_DELAY_RESCAN. * But you cannot be infinitly returning those so have some logic to * determine that there is nothing to do without dropping the lock. */ void fct_port_worker(void *arg) { fct_local_port_t *port = (fct_local_port_t *)arg; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; disc_action_t suggested_action; clock_t dl, short_delay, long_delay; int64_t tmp_delay; iport->iport_cmdcheck_clock = ddi_get_lbolt() + drv_usectohz(FCT_CMDLIST_CHECK_SECONDS * 1000000); short_delay = drv_usectohz(10000); long_delay = drv_usectohz(1000000); stmf_trace(iport->iport_alias, "iport is %p", iport); /* Discovery loop */ mutex_enter(&iport->iport_worker_lock); atomic_or_32(&iport->iport_flags, IPORT_WORKER_RUNNING); while ((iport->iport_flags & IPORT_TERMINATE_WORKER) == 0) { suggested_action = DISC_ACTION_NO_WORK; /* * Local port events are of the highest prioriy */ if (iport->iport_event_head) { suggested_action |= fct_handle_local_port_event(iport); } /* * We could post solicited ELSes to discovery queue. * solicited CT will be processed inside fct_check_solcmd_queue */ if (iport->iport_solcmd_queue) { suggested_action |= fct_check_solcmd_queue(iport); } /* * All solicited and unsolicited ELS will be handled here */ if (iport->iport_rpwe_head) { suggested_action |= fct_walk_discovery_queue(iport); } /* * We only process it when there's no outstanding link init CMD */ if ((iport->iport_link_state == PORT_STATE_LINK_INIT_START) && !(iport->iport_li_state & (LI_STATE_FLAG_CMD_WAITING | LI_STATE_FLAG_NO_LI_YET))) { suggested_action |= fct_process_link_init(iport); } /* * We process cmd aborting in the end */ if (iport->iport_abort_queue) { suggested_action |= fct_cmd_terminator(iport); } /* * Check cmd max/free */ if (iport->iport_cmdcheck_clock <= ddi_get_lbolt()) { suggested_action |= fct_check_cmdlist(iport); iport->iport_cmdcheck_clock = ddi_get_lbolt() + drv_usectohz(FCT_CMDLIST_CHECK_SECONDS * 1000000); iport->iport_max_active_ncmds = 0; } if (iport->iport_offline_prstate != FCT_OPR_DONE) { suggested_action |= fct_handle_port_offline(iport); } if (suggested_action & DISC_ACTION_RESCAN) { continue; } else if (suggested_action & DISC_ACTION_DELAY_RESCAN) { /* * This is not very optimum as whoever returned * DISC_ACTION_DELAY_RESCAN must have dropped the lock * and more things might have queued up. But since * we are only doing small delays, it only delays * things by a few ms, which is okey. */ if (suggested_action & DISC_ACTION_USE_SHORT_DELAY) { dl = short_delay; } else { dl = long_delay; } atomic_or_32(&iport->iport_flags, IPORT_WORKER_DOING_TIMEDWAIT); (void) cv_timedwait(&iport->iport_worker_cv, &iport->iport_worker_lock, ddi_get_lbolt() + dl); atomic_and_32(&iport->iport_flags, ~IPORT_WORKER_DOING_TIMEDWAIT); } else { atomic_or_32(&iport->iport_flags, IPORT_WORKER_DOING_WAIT); tmp_delay = (int64_t)(iport->iport_cmdcheck_clock - ddi_get_lbolt()); if (tmp_delay < 0) { tmp_delay = (int64_t)short_delay; } (void) cv_timedwait(&iport->iport_worker_cv, &iport->iport_worker_lock, ddi_get_lbolt() + (clock_t)tmp_delay); atomic_and_32(&iport->iport_flags, ~IPORT_WORKER_DOING_WAIT); } } atomic_and_32(&iport->iport_flags, ~IPORT_WORKER_RUNNING); mutex_exit(&iport->iport_worker_lock); } static char *topologies[] = { "Unknown", "Direct Pt-to-Pt", "Private Loop", "Unknown", "Unknown", "Fabric Pt-to-Pt", "Public Loop" }; void fct_li_to_txt(fct_link_info_t *li, char *topology, char *speed) { uint8_t s = li->port_speed; if (li->port_topology > PORT_TOPOLOGY_PUBLIC_LOOP) { (void) sprintf(topology, "Invalid %02x", li->port_topology); } else { (void) strcpy(topology, topologies[li->port_topology]); } if ((s == 0) || ((s & 0xf00) != 0) || ((s & (s - 1)) != 0)) { speed[0] = '?'; } else if (s == PORT_SPEED_10G) { speed[0] = '1'; speed[1] = '0'; speed[2] = 'G'; speed[3] = 0; } else { speed[0] = '0' + li->port_speed; speed[1] = 'G'; speed[2] = 0; } } /* * discovery lock held. * XXX: Implement command cleanup upon Link down. * XXX: Implement a clean start and FC-GS registrations upon Link up. * * ================ Local Port State Machine ============ * ---| * | v * | --->[LINK_DOWN]--->[LINK_INIT_START]--->[LINK_INIT_DONE] * | ^ ^ ^ | | * | |---| | |-- |-| |---><--| * | | | v | v * |->[FATAL_CLEANING] [LINK_DOWN_CLEANING]--->[LINK_UP_CLEANING] * ^ * |-- * ======================================================= * An explicit port_online() is only allowed in LINK_DOWN state. * An explicit port_offline() is only allowed in LINKDOWN and * LINK_INIT_DONE state. */ disc_action_t fct_handle_local_port_event(fct_i_local_port_t *iport) { disc_action_t ret = DISC_ACTION_RESCAN; fct_i_event_t *in; uint16_t old_state, new_state, new_bits; int dqueue_and_free = 1; int retry_implicit_logo = 0; if (iport->iport_event_head == NULL) return (DISC_ACTION_NO_WORK); in = iport->iport_event_head; mutex_exit(&iport->iport_worker_lock); rw_enter(&iport->iport_lock, RW_WRITER); if (in->event_type == FCT_EVENT_LINK_UP) { DTRACE_FC_1(link__up, fct_i_local_port_t, iport); } else if (in->event_type == FCT_EVENT_LINK_DOWN) { DTRACE_FC_1(link__down, fct_i_local_port_t, iport); } /* Calculate new state */ new_state = iport->iport_link_state; if (in->event_type == FCT_EVENT_LINK_DOWN) { new_state = PORT_STATE_LINK_DOWN_CLEANING; } else if (in->event_type == FCT_EVENT_LINK_UP) { if (iport->iport_link_state == PORT_STATE_LINK_DOWN_CLEANING) new_state = PORT_STATE_LINK_UP_CLEANING; else if (iport->iport_link_state == PORT_STATE_LINK_DOWN) new_state = PORT_STATE_LINK_INIT_START; else { /* This should not happen */ stmf_trace(iport->iport_alias, "Link up received when link state was" "%x, Ignoring...", iport->iport_link_state); } } else if (in->event_type == FCT_I_EVENT_CLEANUP_POLL) { if (!fct_local_port_cleanup_done(iport)) { if (iport->iport_link_cleanup_retry >= 3) { iport->iport_link_cleanup_retry = 0; retry_implicit_logo = 1; } else { iport->iport_link_cleanup_retry++; } dqueue_and_free = 0; ret = DISC_ACTION_DELAY_RESCAN; } else { if (iport->iport_link_state == PORT_STATE_LINK_DOWN_CLEANING) { new_state = PORT_STATE_LINK_DOWN; } else if (iport->iport_link_state == PORT_STATE_LINK_UP_CLEANING) { new_state = PORT_STATE_LINK_INIT_START; } else { /* This should not have happened */ cmn_err(CE_WARN, "port state changed to %x " "during cleanup", iport->iport_link_state); new_state = PORT_STATE_LINK_DOWN; } } } else if (in->event_type == FCT_EVENT_LINK_RESET) { /* Link reset is only allowed when we are Online */ if (iport->iport_link_state & S_LINK_ONLINE) { new_state = PORT_STATE_LINK_UP_CLEANING; } } else if (in->event_type == FCT_I_EVENT_LINK_INIT_DONE) { if (iport->iport_link_state == PORT_STATE_LINK_INIT_START) { new_state = PORT_STATE_LINK_INIT_DONE; iport->iport_li_state = LI_STATE_START; } } else { ASSERT(0); } new_bits = iport->iport_link_state ^ (iport->iport_link_state | new_state); old_state = iport->iport_link_state; iport->iport_link_state = new_state; rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "port state change from %x to %x", old_state, new_state); if (new_bits & S_PORT_CLEANUP) { (void) fct_implicitly_logo_all(iport, 0); fct_handle_event(iport->iport_port, FCT_I_EVENT_CLEANUP_POLL, 0, 0); } if (retry_implicit_logo) { (void) fct_implicitly_logo_all(iport, 1); } if (new_bits & S_INIT_LINK) { fct_link_info_t *li = &iport->iport_link_info; fct_status_t li_ret; iport->iport_li_state |= LI_STATE_FLAG_NO_LI_YET; bzero(li, sizeof (*li)); if ((li_ret = iport->iport_port->port_get_link_info( iport->iport_port, li)) != FCT_SUCCESS) { stmf_trace(iport->iport_alias, "iport-%p: " "port_get_link_info failed, ret %llx, forcing " "link down.", iport, li_ret); fct_handle_event(iport->iport_port, FCT_EVENT_LINK_DOWN, 0, 0); } else { iport->iport_login_retry = 0; /* This will reset LI_STATE_FLAG_NO_LI_YET */ iport->iport_li_state = LI_STATE_START; atomic_or_32(&iport->iport_flags, IPORT_ALLOW_UNSOL_FLOGI); } fct_log_local_port_event(iport->iport_port, ESC_SUNFC_PORT_ONLINE); } else if (new_bits & S_RCVD_LINK_DOWN) { fct_log_local_port_event(iport->iport_port, ESC_SUNFC_PORT_OFFLINE); } mutex_enter(&iport->iport_worker_lock); if (in && dqueue_and_free) { iport->iport_event_head = in->event_next; if (iport->iport_event_head == NULL) iport->iport_event_tail = NULL; kmem_free(in, sizeof (*in)); } return (ret); } int fct_lport_has_bigger_wwn(fct_i_local_port_t *iport) { uint8_t *l, *r; int i; uint64_t wl, wr; l = iport->iport_port->port_pwwn; r = iport->iport_link_info.port_rpwwn; for (i = 0, wl = 0; i < 8; i++) { wl <<= 8; wl |= l[i]; } for (i = 0, wr = 0; i < 8; i++) { wr <<= 8; wr |= r[i]; } if (wl > wr) { return (1); } return (0); } void fct_do_flogi(fct_i_local_port_t *iport) { fct_flogi_xchg_t fx; fct_status_t ret; int force_link_down = 0; int do_retry = 0; DTRACE_FC_1(fabric__login__start, fct_i_local_port_t, iport); bzero(&fx, sizeof (fx)); fx.fx_op = ELS_OP_FLOGI; if (iport->iport_login_retry == 0) { fx.fx_sec_timeout = 2; } else { fx.fx_sec_timeout = 5; } if (iport->iport_link_info.port_topology & PORT_TOPOLOGY_PRIVATE_LOOP) { fx.fx_sid = iport->iport_link_info.portid & 0xFF; } fx.fx_did = 0xFFFFFE; bcopy(iport->iport_port->port_nwwn, fx.fx_nwwn, 8); bcopy(iport->iport_port->port_pwwn, fx.fx_pwwn, 8); mutex_exit(&iport->iport_worker_lock); ret = iport->iport_port->port_flogi_xchg(iport->iport_port, &fx); mutex_enter(&iport->iport_worker_lock); if (IPORT_FLOGI_DONE(iport)) { /* The unsolicited path finished it. */ goto done; } if (ret == FCT_NOT_FOUND) { if (iport->iport_link_info.port_topology & PORT_TOPOLOGY_PRIVATE_LOOP) { /* This is a private loop. There is no switch. */ iport->iport_link_info.port_no_fct_flogi = 1; goto done; } /* * This is really an error. This means we cannot init the * link. Lets force the link to go down. */ force_link_down = 1; } else if ((ret == FCT_SUCCESS) && (fx.fx_op == ELS_OP_LSRJT)) { if ((fx.fx_rjt_reason == 5) || (fx.fx_rjt_reason == 0xe) || ((fx.fx_rjt_reason == 9) && (fx.fx_rjt_expl == 0x29))) { do_retry = 1; } else { force_link_down = 1; } } else if (ret == STMF_TIMEOUT) { do_retry = 1; } else if (ret != FCT_SUCCESS) { force_link_down = 1; } if (do_retry) { iport->iport_login_retry++; if (iport->iport_login_retry >= 5) force_link_down = 1; goto done; } if (force_link_down) { stmf_trace(iport->iport_alias, "iport-%p: flogi xchg failed. " "Forcing link down, ret=%llx login_retry=%d ret_op=%d " "reason=%d expl=%d", iport, ret, iport->iport_login_retry, fx.fx_op, fx.fx_rjt_reason, fx.fx_rjt_expl); mutex_exit(&iport->iport_worker_lock); fct_handle_event(iport->iport_port, FCT_EVENT_LINK_DOWN, 0, 0); mutex_enter(&iport->iport_worker_lock); goto done; } /* FLOGI succeeded. Update local port state */ ASSERT(fx.fx_op == ELS_OP_ACC); bcopy(fx.fx_nwwn, iport->iport_link_info.port_rnwwn, 8); bcopy(fx.fx_pwwn, iport->iport_link_info.port_rpwwn, 8); if (fx.fx_fport) { iport->iport_link_info.port_topology |= PORT_TOPOLOGY_FABRIC_BIT; iport->iport_link_info.portid = fx.fx_did; } iport->iport_link_info.port_fct_flogi_done = 1; done: DTRACE_FC_1(fabric__login__end, fct_i_local_port_t, iport); } /* * Called by FCAs to handle unsolicited FLOGIs. */ fct_status_t fct_handle_rcvd_flogi(fct_local_port_t *port, fct_flogi_xchg_t *fx) { fct_i_local_port_t *iport; uint32_t t; iport = (fct_i_local_port_t *)port->port_fct_private; if ((iport->iport_flags & IPORT_ALLOW_UNSOL_FLOGI) == 0) { return (FCT_FAILURE); } mutex_enter(&iport->iport_worker_lock); if (((iport->iport_flags & IPORT_ALLOW_UNSOL_FLOGI) == 0) || (iport->iport_link_state != PORT_STATE_LINK_INIT_START) || ((iport->iport_li_state & LI_STATE_MASK) > LI_STATE_N2N_PLOGI)) { mutex_exit(&iport->iport_worker_lock); return (FCT_FAILURE); } if (iport->iport_link_info.port_fct_flogi_done == 0) { iport->iport_link_info.port_fct_flogi_done = 1; bcopy(fx->fx_pwwn, iport->iport_link_info.port_rpwwn, 8); bcopy(fx->fx_nwwn, iport->iport_link_info.port_rnwwn, 8); } fx->fx_op = ELS_OP_ACC; t = fx->fx_sid; fx->fx_sid = fx->fx_did; fx->fx_did = t; bcopy(iport->iport_port->port_pwwn, fx->fx_pwwn, 8); bcopy(iport->iport_port->port_nwwn, fx->fx_nwwn, 8); mutex_exit(&iport->iport_worker_lock); return (FCT_SUCCESS); } /* * iport_li_state can only be changed here and local_event */ disc_action_t fct_process_link_init(fct_i_local_port_t *iport) { fct_cmd_t *cmd = NULL; char *pname = NULL; uint8_t elsop = 0; uint16_t ctop = 0; uint32_t wkdid = 0; int implicit = 0; int force_login = 0; disc_action_t ret = DISC_ACTION_RESCAN; fct_link_info_t *li = &iport->iport_link_info; char topo[24], speed[4]; ASSERT(MUTEX_HELD(&iport->iport_worker_lock)); check_state_again: switch (iport->iport_li_state & LI_STATE_MASK) { case LI_STATE_DO_FLOGI: /* Is FLOGI even needed or already done ? */ if ((iport->iport_link_info.port_no_fct_flogi) || (IPORT_FLOGI_DONE(iport))) { iport->iport_li_state++; goto check_state_again; } fct_do_flogi(iport); break; case LI_STATE_FINI_TOPOLOGY: fct_li_to_txt(li, topo, speed); cmn_err(CE_NOTE, "%s LINK UP, portid %x, topology %s," "speed %s", iport->iport_alias, li->portid, topo, speed); if (li->port_topology != iport->iport_link_old_topology) { if (iport->iport_nrps) { /* * rehash it if change from fabric to * none fabric, vice versa */ if ((li->port_topology ^ iport->iport_link_old_topology) & PORT_TOPOLOGY_FABRIC_BIT) { mutex_exit(&iport->iport_worker_lock); fct_rehash(iport); mutex_enter(&iport->iport_worker_lock); } } iport->iport_link_old_topology = li->port_topology; } /* Skip next level if topo is not N2N */ if (li->port_topology != PORT_TOPOLOGY_PT_TO_PT) { iport->iport_li_state += 2; atomic_and_32(&iport->iport_flags, ~IPORT_ALLOW_UNSOL_FLOGI); } else { iport->iport_li_state++; iport->iport_login_retry = 0; iport->iport_li_cmd_timeout = ddi_get_lbolt() + drv_usectohz(25 * 1000000); } goto check_state_again; case LI_STATE_N2N_PLOGI: ASSERT(IPORT_FLOGI_DONE(iport)); ASSERT(iport->iport_link_info.port_topology == PORT_TOPOLOGY_PT_TO_PT); if (iport->iport_li_state & LI_STATE_FLAG_CMD_RETCHECK) { iport->iport_li_state &= ~LI_STATE_FLAG_CMD_RETCHECK; if (iport->iport_li_comp_status != FCT_SUCCESS) { iport->iport_login_retry++; if (iport->iport_login_retry >= 3) { stmf_trace(iport->iport_alias, "Failing" " to PLOGI to remote port in N2N " " ret=%llx, forcing link down", iport->iport_li_comp_status); mutex_exit(&iport->iport_worker_lock); fct_handle_event(iport->iport_port, FCT_EVENT_LINK_DOWN, 0, 0); mutex_enter(&iport->iport_worker_lock); } } } /* Find out if we need to do PLOGI at all */ if (iport->iport_nrps_login) { iport->iport_li_state++; atomic_and_32(&iport->iport_flags, ~IPORT_ALLOW_UNSOL_FLOGI); goto check_state_again; } if ((ddi_get_lbolt() >= iport->iport_li_cmd_timeout) && (!fct_lport_has_bigger_wwn(iport))) { /* Cant wait forever */ stmf_trace(iport->iport_alias, "N2N: Remote port is " "not logging in, forcing from our side"); force_login = 1; } else { force_login = 0; } if (force_login || fct_lport_has_bigger_wwn(iport)) { elsop = ELS_OP_PLOGI; wkdid = 1; iport->iport_link_info.portid = 0xEF; implicit = 0; iport->iport_li_state |= LI_STATE_FLAG_CMD_RETCHECK; } else { ret = DISC_ACTION_DELAY_RESCAN; } break; case LI_STATE_DO_FCLOGIN: if (iport->iport_li_state & LI_STATE_FLAG_CMD_RETCHECK) { iport->iport_li_state &= ~LI_STATE_FLAG_CMD_RETCHECK; if (iport->iport_li_comp_status != FCT_SUCCESS) { /* * Fabric controller login failed. Just skip all * the fabric controller related cmds. */ iport->iport_li_state = LI_STATE_DO_SCR + 1; } else { /* * Good. Now lets go to next state */ iport->iport_li_state++; } goto check_state_again; } if (!IPORT_IN_NS_TOPO(iport)) { iport->iport_li_state = LI_STATE_DO_SCR + 1; goto check_state_again; } elsop = ELS_OP_PLOGI; wkdid = FS_FABRIC_CONTROLLER; implicit = 1; /* * We want to come back in the same state and check its ret * We can't modify the state here */ iport->iport_li_state |= LI_STATE_FLAG_CMD_RETCHECK; break; case LI_STATE_DO_SCR: elsop = ELS_OP_SCR; wkdid = FS_FABRIC_CONTROLLER; /* * We dont care about success of this state. Just go to * next state upon completion. */ iport->iport_li_state++; break; case LI_STATE_DO_NSLOGIN: if (iport->iport_li_state & LI_STATE_FLAG_CMD_RETCHECK) { iport->iport_li_state &= ~LI_STATE_FLAG_CMD_RETCHECK; if (iport->iport_li_comp_status != FCT_SUCCESS) { iport->iport_li_state = LI_STATE_DO_RSNN + 1; } else { iport->iport_li_state++; } goto check_state_again; } if (!IPORT_IN_NS_TOPO(iport)) { iport->iport_li_state = LI_STATE_DO_RSNN + 1; goto check_state_again; } elsop = ELS_OP_PLOGI; wkdid = FS_NAME_SERVER; iport->iport_li_state |= LI_STATE_FLAG_CMD_RETCHECK; break; /* * CT state */ case LI_STATE_DO_RNN: ctop = NS_RNN_ID; iport->iport_li_state++; break; case LI_STATE_DO_RCS: ctop = NS_RCS_ID; iport->iport_li_state++; break; case LI_STATE_DO_RFT: ctop = NS_RFT_ID; iport->iport_li_state++; break; case LI_STATE_DO_RSPN: /* * Check if we need skip the state */ pname = iport->iport_port->port_sym_port_name != NULL ? iport->iport_port->port_sym_port_name : NULL; if (pname == NULL) { pname = iport->iport_port->port_default_alias != NULL ? iport->iport_port->port_default_alias : NULL; iport->iport_port->port_sym_port_name = pname; } if (pname == NULL) { iport->iport_li_state++; goto check_state_again; } ctop = NS_RSPN_ID; iport->iport_li_state++; break; case LI_STATE_DO_RSNN: ctop = NS_RSNN_NN; iport->iport_li_state++; break; case LI_STATE_MAX: mutex_exit(&iport->iport_worker_lock); fct_handle_event(iport->iport_port, FCT_I_EVENT_LINK_INIT_DONE, 0, 0); mutex_enter(&iport->iport_worker_lock); break; default: ASSERT(0); } if (elsop != 0) { cmd = fct_create_solels(iport->iport_port, NULL, implicit, elsop, wkdid, fct_link_init_cb); } else if (ctop != 0) { cmd = fct_create_solct(iport->iport_port, NULL, ctop, fct_link_init_cb); } if (cmd) { iport->iport_li_state |= LI_STATE_FLAG_CMD_WAITING; mutex_exit(&iport->iport_worker_lock); fct_post_to_solcmd_queue(iport->iport_port, cmd); mutex_enter(&iport->iport_worker_lock); } return (ret); } /* * Handles both solicited and unsolicited elses. Can be called inside * interrupt context. */ void fct_handle_els(fct_cmd_t *cmd) { fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *)port->port_fct_private; fct_i_cmd_t *icmd = (fct_i_cmd_t *)cmd->cmd_fct_private; fct_els_t *els = (fct_els_t *)cmd->cmd_specific; fct_remote_port_t *rp; fct_i_remote_port_t *irp; uint16_t cmd_slot; uint8_t op; op = els->els_req_payload[0]; icmd->icmd_start_time = ddi_get_lbolt(); if (cmd->cmd_type == FCT_CMD_RCVD_ELS) { icmd->icmd_flags |= ICMD_KNOWN_TO_FCA; } stmf_trace(iport->iport_alias, "Posting %ssol ELS %x (%s) rp_id=%x" " lp_id=%x", (cmd->cmd_type == FCT_CMD_RCVD_ELS) ? "un" : "", op, FCT_ELS_NAME(op), cmd->cmd_rportid, cmd->cmd_lportid); rw_enter(&iport->iport_lock, RW_READER); start_els_posting:; /* Make sure local port is sane */ if ((iport->iport_link_state & S_LINK_ONLINE) == 0) { rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "ELS %x not posted becasue" "port state was %x", els->els_req_payload[0], iport->iport_link_state); fct_queue_cmd_for_termination(cmd, FCT_LOCAL_PORT_OFFLINE); return; } /* Weed out any bad initiators in case of N2N topology */ if ((cmd->cmd_type == FCT_CMD_RCVD_ELS) && (els->els_req_payload[0] == ELS_OP_PLOGI) && (iport->iport_link_state == PORT_STATE_LINK_INIT_START) && (iport->iport_link_info.port_topology == PORT_TOPOLOGY_PT_TO_PT)) { int state; int killit = 0; mutex_enter(&iport->iport_worker_lock); state = iport->iport_li_state & LI_STATE_MASK; /* * We dont allow remote port to plogi in N2N if we have not yet * resolved the topology. */ if (state <= LI_STATE_FINI_TOPOLOGY) { killit = 1; stmf_trace(iport->iport_alias, "port %x is trying to " "PLOGI in N2N topology, While we have not resolved" " the topology. Dropping...", cmd->cmd_rportid); } else if (state <= LI_STATE_N2N_PLOGI) { if (fct_lport_has_bigger_wwn(iport)) { killit = 1; stmf_trace(iport->iport_alias, "port %x is " "trying to PLOGI in N2N topology, even " "though it has smaller PWWN", cmd->cmd_rportid); } else { /* * Remote port is assigning us a PORTID as * a part of PLOGI. */ iport->iport_link_info.portid = cmd->cmd_lportid; } } mutex_exit(&iport->iport_worker_lock); if (killit) { rw_exit(&iport->iport_lock); fct_queue_cmd_for_termination(cmd, FCT_LOCAL_PORT_OFFLINE); return; } } /* * For all unsolicited ELSes that are not FLOGIs, our portid * has been established by now. Sometimes port IDs change due to * link resets but remote ports may still send ELSes using the * old IDs. Kill those right here. */ if ((cmd->cmd_type == FCT_CMD_RCVD_ELS) && (els->els_req_payload[0] != ELS_OP_FLOGI)) { if (cmd->cmd_lportid != iport->iport_link_info.portid) { rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "Rcvd %s with " "wrong lportid %x, expecting %x. Killing ELS.", FCT_ELS_NAME(op), cmd->cmd_lportid, iport->iport_link_info.portid); fct_queue_cmd_for_termination(cmd, FCT_NOT_FOUND); return; } } /* * We always lookup by portid. port handles are too * unreliable at this stage. */ irp = fct_portid_to_portptr(iport, cmd->cmd_rportid); if (els->els_req_payload[0] == ELS_OP_PLOGI) { if (irp == NULL) { /* drop the lock while we do allocations */ rw_exit(&iport->iport_lock); rp = fct_alloc(FCT_STRUCT_REMOTE_PORT, port->port_fca_rp_private_size, 0); if (rp == NULL) { fct_queue_cmd_for_termination(cmd, FCT_ALLOC_FAILURE); return; } irp = (fct_i_remote_port_t *)rp->rp_fct_private; rw_init(&irp->irp_lock, 0, RW_DRIVER, 0); irp->irp_rp = rp; irp->irp_portid = cmd->cmd_rportid; rp->rp_port = port; rp->rp_id = cmd->cmd_rportid; rp->rp_handle = FCT_HANDLE_NONE; /* * Grab port lock as writer since we are going * to modify the local port struct. */ rw_enter(&iport->iport_lock, RW_WRITER); /* Make sure nobody created the struct except us */ if (fct_portid_to_portptr(iport, cmd->cmd_rportid)) { /* Oh well, free it */ fct_free(rp); } else { fct_queue_rp(iport, irp); } rw_downgrade(&iport->iport_lock); /* Start over becasue we dropped the lock */ goto start_els_posting; } /* A PLOGI is by default a logout of previous session */ irp->irp_deregister_timer = ddi_get_lbolt() + drv_usectohz(USEC_DEREG_RP_TIMEOUT); irp->irp_dereg_count = 0; fct_post_to_discovery_queue(iport, irp, NULL); /* A PLOGI also invalidates any RSCNs related to this rp */ atomic_add_32(&irp->irp_rscn_counter, 1); } else { /* * For everything else, we have (or be able to lookup) a * valid port pointer. */ if (irp == NULL) { rw_exit(&iport->iport_lock); if (cmd->cmd_type == FCT_CMD_RCVD_ELS) { /* XXX Throw a logout to the initiator */ stmf_trace(iport->iport_alias, "ELS %x " "received from %x without a session", els->els_req_payload[0], cmd->cmd_rportid); } else { stmf_trace(iport->iport_alias, "Sending ELS %x " "to %x without a session", els->els_req_payload[0], cmd->cmd_rportid); } fct_queue_cmd_for_termination(cmd, FCT_NOT_LOGGED_IN); return; } } cmd->cmd_rp = rp = irp->irp_rp; /* * Lets get a slot for this els */ if (!(icmd->icmd_flags & ICMD_IMPLICIT)) { cmd_slot = fct_alloc_cmd_slot(iport, cmd); if (cmd_slot == FCT_SLOT_EOL) { /* This should not have happened */ rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "ran out of xchg resources"); fct_queue_cmd_for_termination(cmd, FCT_NO_XCHG_RESOURCE); return; } } else { /* * Tell the framework that fct_cmd_free() can decrement the * irp_nonfcp_xchg_count variable. */ atomic_or_32(&icmd->icmd_flags, ICMD_IMPLICIT_CMD_HAS_RESOURCE); } atomic_add_16(&irp->irp_nonfcp_xchg_count, 1); /* * Grab the remote port lock while we modify the port state. * we should not drop the fca port lock (as a reader) until we * modify the remote port state. */ rw_enter(&irp->irp_lock, RW_WRITER); if ((op == ELS_OP_PLOGI) || (op == ELS_OP_PRLI) || (op == ELS_OP_LOGO) || (op == ELS_OP_PRLO) || (op == ELS_OP_TPRLO)) { uint32_t rf = IRP_PRLI_DONE; if ((op == ELS_OP_PLOGI) || (op == ELS_OP_LOGO)) { rf |= IRP_PLOGI_DONE; if (irp->irp_flags & IRP_PLOGI_DONE) atomic_add_32(&iport->iport_nrps_login, -1); } atomic_add_16(&irp->irp_sa_elses_count, 1); atomic_and_32(&irp->irp_flags, ~rf); atomic_or_32(&icmd->icmd_flags, ICMD_SESSION_AFFECTING); } else { atomic_add_16(&irp->irp_nsa_elses_count, 1); } fct_post_to_discovery_queue(iport, irp, icmd); rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); } /* * Cleanup I/Os for a rport. ttc is a bit Mask of cmd types to clean. * No locks held. */ int fct_trigger_rport_cleanup(fct_i_remote_port_t *irp, int ttc) { fct_remote_port_t *rp = irp->irp_rp; fct_local_port_t *port = rp->rp_port; fct_i_local_port_t *iport = (fct_i_local_port_t *)port->port_fct_private; fct_cmd_t *cmd; fct_i_cmd_t *icmd; int i; int ret; uint16_t total, cleaned, skipped, unhandled; rw_enter(&iport->iport_lock, RW_WRITER); rw_enter(&irp->irp_lock, RW_WRITER); mutex_enter(&iport->iport_worker_lock); total = port->port_max_xchges - iport->iport_nslots_free; cleaned = skipped = unhandled = 0; for (i = 0; i < port->port_max_xchges; i++) { if (iport->iport_cmd_slots[i].slot_cmd == NULL) continue; icmd = iport->iport_cmd_slots[i].slot_cmd; if (icmd->icmd_flags & ICMD_IN_TRANSITION) { unhandled++; continue; } if (icmd->icmd_flags & ICMD_CMD_COMPLETE) { unhandled++; continue; } cmd = icmd->icmd_cmd; if (cmd->cmd_rp != rp) { skipped++; continue; } if (cmd->cmd_type & ttc) { if (cmd->cmd_type == FCT_CMD_FCP_XCHG) fct_queue_scsi_task_for_termination(cmd, FCT_ABORTED); else fct_q_for_termination_lock_held(iport, icmd, FCT_ABORTED); cleaned++; } else { skipped++; } } if (((cleaned + skipped) == total) && (unhandled == 0)) { ret = 1; } else { /* * XXX: handle this situation. */ stmf_trace(iport->iport_alias, "Clean up trouble for irp" " %p, c/s/u/t = %d/%d/%d/%d", irp, cleaned, skipped, unhandled, total); ret = 0; } if ((cleaned) && IS_WORKER_SLEEPING(iport)) cv_signal(&iport->iport_worker_cv); mutex_exit(&iport->iport_worker_lock); rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); return (ret); } void fct_dequeue_els(fct_i_remote_port_t *irp) { fct_i_cmd_t *icmd; rw_enter(&irp->irp_lock, RW_WRITER); icmd = irp->irp_els_list; irp->irp_els_list = icmd->icmd_next; atomic_and_32(&icmd->icmd_flags, ~ICMD_IN_IRP_QUEUE); rw_exit(&irp->irp_lock); } fct_status_t fct_register_remote_port(fct_local_port_t *port, fct_remote_port_t *rp, fct_cmd_t *cmd) { fct_status_t ret; fct_i_local_port_t *iport; fct_i_remote_port_t *irp; int i; char info[160]; iport = (fct_i_local_port_t *)port->port_fct_private; irp = (fct_i_remote_port_t *)rp->rp_fct_private; if ((ret = port->port_register_remote_port(port, rp, cmd)) != FCT_SUCCESS) return (ret); rw_enter(&iport->iport_lock, RW_WRITER); rw_enter(&irp->irp_lock, RW_WRITER); if (rp->rp_handle != FCT_HANDLE_NONE) { if (rp->rp_handle >= port->port_max_logins) { (void) snprintf(info, 160, "fct_register_remote_port: FCA " "returned a handle (%d) for portid %x which is " "out of range (max logins = %d)", rp->rp_handle, rp->rp_id, port->port_max_logins); info[159] = 0; goto hba_fatal_err; } if ((iport->iport_rp_slots[rp->rp_handle] != NULL) && (iport->iport_rp_slots[rp->rp_handle] != irp)) { fct_i_remote_port_t *t_irp = iport->iport_rp_slots[rp->rp_handle]; (void) snprintf(info, 160, "fct_register_remote_port: " "FCA returned a handle %d for portid %x " "which was already in use for a different " "portid (%x)", rp->rp_handle, rp->rp_id, t_irp->irp_rp->rp_id); info[159] = 0; goto hba_fatal_err; } } else { /* Pick a handle for this port */ for (i = 0; i < port->port_max_logins; i++) { if (iport->iport_rp_slots[i] == NULL) { break; } } if (i == port->port_max_logins) { /* This is really pushing it. */ (void) snprintf(info, 160, "fct_register_remote_port " "Cannot register portid %x because all the " "handles are used up", rp->rp_id); info[159] = 0; goto hba_fatal_err; } rp->rp_handle = i; } /* By this time rport_handle is valid */ if ((irp->irp_flags & IRP_HANDLE_OPENED) == 0) { iport->iport_rp_slots[rp->rp_handle] = irp; atomic_or_32(&irp->irp_flags, IRP_HANDLE_OPENED); } (void) atomic_add_64_nv(&iport->iport_last_change, 1); fct_log_remote_port_event(port, ESC_SUNFC_TARGET_ADD, rp->rp_pwwn, rp->rp_id); register_rp_done:; rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); return (FCT_SUCCESS); hba_fatal_err:; rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); /* * XXX Throw HBA fatal error event */ (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); return (FCT_FAILURE); } fct_status_t fct_deregister_remote_port(fct_local_port_t *port, fct_remote_port_t *rp) { fct_status_t ret = FCT_SUCCESS; fct_i_local_port_t *iport = PORT_TO_IPORT(port); fct_i_remote_port_t *irp = RP_TO_IRP(rp); if (irp->irp_snn) { kmem_free(irp->irp_snn, strlen(irp->irp_snn) + 1); irp->irp_snn = NULL; } if (irp->irp_spn) { kmem_free(irp->irp_spn, strlen(irp->irp_spn) + 1); irp->irp_spn = NULL; } if ((ret = port->port_deregister_remote_port(port, rp)) != FCT_SUCCESS) { return (ret); } if (irp->irp_flags & IRP_HANDLE_OPENED) { atomic_and_32(&irp->irp_flags, ~IRP_HANDLE_OPENED); iport->iport_rp_slots[rp->rp_handle] = NULL; } (void) atomic_add_64_nv(&iport->iport_last_change, 1); fct_log_remote_port_event(port, ESC_SUNFC_TARGET_REMOVE, rp->rp_pwwn, rp->rp_id); return (FCT_SUCCESS); } fct_status_t fct_send_accrjt(fct_cmd_t *cmd, uint8_t accrjt, uint8_t reason, uint8_t expl) { fct_local_port_t *port = (fct_local_port_t *)cmd->cmd_port; fct_els_t *els = (fct_els_t *)cmd->cmd_specific; els->els_resp_size = els->els_resp_alloc_size = 8; els->els_resp_payload = (uint8_t *)kmem_zalloc(8, KM_SLEEP); els->els_resp_payload[0] = accrjt; if (accrjt == 1) { els->els_resp_payload[5] = reason; els->els_resp_payload[6] = expl; } else { els->els_resp_size = 4; } return (port->port_send_cmd_response(cmd, 0)); } disc_action_t fct_walk_discovery_queue(fct_i_local_port_t *iport) { char info[80]; fct_i_remote_port_t **pirp; fct_i_remote_port_t *prev_irp = NULL; disc_action_t suggested_action = DISC_ACTION_NO_WORK; fct_i_remote_port_t *irp_dereg_list = NULL; fct_i_remote_port_t *irp_cur_item = NULL; for (pirp = &iport->iport_rpwe_head; *pirp != NULL; ) { fct_i_remote_port_t *irp = *pirp; disc_action_t ret = DISC_ACTION_NO_WORK; int do_deregister = 0; int irp_deregister_timer = 0; if (irp->irp_els_list) { ret |= fct_process_els(iport, irp); } irp_deregister_timer = irp->irp_deregister_timer; if (irp_deregister_timer) { if (ddi_get_lbolt() >= irp_deregister_timer) { do_deregister = 1; } else { ret |= DISC_ACTION_DELAY_RESCAN; } } suggested_action |= ret; if (irp->irp_els_list == NULL) { mutex_exit(&iport->iport_worker_lock); rw_enter(&iport->iport_lock, RW_WRITER); rw_enter(&irp->irp_lock, RW_WRITER); mutex_enter(&iport->iport_worker_lock); if (irp->irp_els_list == NULL) { if (!irp_deregister_timer || (do_deregister && !irp->irp_sa_elses_count && !irp->irp_nsa_elses_count && !irp->irp_fcp_xchg_count && !irp->irp_nonfcp_xchg_count)) { /* dequeue irp from discovery queue */ atomic_and_32(&irp->irp_flags, ~IRP_IN_DISCOVERY_QUEUE); *pirp = irp->irp_discovery_next; if (iport->iport_rpwe_head == NULL) iport->iport_rpwe_tail = NULL; else if (irp == iport->iport_rpwe_tail) iport->iport_rpwe_tail = prev_irp; irp->irp_discovery_next = NULL; if (do_deregister) { fct_deque_rp(iport, irp); rw_exit(&irp->irp_lock); /* queue irp for deregister */ irp->irp_next = NULL; if (!irp_dereg_list) { irp_dereg_list = irp_cur_item = irp; } else { irp_cur_item->irp_next = irp; irp_cur_item = irp; } } else { rw_exit(&irp->irp_lock); } rw_exit(&iport->iport_lock); if ((irp = *pirp) == NULL) break; } else { /* * wait for another scan until * deregister timeout */ rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); } } else { rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); /* * When we dropped the lock, * something went in. */ suggested_action |= DISC_ACTION_RESCAN; } } pirp = &(irp->irp_discovery_next); prev_irp = irp; } /* do deregister */ if (irp_dereg_list) { fct_i_remote_port_t *irp_next_item; /* drop the lock */ mutex_exit(&iport->iport_worker_lock); for (irp_cur_item = irp_dereg_list; irp_cur_item != NULL; ) { irp_next_item = irp_cur_item->irp_next; if (fct_deregister_remote_port(iport->iport_port, irp_cur_item->irp_rp) == FCT_SUCCESS) { fct_free(irp_cur_item->irp_rp); } else if (++irp_cur_item->irp_dereg_count >= 5) { irp_cur_item->irp_deregister_timer = 0; irp_cur_item->irp_dereg_count = 0; /* * It looks like we can't deregister it in the * normal way, so we have to use extrem way */ (void) snprintf(info, 80, "fct_walk_discovery_queue: " "iport-%p, can't deregister irp-%p after " "trying 5 times", (void *)iport, (void *)irp_cur_item); info[79] = 0; (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); suggested_action |= DISC_ACTION_RESCAN; break; } else { /* grab the iport_lock */ rw_enter(&iport->iport_lock, RW_WRITER); /* recover */ irp_cur_item->irp_deregister_timer = ddi_get_lbolt() + drv_usectohz(USEC_DEREG_RP_INTERVAL); fct_post_to_discovery_queue(iport, irp_cur_item, NULL); fct_queue_rp(iport, irp_cur_item); rw_exit(&iport->iport_lock); suggested_action |= DISC_ACTION_DELAY_RESCAN; } irp_cur_item = irp_next_item; } mutex_enter(&iport->iport_worker_lock); } return (suggested_action); } disc_action_t fct_process_plogi(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = icmd->icmd_cmd; fct_remote_port_t *rp = cmd->cmd_rp; fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; fct_els_t *els = (fct_els_t *) cmd->cmd_specific; fct_i_remote_port_t *irp = (fct_i_remote_port_t *) rp->rp_fct_private; uint8_t *p; fct_status_t ret; uint8_t cmd_type = cmd->cmd_type; uint32_t icmd_flags = icmd->icmd_flags; clock_t end_time; char info[160]; DTRACE_FC_4(rport__login__start, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd_type != FCT_CMD_RCVD_ELS)); /* Drain I/Os */ if ((irp->irp_nonfcp_xchg_count + irp->irp_fcp_xchg_count) > 1) { /* Trigger cleanup if necessary */ if ((irp->irp_flags & IRP_SESSION_CLEANUP) == 0) { stmf_trace(iport->iport_alias, "handling PLOGI rp_id" " %x. Triggering cleanup", cmd->cmd_rportid); /* Cleanup everything except elses */ if (fct_trigger_rport_cleanup(irp, ~(cmd->cmd_type))) { atomic_or_32(&irp->irp_flags, IRP_SESSION_CLEANUP); } else { /* XXX: handle this */ /* EMPTY */ } } end_time = icmd->icmd_start_time + drv_usectohz(USEC_ELS_TIMEOUT); if (ddi_get_lbolt() > end_time) { (void) snprintf(info, 160, "fct_process_plogi: unable to " "clean up I/O. iport-%p, icmd-%p", (void *)iport, (void *)icmd); info[159] = 0; (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); return (DISC_ACTION_DELAY_RESCAN); } if ((ddi_get_lbolt() & 0x7f) == 0) { stmf_trace(iport->iport_alias, "handling" " PLOGI rp_id %x, waiting for cmds to" " drain", cmd->cmd_rportid); } return (DISC_ACTION_DELAY_RESCAN); } atomic_and_32(&irp->irp_flags, ~IRP_SESSION_CLEANUP); /* Session can only be terminated after all the I/Os have drained */ if (irp->irp_flags & IRP_SCSI_SESSION_STARTED) { stmf_deregister_scsi_session(iport->iport_port->port_lport, irp->irp_session); stmf_free(irp->irp_session); irp->irp_session = NULL; atomic_and_32(&irp->irp_flags, ~IRP_SCSI_SESSION_STARTED); } if (cmd->cmd_type == FCT_CMD_RCVD_ELS) { els->els_resp_size = els->els_req_size; p = els->els_resp_payload = (uint8_t *)kmem_zalloc( els->els_resp_size, KM_SLEEP); els->els_resp_alloc_size = els->els_resp_size; bcopy(els->els_req_payload, p, els->els_resp_size); p[0] = ELS_OP_ACC; bcopy(p+20, rp->rp_pwwn, 8); bcopy(p+28, rp->rp_nwwn, 8); bcopy(port->port_pwwn, p+20, 8); bcopy(port->port_nwwn, p+28, 8); fct_wwn_to_str(rp->rp_pwwn_str, rp->rp_pwwn); fct_wwn_to_str(rp->rp_nwwn_str, rp->rp_nwwn); fct_wwn_to_str(port->port_pwwn_str, port->port_pwwn); fct_wwn_to_str(port->port_nwwn_str, port->port_nwwn); stmf_wwn_to_devid_desc((scsi_devid_desc_t *)irp->irp_id, rp->rp_pwwn, PROTOCOL_FIBRE_CHANNEL); } ret = fct_register_remote_port(port, rp, cmd); fct_dequeue_els(irp); if ((ret == FCT_SUCCESS) && !(icmd->icmd_flags & ICMD_IMPLICIT)) { if (cmd->cmd_type == FCT_CMD_RCVD_ELS) { ret = port->port_send_cmd_response(cmd, 0); if ((ret == FCT_SUCCESS) && IPORT_IN_NS_TOPO(iport) && !FC_WELL_KNOWN_ADDR(irp->irp_portid)) { fct_cmd_t *ct_cmd = fct_create_solct(port, rp, NS_GSNN_NN, fct_gsnn_cb); if (ct_cmd) { fct_post_to_solcmd_queue(port, ct_cmd); } ct_cmd = fct_create_solct(port, rp, NS_GSPN_ID, fct_gspn_cb); if (ct_cmd) fct_post_to_solcmd_queue(port, ct_cmd); ct_cmd = fct_create_solct(port, rp, NS_GCS_ID, fct_gcs_cb); if (ct_cmd) fct_post_to_solcmd_queue(port, ct_cmd); ct_cmd = fct_create_solct(port, rp, NS_GFT_ID, fct_gft_cb); if (ct_cmd) fct_post_to_solcmd_queue(port, ct_cmd); } } else { /* * The reason we set this flag is to prevent * killing a PRLI while we have not yet processed * a response to PLOGI. Because the initiator * will send a PRLI as soon as it responds to PLOGI. * Check fct_process_els() for more info. */ atomic_or_32(&irp->irp_flags, IRP_SOL_PLOGI_IN_PROGRESS); atomic_or_32(&icmd->icmd_flags, ICMD_KNOWN_TO_FCA); ret = port->port_send_cmd(cmd); if (ret != FCT_SUCCESS) { atomic_and_32(&icmd->icmd_flags, ~ICMD_KNOWN_TO_FCA); atomic_and_32(&irp->irp_flags, ~IRP_SOL_PLOGI_IN_PROGRESS); } } } atomic_add_16(&irp->irp_sa_elses_count, -1); if (ret == FCT_SUCCESS) { if (cmd_type == FCT_CMD_RCVD_ELS) { atomic_or_32(&irp->irp_flags, IRP_PLOGI_DONE); atomic_add_32(&iport->iport_nrps_login, 1); if (irp->irp_deregister_timer) irp->irp_deregister_timer = 0; } if (icmd_flags & ICMD_IMPLICIT) { DTRACE_FC_5(rport__login__end, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd_type != FCT_CMD_RCVD_ELS), int, FCT_SUCCESS); p = els->els_resp_payload; p[0] = ELS_OP_ACC; cmd->cmd_comp_status = FCT_SUCCESS; fct_send_cmd_done(cmd, FCT_SUCCESS, FCT_IOF_FCA_DONE); } } else { DTRACE_FC_5(rport__login__end, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd_type != FCT_CMD_RCVD_ELS), int, ret); fct_queue_cmd_for_termination(cmd, ret); } /* Do not touch cmd here as it may have been freed */ return (DISC_ACTION_RESCAN); } uint8_t fct_prli_temp[] = { 0x20, 0x10, 0, 0x14, 8, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; disc_action_t fct_process_prli(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = icmd->icmd_cmd; fct_remote_port_t *rp = cmd->cmd_rp; fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; fct_els_t *els = (fct_els_t *) cmd->cmd_specific; fct_i_remote_port_t *irp = (fct_i_remote_port_t *) rp->rp_fct_private; stmf_scsi_session_t *ses = NULL; fct_status_t ret; clock_t end_time; char info[160]; /* We dont support solicited PRLIs yet */ ASSERT(cmd->cmd_type == FCT_CMD_RCVD_ELS); if (irp->irp_flags & IRP_SOL_PLOGI_IN_PROGRESS) { /* * Dont process the PRLI yet. Let the framework process the * PLOGI completion 1st. This should be very quick because * the reason we got the PRLI is because the initiator * has responded to PLOGI already. */ /* XXX: Probably need a timeout here */ return (DISC_ACTION_DELAY_RESCAN); } /* The caller has made sure that login is done */ /* Make sure the process is fcp in this case */ if ((els->els_req_size != 20) || (bcmp(els->els_req_payload, fct_prli_temp, 16))) { if (els->els_req_payload[4] != 0x08) stmf_trace(iport->iport_alias, "PRLI received from" " %x for unknown FC-4 type %x", cmd->cmd_rportid, els->els_req_payload[4]); else stmf_trace(iport->iport_alias, "Rejecting PRLI from %x " " pld sz %d, prli_flags %x", cmd->cmd_rportid, els->els_req_size, els->els_req_payload[6]); fct_dequeue_els(irp); atomic_add_16(&irp->irp_sa_elses_count, -1); ret = fct_send_accrjt(cmd, ELS_OP_LSRJT, 3, 0x2c); goto prli_end; } if (irp->irp_fcp_xchg_count) { /* Trigger cleanup if necessary */ if ((irp->irp_flags & IRP_FCP_CLEANUP) == 0) { stmf_trace(iport->iport_alias, "handling PRLI from" " %x. Triggering cleanup", cmd->cmd_rportid); if (fct_trigger_rport_cleanup(irp, FCT_CMD_FCP_XCHG)) { atomic_or_32(&irp->irp_flags, IRP_FCP_CLEANUP); } else { /* XXX: handle this */ /* EMPTY */ } } end_time = icmd->icmd_start_time + drv_usectohz(USEC_ELS_TIMEOUT); if (ddi_get_lbolt() > end_time) { (void) snprintf(info, 160, "fct_process_prli: unable to clean " "up I/O. iport-%p, icmd-%p", (void *)iport, (void *)icmd); info[159] = 0; (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); return (DISC_ACTION_DELAY_RESCAN); } if ((ddi_get_lbolt() & 0x7f) == 0) { stmf_trace(iport->iport_alias, "handling" " PRLI from %x, waiting for cmds to" " drain", cmd->cmd_rportid); } return (DISC_ACTION_DELAY_RESCAN); } atomic_and_32(&irp->irp_flags, ~IRP_FCP_CLEANUP); /* Session can only be terminated after all the I/Os have drained */ if (irp->irp_flags & IRP_SCSI_SESSION_STARTED) { stmf_deregister_scsi_session(iport->iport_port->port_lport, irp->irp_session); stmf_free(irp->irp_session); irp->irp_session = NULL; atomic_and_32(&irp->irp_flags, ~IRP_SCSI_SESSION_STARTED); } /* All good, lets start a session */ ses = (stmf_scsi_session_t *)stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0, 0); if (ses) { ses->ss_port_private = irp; ses->ss_rport_id = (scsi_devid_desc_t *)irp->irp_id; ses->ss_lport = port->port_lport; if (stmf_register_scsi_session(port->port_lport, ses) != STMF_SUCCESS) { stmf_free(ses); ses = NULL; } else { irp->irp_session = ses; irp->irp_session->ss_rport_alias = irp->irp_snn; /* * The reason IRP_SCSI_SESSION_STARTED is different * from IRP_PRLI_DONE is that we clear IRP_PRLI_DONE * inside interrupt context. We dont want to deregister * the session from an interrupt. */ atomic_or_32(&irp->irp_flags, IRP_SCSI_SESSION_STARTED); } } fct_dequeue_els(irp); atomic_add_16(&irp->irp_sa_elses_count, -1); if (ses == NULL) { /* fail PRLI */ ret = fct_send_accrjt(cmd, ELS_OP_LSRJT, 3, 0); } else { /* accept PRLI */ els->els_resp_payload = (uint8_t *)kmem_zalloc(20, KM_SLEEP); bcopy(fct_prli_temp, els->els_resp_payload, 20); els->els_resp_payload[0] = 2; els->els_resp_payload[6] = 0x21; /* XXX the two bytes below need to set as per capabilities */ els->els_resp_payload[18] = 0; els->els_resp_payload[19] = 0x12; els->els_resp_size = els->els_resp_alloc_size = 20; if ((ret = port->port_send_cmd_response(cmd, 0)) != FCT_SUCCESS) { stmf_deregister_scsi_session(port->port_lport, ses); stmf_free(irp->irp_session); irp->irp_session = NULL; atomic_and_32(&irp->irp_flags, ~IRP_SCSI_SESSION_STARTED); } else { /* Mark that PRLI is done */ atomic_or_32(&irp->irp_flags, IRP_PRLI_DONE); } } prli_end:; if (ret != FCT_SUCCESS) fct_queue_cmd_for_termination(cmd, ret); return (DISC_ACTION_RESCAN); } disc_action_t fct_process_logo(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = icmd->icmd_cmd; fct_remote_port_t *rp = cmd->cmd_rp; fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; fct_i_remote_port_t *irp = (fct_i_remote_port_t *) rp->rp_fct_private; fct_status_t ret; char info[160]; clock_t end_time; DTRACE_FC_4(rport__logout__start, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd->cmd_type != FCT_CMD_RCVD_ELS)); /* Drain I/Os */ if ((irp->irp_nonfcp_xchg_count + irp->irp_fcp_xchg_count) > 1) { /* Trigger cleanup if necessary */ if ((irp->irp_flags & IRP_SESSION_CLEANUP) == 0) { stmf_trace(iport->iport_alias, "handling LOGO rp_id" " %x. Triggering cleanup", cmd->cmd_rportid); /* Cleanup everything except elses */ if (fct_trigger_rport_cleanup(irp, ~(cmd->cmd_type))) { atomic_or_32(&irp->irp_flags, IRP_SESSION_CLEANUP); } else { /* XXX: need more handling */ return (DISC_ACTION_DELAY_RESCAN); } } end_time = icmd->icmd_start_time + drv_usectohz(USEC_ELS_TIMEOUT); if (ddi_get_lbolt() > end_time) { (void) snprintf(info, 160, "fct_process_logo: unable to clean " "up I/O. iport-%p, icmd-%p", (void *)iport, (void *)icmd); info[159] = 0; (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); return (DISC_ACTION_DELAY_RESCAN); } if ((ddi_get_lbolt() & 0x7f) == 0) { stmf_trace(iport->iport_alias, "handling" " LOGO rp_id %x, waiting for cmds to" " drain", cmd->cmd_rportid); } return (DISC_ACTION_DELAY_RESCAN); } atomic_and_32(&irp->irp_flags, ~IRP_SESSION_CLEANUP); /* Session can only be terminated after all the I/Os have drained */ if (irp->irp_flags & IRP_SCSI_SESSION_STARTED) { stmf_deregister_scsi_session(iport->iport_port->port_lport, irp->irp_session); stmf_free(irp->irp_session); irp->irp_session = NULL; atomic_and_32(&irp->irp_flags, ~IRP_SCSI_SESSION_STARTED); } fct_dequeue_els(irp); atomic_add_16(&irp->irp_sa_elses_count, -1); /* don't send response if this is an implicit logout cmd */ if (!(icmd->icmd_flags & ICMD_IMPLICIT)) { if (cmd->cmd_type == FCT_CMD_RCVD_ELS) { ret = fct_send_accrjt(cmd, ELS_OP_ACC, 0, 0); } else { atomic_or_32(&icmd->icmd_flags, ICMD_KNOWN_TO_FCA); ret = port->port_send_cmd(cmd); if (ret != FCT_SUCCESS) { atomic_and_32(&icmd->icmd_flags, ~ICMD_KNOWN_TO_FCA); } } if (ret != FCT_SUCCESS) { fct_queue_cmd_for_termination(cmd, ret); } DTRACE_FC_4(rport__logout__end, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd->cmd_type != FCT_CMD_RCVD_ELS)); } else { DTRACE_FC_4(rport__logout__end, fct_cmd_t, cmd, fct_local_port_t, port, fct_i_remote_port_t, irp, int, (cmd->cmd_type != FCT_CMD_RCVD_ELS)); fct_cmd_free(cmd); } irp->irp_deregister_timer = ddi_get_lbolt() + drv_usectohz(USEC_DEREG_RP_TIMEOUT); irp->irp_dereg_count = 0; /* Do not touch cmd here as it may have been freed */ ASSERT(irp->irp_flags & IRP_IN_DISCOVERY_QUEUE); return (DISC_ACTION_RESCAN); } disc_action_t fct_process_prlo(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = icmd->icmd_cmd; fct_remote_port_t *rp = cmd->cmd_rp; fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; fct_i_remote_port_t *irp = (fct_i_remote_port_t *) rp->rp_fct_private; fct_status_t ret; clock_t end_time; char info[160]; /* We do not support solicited PRLOs yet */ ASSERT(cmd->cmd_type == FCT_CMD_RCVD_ELS); /* Drain I/Os */ if (irp->irp_fcp_xchg_count) { /* Trigger cleanup if necessary */ if ((irp->irp_flags & IRP_FCP_CLEANUP) == 0) { stmf_trace(iport->iport_alias, "handling LOGO from" " %x. Triggering cleanup", cmd->cmd_rportid); /* Cleanup everything except elses */ if (fct_trigger_rport_cleanup(irp, FCT_CMD_FCP_XCHG)) { atomic_or_32(&irp->irp_flags, IRP_FCP_CLEANUP); } else { /* XXX: need more handling */ return (DISC_ACTION_DELAY_RESCAN); } } end_time = icmd->icmd_start_time + drv_usectohz(USEC_ELS_TIMEOUT); if (ddi_get_lbolt() > end_time) { (void) snprintf(info, 160, "fct_process_prlo: unable to " "clean up I/O. iport-%p, icmd-%p", (void *)iport, (void *)icmd); info[159] = 0; (void) fct_port_shutdown(iport->iport_port, STMF_RFLAG_FATAL_ERROR | STMF_RFLAG_RESET, info); return (DISC_ACTION_DELAY_RESCAN); } if ((ddi_get_lbolt() & 0x7f) == 0) { stmf_trace(iport->iport_alias, "handling" " PRLO from %x, waiting for cmds to" " drain", cmd->cmd_rportid); } return (DISC_ACTION_DELAY_RESCAN); } atomic_and_32(&irp->irp_flags, ~IRP_FCP_CLEANUP); /* Session can only be terminated after all the I/Os have drained */ if (irp->irp_flags & IRP_SCSI_SESSION_STARTED) { stmf_deregister_scsi_session(iport->iport_port->port_lport, irp->irp_session); stmf_free(irp->irp_session); irp->irp_session = NULL; atomic_and_32(&irp->irp_flags, ~IRP_SCSI_SESSION_STARTED); } fct_dequeue_els(irp); atomic_add_16(&irp->irp_sa_elses_count, -1); ret = fct_send_accrjt(cmd, ELS_OP_ACC, 0, 0); if (ret != FCT_SUCCESS) fct_queue_cmd_for_termination(cmd, ret); return (DISC_ACTION_RESCAN); } disc_action_t fct_process_rcvd_adisc(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = icmd->icmd_cmd; fct_remote_port_t *rp = cmd->cmd_rp; fct_local_port_t *port = cmd->cmd_port; fct_i_local_port_t *iport = (fct_i_local_port_t *) port->port_fct_private; fct_els_t *els = (fct_els_t *) cmd->cmd_specific; fct_i_remote_port_t *irp = (fct_i_remote_port_t *) rp->rp_fct_private; uint8_t *p; uint32_t *q; fct_status_t ret; fct_dequeue_els(irp); atomic_add_16(&irp->irp_nsa_elses_count, -1); /* Validate the adisc request */ p = els->els_req_payload; q = (uint32_t *)p; if ((els->els_req_size != 28) || (bcmp(rp->rp_pwwn, p + 8, 8)) || (bcmp(rp->rp_nwwn, p + 16, 8))) { ret = fct_send_accrjt(cmd, ELS_OP_LSRJT, 3, 0); } else { rp->rp_hard_address = BE_32(q[1]); els->els_resp_size = els->els_resp_alloc_size = 28; els->els_resp_payload = (uint8_t *)kmem_zalloc(28, KM_SLEEP); bcopy(p, els->els_resp_payload, 28); p = els->els_resp_payload; q = (uint32_t *)p; p[0] = ELS_OP_ACC; q[1] = BE_32(port->port_hard_address); bcopy(port->port_pwwn, p + 8, 8); bcopy(port->port_nwwn, p + 16, 8); q[6] = BE_32(iport->iport_link_info.portid); ret = port->port_send_cmd_response(cmd, 0); } if (ret != FCT_SUCCESS) { fct_queue_cmd_for_termination(cmd, ret); } return (DISC_ACTION_RESCAN); } disc_action_t fct_process_unknown_els(fct_i_cmd_t *icmd) { fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); fct_status_t ret = FCT_FAILURE; uint8_t op = 0; ASSERT(icmd->icmd_cmd->cmd_type == FCT_CMD_RCVD_ELS); fct_dequeue_els(ICMD_TO_IRP(icmd)); atomic_add_16(&ICMD_TO_IRP(icmd)->irp_nsa_elses_count, -1); op = ICMD_TO_ELS(icmd)->els_req_payload[0]; stmf_trace(iport->iport_alias, "Rejecting unknown unsol els %x (%s)", op, FCT_ELS_NAME(op)); ret = fct_send_accrjt(icmd->icmd_cmd, ELS_OP_LSRJT, 1, 0); if (ret != FCT_SUCCESS) { fct_queue_cmd_for_termination(icmd->icmd_cmd, ret); } return (DISC_ACTION_RESCAN); } disc_action_t fct_process_rscn(fct_i_cmd_t *icmd) { fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); fct_status_t ret = FCT_FAILURE; uint8_t op = 0; uint8_t *rscn_req_payload; uint32_t rscn_req_size; fct_dequeue_els(ICMD_TO_IRP(icmd)); atomic_add_16(&ICMD_TO_IRP(icmd)->irp_nsa_elses_count, -1); if (icmd->icmd_cmd->cmd_type == FCT_CMD_RCVD_ELS) { op = ICMD_TO_ELS(icmd)->els_req_payload[0]; stmf_trace(iport->iport_alias, "Accepting RSCN %x (%s)", op, FCT_ELS_NAME(op)); rscn_req_size = ICMD_TO_ELS(icmd)->els_req_size; rscn_req_payload = kmem_alloc(rscn_req_size, KM_SLEEP); bcopy(ICMD_TO_ELS(icmd)->els_req_payload, rscn_req_payload, rscn_req_size); ret = fct_send_accrjt(icmd->icmd_cmd, ELS_OP_ACC, 1, 0); if (ret != FCT_SUCCESS) { fct_queue_cmd_for_termination(icmd->icmd_cmd, ret); } else { if (fct_rscn_options & RSCN_OPTION_VERIFY) { fct_rscn_verify(iport, rscn_req_payload, rscn_req_size); } } kmem_free(rscn_req_payload, rscn_req_size); } else { ASSERT(0); } return (DISC_ACTION_RESCAN); } disc_action_t fct_process_els(fct_i_local_port_t *iport, fct_i_remote_port_t *irp) { fct_i_cmd_t *cmd_to_abort = NULL; fct_i_cmd_t **ppcmd, *icmd; fct_cmd_t *cmd; fct_els_t *els; int dq; disc_action_t ret = DISC_ACTION_NO_WORK; uint8_t op; mutex_exit(&iport->iport_worker_lock); /* * Do some cleanup based on the following. * - We can only have one session affecting els pending. * - If any session affecting els is pending no other els is allowed. * - If PLOGI is not done, nothing except PLOGI or LOGO is allowed. * NOTE: If port is down the cleanup is done outside of this * function. * NOTE: There is a side effect, if a sa ELS (non PLOGI) is received * while a PLOGI is pending, it will kill itself and the PLOGI. * which is probably ok. */ rw_enter(&irp->irp_lock, RW_WRITER); ppcmd = &irp->irp_els_list; while ((*ppcmd) != NULL) { int special_prli_cond = 0; dq = 0; els = (fct_els_t *)((*ppcmd)->icmd_cmd)->cmd_specific; if (((*ppcmd)->icmd_cmd->cmd_type == FCT_CMD_RCVD_ELS) && (els->els_req_payload[0] == ELS_OP_PRLI) && (irp->irp_flags & IRP_SOL_PLOGI_IN_PROGRESS)) { /* * The initiator sent a PRLI right after responding * to PLOGI and we have not yet finished processing * the PLOGI completion. We should not kill the PRLI * as the initiator may not retry it. */ special_prli_cond = 1; } if ((*ppcmd)->icmd_flags & ICMD_BEING_ABORTED) { dq = 1; } else if (irp->irp_sa_elses_count > 1) { dq = 1; /* This els might have set the CLEANUP flag */ atomic_and_32(&irp->irp_flags, ~IRP_SESSION_CLEANUP); stmf_trace(iport->iport_alias, "Killing ELS %x cond 1", els->els_req_payload[0]); } else if (irp->irp_sa_elses_count && (((*ppcmd)->icmd_flags & ICMD_SESSION_AFFECTING) == 0)) { stmf_trace(iport->iport_alias, "Killing ELS %x cond 2", els->els_req_payload[0]); dq = 1; } else if (((irp->irp_flags & IRP_PLOGI_DONE) == 0) && (els->els_req_payload[0] != ELS_OP_PLOGI) && (els->els_req_payload[0] != ELS_OP_LOGO) && (special_prli_cond == 0)) { stmf_trace(iport->iport_alias, "Killing ELS %x cond 3", els->els_req_payload[0]); dq = 1; } if (dq) { fct_i_cmd_t *c = (*ppcmd)->icmd_next; if ((*ppcmd)->icmd_flags & ICMD_SESSION_AFFECTING) atomic_add_16(&irp->irp_sa_elses_count, -1); else atomic_add_16(&irp->irp_nsa_elses_count, -1); (*ppcmd)->icmd_next = cmd_to_abort; cmd_to_abort = *ppcmd; *ppcmd = c; } else { ppcmd = &((*ppcmd)->icmd_next); } } rw_exit(&irp->irp_lock); while (cmd_to_abort) { fct_i_cmd_t *c = cmd_to_abort->icmd_next; atomic_and_32(&cmd_to_abort->icmd_flags, ~ICMD_IN_IRP_QUEUE); fct_queue_cmd_for_termination(cmd_to_abort->icmd_cmd, FCT_ABORTED); cmd_to_abort = c; } /* * pick from the top of the queue */ icmd = irp->irp_els_list; if (icmd == NULL) { /* * The cleanup took care of everything. */ mutex_enter(&iport->iport_worker_lock); return (DISC_ACTION_RESCAN); } cmd = icmd->icmd_cmd; els = ICMD_TO_ELS(icmd); op = els->els_req_payload[0]; if ((icmd->icmd_flags & ICMD_ELS_PROCESSING_STARTED) == 0) { stmf_trace(iport->iport_alias, "Processing %ssol ELS %x (%s) " "rp_id=%x", (cmd->cmd_type == FCT_CMD_RCVD_ELS) ? "un" : "", op, FCT_ELS_NAME(op), cmd->cmd_rportid); atomic_or_32(&icmd->icmd_flags, ICMD_ELS_PROCESSING_STARTED); } if (op == ELS_OP_PLOGI) { ret |= fct_process_plogi(icmd); } else if (op == ELS_OP_PRLI) { ret |= fct_process_prli(icmd); } else if (op == ELS_OP_LOGO) { ret |= fct_process_logo(icmd); } else if ((op == ELS_OP_PRLO) || (op == ELS_OP_TPRLO)) { ret |= fct_process_prlo(icmd); } else if (cmd->cmd_type == FCT_CMD_SOL_ELS) { fct_status_t s; fct_local_port_t *port = iport->iport_port; fct_dequeue_els(irp); atomic_add_16(&irp->irp_nsa_elses_count, -1); atomic_or_32(&icmd->icmd_flags, ICMD_KNOWN_TO_FCA); if ((s = port->port_send_cmd(cmd)) != FCT_SUCCESS) { atomic_and_32(&icmd->icmd_flags, ~ICMD_KNOWN_TO_FCA); fct_queue_cmd_for_termination(cmd, s); stmf_trace(iport->iport_alias, "Solicited els " "transport failed, ret = %llx", s); } } else if (op == ELS_OP_ADISC) { ret |= fct_process_rcvd_adisc(icmd); } else if (op == ELS_OP_RSCN) { (void) fct_process_rscn(icmd); } else { (void) fct_process_unknown_els(icmd); } /* * This if condition will be false if a sa ELS trigged a cleanup * and set the ret = DISC_ACTION_DELAY_RESCAN. In that case we should * keep it that way. */ if (ret == DISC_ACTION_NO_WORK) { /* * Since we dropped the lock, we will force a rescan. The * only exception is if someone returned * DISC_ACTION_DELAY_RESCAN, in which case that should be the * return value. */ ret = DISC_ACTION_RESCAN; } mutex_enter(&iport->iport_worker_lock); return (ret); } void fct_handle_sol_els_completion(fct_i_local_port_t *iport, fct_i_cmd_t *icmd) { fct_i_remote_port_t *irp = NULL; fct_els_t *els = ICMD_TO_ELS(icmd); uint8_t op = els->els_req_payload[0]; if (icmd->icmd_cmd->cmd_rp) { irp = ICMD_TO_IRP(icmd); } if (icmd->icmd_cmd->cmd_rp && (icmd->icmd_cmd->cmd_comp_status == FCT_SUCCESS) && (els->els_req_payload[0] == ELS_OP_PLOGI)) { bcopy(els->els_resp_payload + 20, irp->irp_rp->rp_pwwn, 8); bcopy(els->els_resp_payload + 28, irp->irp_rp->rp_nwwn, 8); stmf_wwn_to_devid_desc((scsi_devid_desc_t *)irp->irp_id, irp->irp_rp->rp_pwwn, PROTOCOL_FIBRE_CHANNEL); atomic_or_32(&irp->irp_flags, IRP_PLOGI_DONE); atomic_add_32(&iport->iport_nrps_login, 1); if (irp->irp_deregister_timer) { irp->irp_deregister_timer = 0; irp->irp_dereg_count = 0; } } if (irp && (els->els_req_payload[0] == ELS_OP_PLOGI)) { atomic_and_32(&irp->irp_flags, ~IRP_SOL_PLOGI_IN_PROGRESS); } atomic_or_32(&icmd->icmd_flags, ICMD_CMD_COMPLETE); stmf_trace(iport->iport_alias, "Sol ELS %x (%s) completed with " "status %llx, did/%x", op, FCT_ELS_NAME(op), icmd->icmd_cmd->cmd_comp_status, icmd->icmd_cmd->cmd_rportid); } static disc_action_t fct_check_cmdlist(fct_i_local_port_t *iport) { int num_to_release, ndx; fct_i_cmd_t *icmd; uint32_t total, max_active; ASSERT(MUTEX_HELD(&iport->iport_worker_lock)); total = iport->iport_total_alloced_ncmds; max_active = iport->iport_max_active_ncmds; if (total <= max_active) return (DISC_ACTION_NO_WORK); /* * Everytime, we release half of the difference */ num_to_release = (total + 1 - max_active) / 2; mutex_exit(&iport->iport_worker_lock); for (ndx = 0; ndx < num_to_release; ndx++) { mutex_enter(&iport->iport_cached_cmd_lock); icmd = iport->iport_cached_cmdlist; if (icmd == NULL) { mutex_exit(&iport->iport_cached_cmd_lock); break; } iport->iport_cached_cmdlist = icmd->icmd_next; iport->iport_cached_ncmds--; mutex_exit(&iport->iport_cached_cmd_lock); atomic_add_32(&iport->iport_total_alloced_ncmds, -1); fct_free(icmd->icmd_cmd); } mutex_enter(&iport->iport_worker_lock); return (DISC_ACTION_RESCAN); } /* * The efficiency of handling solicited commands is very low here. But * fortunately, we seldom send solicited commands. So it will not hurt * the system performance much. */ static disc_action_t fct_check_solcmd_queue(fct_i_local_port_t *iport) { fct_i_cmd_t *icmd = NULL; fct_i_cmd_t *prev_icmd = NULL; fct_i_cmd_t *next_icmd = NULL; ASSERT(mutex_owned(&iport->iport_worker_lock)); for (icmd = iport->iport_solcmd_queue; icmd; icmd = next_icmd) { ASSERT(icmd->icmd_flags | ICMD_IN_SOLCMD_QUEUE); next_icmd = icmd->icmd_solcmd_next; if (icmd->icmd_flags & ICMD_SOLCMD_NEW) { /* * This solicited cmd is new. * Dispatch ELSes to discovery queue to make use of * existent framework. */ icmd->icmd_flags &= ~ICMD_SOLCMD_NEW; mutex_exit(&iport->iport_worker_lock); if (icmd->icmd_cmd->cmd_type == FCT_CMD_SOL_ELS) { fct_handle_els(icmd->icmd_cmd); } else { fct_handle_solct(icmd->icmd_cmd); } mutex_enter(&iport->iport_worker_lock); } else if (icmd->icmd_flags & ICMD_CMD_COMPLETE) { /* * To make fct_check_solcmd simple and flexible, * We need only call callback to finish post-handling. */ if (icmd->icmd_cb) { /* * mutex ??? */ icmd->icmd_cb(icmd); } /* * Release resources for this solicited cmd */ if (iport->iport_solcmd_queue == icmd) { iport->iport_solcmd_queue = next_icmd; } else { prev_icmd = iport->iport_solcmd_queue; while (prev_icmd->icmd_solcmd_next != icmd) { prev_icmd = prev_icmd->icmd_solcmd_next; } prev_icmd->icmd_solcmd_next = next_icmd; } icmd->icmd_cb = NULL; mutex_exit(&iport->iport_worker_lock); fct_cmd_free(icmd->icmd_cmd); mutex_enter(&iport->iport_worker_lock); } else { /* * This solicited cmd is still ongoing. * We need check if it's time to abort this cmd */ if (((icmd->icmd_start_time + drv_usectohz( USEC_SOL_TIMEOUT)) < ddi_get_lbolt()) && !(icmd->icmd_flags & ICMD_BEING_ABORTED)) { fct_q_for_termination_lock_held(iport, icmd, FCT_ABORTED); } } } return (DISC_ACTION_DELAY_RESCAN); } void fct_handle_solct(fct_cmd_t *cmd) { fct_status_t ret = FCT_SUCCESS; fct_i_cmd_t *icmd = CMD_TO_ICMD(cmd); fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); fct_i_remote_port_t *irp = ICMD_TO_IRP(icmd); ASSERT(cmd->cmd_type == FCT_CMD_SOL_CT); rw_enter(&iport->iport_lock, RW_READER); /* * Let's make sure local port is sane */ if ((iport->iport_link_state & S_LINK_ONLINE) == 0) { rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "fct_transport_solct: " "solcmd-%p transport failed, becasue port state was %x", cmd, iport->iport_link_state); fct_queue_cmd_for_termination(cmd, FCT_LOCAL_PORT_OFFLINE); return; } /* * Let's make sure we have plogi-ed to name server */ rw_enter(&irp->irp_lock, RW_READER); if (!(irp->irp_flags & IRP_PLOGI_DONE)) { rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "fct_transport_solct: " "Must login to name server first - cmd-%p", cmd); fct_queue_cmd_for_termination(cmd, FCT_NOT_LOGGED_IN); return; } /* * Let's get a slot for this solcmd */ if (fct_alloc_cmd_slot(iport, cmd) == FCT_SLOT_EOL) { rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); stmf_trace(iport->iport_alias, "fct_transport_solcmd: " "ran out of xchg resources - cmd-%p", cmd); fct_queue_cmd_for_termination(cmd, FCT_NO_XCHG_RESOURCE); return; } if (fct_netbuf_to_value(ICMD_TO_CT(icmd)->ct_req_payload + 8, 2) == NS_GID_PN) { fct_i_remote_port_t *query_irp = NULL; query_irp = fct_lookup_irp_by_portwwn(iport, ICMD_TO_CT(icmd)->ct_req_payload + 16); if (query_irp) { atomic_and_32(&query_irp->irp_flags, ~IRP_RSCN_QUEUED); } } rw_exit(&irp->irp_lock); rw_exit(&iport->iport_lock); atomic_add_16(&irp->irp_nonfcp_xchg_count, 1); atomic_or_32(&icmd->icmd_flags, ICMD_KNOWN_TO_FCA); icmd->icmd_start_time = ddi_get_lbolt(); ret = iport->iport_port->port_send_cmd(cmd); if (ret != FCT_SUCCESS) { atomic_and_32(&icmd->icmd_flags, ~ICMD_KNOWN_TO_FCA); fct_queue_cmd_for_termination(cmd, ret); } } void fct_logo_cb(fct_i_cmd_t *icmd) { ASSERT(!(icmd->icmd_flags & ICMD_IMPLICIT)); if (!FCT_IS_ELS_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_logo_cb: " "solicited LOGO is not accepted - icmd/%p", icmd); } } void fct_gsnn_cb(fct_i_cmd_t *icmd) { int snlen = 0; char *sn = NULL; fct_i_remote_port_t *query_irp = NULL; if (!FCT_IS_CT_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gsnn_cb: " "GSNN is not accepted by NS - icmd/%p", icmd); return; } mutex_exit(&ICMD_TO_IPORT(icmd)->iport_worker_lock); rw_enter(&ICMD_TO_IPORT(icmd)->iport_lock, RW_READER); mutex_enter(&ICMD_TO_IPORT(icmd)->iport_worker_lock); query_irp = fct_lookup_irp_by_nodewwn(ICMD_TO_IPORT(icmd), ICMD_TO_CT(icmd)->ct_req_payload + 16); if (!query_irp) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gsnn_cb: " "can't get rp icmd-%p", icmd); goto exit_gsnn_cb; } else { snlen = ICMD_TO_CT(icmd)->ct_resp_payload[16]; } if (query_irp && snlen) { /* * Release previous resource, then allocate needed resource */ sn = query_irp->irp_snn; if (sn) { kmem_free(sn, strlen(sn) + 1); } query_irp->irp_snn = NULL; sn = kmem_zalloc(snlen + 1, KM_SLEEP); (void) strncpy(sn, (char *) ICMD_TO_CT(icmd)->ct_resp_payload + 17, snlen); if (strlen(sn) != snlen) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gsnn_cb: %s, but len=%d", sn, snlen); kmem_free(sn, snlen + 1); sn = NULL; } /* * Update symbolic node name */ query_irp->irp_snn = sn; if ((query_irp->irp_flags & IRP_SCSI_SESSION_STARTED) && (query_irp->irp_session)) { query_irp->irp_session->ss_rport_alias = query_irp->irp_snn; } } else { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gsnn_cb: " "irp/%p, snlen/%d", query_irp, snlen); } exit_gsnn_cb: rw_exit(&ICMD_TO_IPORT(icmd)->iport_lock); } void fct_link_init_cb(fct_i_cmd_t *icmd) { fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); iport->iport_li_state &= ~LI_STATE_FLAG_CMD_WAITING; if (icmd->icmd_cmd->cmd_comp_status != FCT_SUCCESS) { stmf_trace(iport->iport_alias, "fct_link_init_cb: ELS-%x failed" "comp_status- %llx", ICMD_TO_ELS(icmd)->els_req_payload[0], icmd->icmd_cmd->cmd_comp_status); iport->iport_li_comp_status = icmd->icmd_cmd->cmd_comp_status; } else if (icmd->icmd_cmd->cmd_type == FCT_CMD_SOL_ELS) { if (!FCT_IS_ELS_ACC(icmd)) { stmf_trace(iport->iport_alias, "fct_link_init_cb: ELS-%x is rejected", ICMD_TO_ELS(icmd)->els_req_payload[0]); iport->iport_li_comp_status = FCT_REJECT_STATUS( ICMD_TO_ELS(icmd)->els_resp_payload[1], ICMD_TO_ELS(icmd)->els_resp_payload[2]); } else { iport->iport_li_comp_status = FCT_SUCCESS; } } else { ASSERT(icmd->icmd_cmd->cmd_type == FCT_CMD_SOL_CT); if (!FCT_IS_CT_ACC(icmd)) { stmf_trace(iport->iport_alias, "fct_link_init_cb: CT-%02x%02x is rejected", ICMD_TO_CT(icmd)->ct_req_payload[8], ICMD_TO_CT(icmd)->ct_req_payload[9]); iport->iport_li_comp_status = FCT_REJECT_STATUS( ICMD_TO_CT(icmd)->ct_resp_payload[8], ICMD_TO_CT(icmd)->ct_resp_payload[9]); } else { iport->iport_li_comp_status = FCT_SUCCESS; } } } void fct_gcs_cb(fct_i_cmd_t *icmd) { fct_sol_ct_t *ct = ICMD_TO_CT(icmd); fct_i_remote_port_t *query_irp = NULL; fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); uint32_t query_portid; uint8_t *resp; uint8_t *req; if (!FCT_IS_CT_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gcs_cb: " "GCS_ID is not accepted by NS - icmd/%p", icmd); return; } mutex_exit(&iport->iport_worker_lock); resp = ct->ct_resp_payload; req = ct->ct_req_payload; query_portid = (req[17] << 16) | (req[18] << 8) | req[19]; rw_enter(&iport->iport_lock, RW_READER); mutex_enter(&iport->iport_worker_lock); query_irp = fct_portid_to_portptr(iport, query_portid); if (query_irp) { query_irp->irp_cos = (resp[16] << 27) | (resp[17] << 18) | (resp[18] << 8) | resp[19]; } rw_exit(&iport->iport_lock); } void fct_gft_cb(fct_i_cmd_t *icmd) { fct_sol_ct_t *ct = ICMD_TO_CT(icmd); fct_i_remote_port_t *query_irp = NULL; fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); uint32_t query_portid; uint8_t *resp; uint8_t *req; if (!FCT_IS_CT_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gft_cb: " "GFT_ID is not accepted by NS - icmd/%p", icmd); return; } mutex_exit(&iport->iport_worker_lock); resp = ct->ct_resp_payload; req = ct->ct_req_payload; query_portid = (req[17] << 16) | (req[18] << 8) | req[19]; rw_enter(&iport->iport_lock, RW_READER); mutex_enter(&iport->iport_worker_lock); query_irp = fct_portid_to_portptr(iport, query_portid); if (query_irp) { (void) memcpy(query_irp->irp_fc4types, resp + 16, 32); } rw_exit(&iport->iport_lock); } void fct_gid_cb(fct_i_cmd_t *icmd) { fct_cmd_t *cmd = NULL; fct_i_remote_port_t *query_irp = NULL; uint32_t nsportid = 0; int do_logo = 0; mutex_exit(&ICMD_TO_IPORT(icmd)->iport_worker_lock); rw_enter(&ICMD_TO_IPORT(icmd)->iport_lock, RW_READER); mutex_enter(&ICMD_TO_IPORT(icmd)->iport_worker_lock); query_irp = fct_lookup_irp_by_portwwn(ICMD_TO_IPORT(icmd), ICMD_TO_CT(icmd)->ct_req_payload + 16); if (!query_irp || (query_irp && (PTR2INT(icmd->icmd_cb_private, uint32_t) != query_irp->irp_rscn_counter))) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gid_cb: " "new RSCN arrived - query_irp/%p, private-%x", query_irp, PTR2INT(icmd->icmd_cb_private, uint32_t)); goto exit_gid_cb; } if ((query_irp->irp_flags & IRP_RSCN_QUEUED) || (!(query_irp->irp_flags & IRP_PLOGI_DONE))) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gid_cb: " "not proper irp_flags - query_irp/%p", query_irp); goto exit_gid_cb; } if (!FCT_IS_CT_ACC(icmd)) { /* * Check if it has disappeared */ stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gid_cb: " "GPN_ID is not accepted by NS - icmd/%p", icmd); do_logo = 1; } else { /* * Check if its portid has changed */ nsportid = fct_netbuf_to_value( ICMD_TO_CT(icmd)->ct_resp_payload + 17, 3); if (nsportid != query_irp->irp_rp->rp_id) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "portid has changed - query_irp/%p", query_irp); do_logo = 1; } } if (do_logo) { cmd = fct_create_solels(ICMD_TO_PORT(icmd), query_irp->irp_rp, 1, ELS_OP_LOGO, 0, fct_logo_cb); if (cmd) { mutex_exit(&ICMD_TO_IPORT(icmd)->iport_worker_lock); fct_post_implicit_logo(cmd); mutex_enter(&ICMD_TO_IPORT(icmd)->iport_worker_lock); } } exit_gid_cb: rw_exit(&ICMD_TO_IPORT(icmd)->iport_lock); } void fct_gspn_cb(fct_i_cmd_t *icmd) { fct_sol_ct_t *ct = ICMD_TO_CT(icmd); fct_i_remote_port_t *query_irp = NULL; fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); uint32_t query_portid; uint8_t *resp; uint8_t *req; uint8_t spnlen; if (!FCT_IS_CT_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_gspn_cb: " "GSPN_ID is not accepted by NS - icmd/%p", icmd); return; } mutex_exit(&iport->iport_worker_lock); resp = ct->ct_resp_payload; req = ct->ct_req_payload; query_portid = (req[17] << 16) | (req[18] << 8) | req[19]; rw_enter(&iport->iport_lock, RW_READER); mutex_enter(&iport->iport_worker_lock); query_irp = fct_portid_to_portptr(iport, query_portid); if (query_irp) { spnlen = resp[16]; if (spnlen > 0) { if (query_irp->irp_spn) { kmem_free(query_irp->irp_spn, strlen(query_irp->irp_spn) + 1); } query_irp->irp_spn = kmem_zalloc(spnlen + 1, KM_SLEEP); (void) strncpy(query_irp->irp_spn, (char *)resp + 17, spnlen); } } rw_exit(&iport->iport_lock); } void fct_rls_cb(fct_i_cmd_t *icmd) { fct_els_t *els = ICMD_TO_ELS(icmd); uint8_t *resp; fct_rls_cb_data_t *rls_cb_data = NULL; fct_port_link_status_t *rls_resp; fct_i_local_port_t *iport = ICMD_TO_IPORT(icmd); rls_cb_data = icmd->icmd_cb_private; if (!FCT_IS_ELS_ACC(icmd)) { stmf_trace(ICMD_TO_IPORT(icmd)->iport_alias, "fct_rls_cb: " "solicited RLS is not accepted - icmd/%p", icmd); if (rls_cb_data) { rls_cb_data->fct_els_res = FCT_FAILURE; sema_v(&iport->iport_rls_sema); } return; } if (!rls_cb_data) { sema_v(&iport->iport_rls_sema); return; } resp = els->els_resp_payload; rls_cb_data = icmd->icmd_cb_private; /* Get the response and store it somewhere */ rls_resp = (fct_port_link_status_t *)rls_cb_data->fct_link_status; rls_resp->LinkFailureCount = BE_32(*((uint32_t *)(resp + 4))); rls_resp->LossOfSyncCount = BE_32(*((uint32_t *)(resp + 8))); rls_resp->LossOfSignalsCount = BE_32(*((uint32_t *)(resp + 12))); rls_resp->PrimitiveSeqProtocolErrorCount = BE_32(*((uint32_t *)(resp + 16))); rls_resp->InvalidTransmissionWordCount = BE_32(*((uint32_t *)(resp + 20))); rls_resp->InvalidCRCCount = BE_32(*((uint32_t *)(resp + 24))); rls_cb_data->fct_els_res = FCT_SUCCESS; sema_v(&iport->iport_rls_sema); icmd->icmd_cb_private = NULL; } /* * For lookup functions, we move locking up one level */ fct_i_remote_port_t * fct_lookup_irp_by_nodewwn(fct_i_local_port_t *iport, uint8_t *nodewwn) { fct_i_remote_port_t *irp = NULL; int idx = 0; for (idx = 0; idx < FCT_HASH_TABLE_SIZE; idx++) { for (irp = iport->iport_rp_tb[idx]; irp; irp = irp->irp_next) { if (bcmp(irp->irp_rp->rp_nwwn, nodewwn, FC_WWN_LEN)) { continue; } else { return (irp); } } } return (NULL); } fct_i_remote_port_t * fct_lookup_irp_by_portwwn(fct_i_local_port_t *iport, uint8_t *portwwn) { fct_i_remote_port_t *irp = NULL; int idx = 0; for (idx = 0; idx < FCT_HASH_TABLE_SIZE; idx++) { for (irp = iport->iport_rp_tb[idx]; irp; irp = irp->irp_next) { if (bcmp(irp->irp_rp->rp_pwwn, portwwn, FC_WWN_LEN)) { continue; } else { return (irp); } } } return (NULL); } #ifdef lint #define FCT_VERIFY_RSCN() _NOTE(EMPTY) #else #define FCT_VERIFY_RSCN() \ do { \ ct_cmd = fct_create_solct(port, irp->irp_rp, NS_GID_PN, \ fct_gid_cb); \ if (ct_cmd) { \ uint32_t cnt; \ cnt = atomic_add_32_nv(&irp->irp_rscn_counter, 1); \ CMD_TO_ICMD(ct_cmd)->icmd_cb_private = \ INT2PTR(cnt, void *); \ irp->irp_flags |= IRP_RSCN_QUEUED; \ fct_post_to_solcmd_queue(port, ct_cmd); \ } \ } while (0) #endif /* ARGSUSED */ static void fct_rscn_verify(fct_i_local_port_t *iport, uint8_t *rscn_req_payload, uint32_t rscn_req_size) { int idx = 0; uint8_t page_format = 0; uint32_t page_portid = 0; uint8_t *page_buf = NULL; uint8_t *last_page_buf = NULL; #ifndef lint fct_cmd_t *ct_cmd = NULL; fct_local_port_t *port = NULL; #endif fct_i_remote_port_t *irp = NULL; page_buf = rscn_req_payload + 4; last_page_buf = rscn_req_payload + fct_netbuf_to_value(rscn_req_payload + 2, 2) - 4; #ifndef lint port = iport->iport_port; #endif for (; page_buf <= last_page_buf; page_buf += 4) { page_format = 0x03 & page_buf[0]; page_portid = fct_netbuf_to_value(page_buf + 1, 3); DTRACE_FC_2(rscn__receive, fct_i_local_port_t, iport, int, page_portid); rw_enter(&iport->iport_lock, RW_READER); if (!page_format) { irp = fct_portid_to_portptr(iport, page_portid); if (!(irp && !(irp->irp_flags & IRP_RSCN_QUEUED))) { rw_exit(&iport->iport_lock); continue; /* try next page */ } if (FC_WELL_KNOWN_ADDR(irp->irp_portid) || !(irp->irp_flags & IRP_PLOGI_DONE)) { rw_exit(&iport->iport_lock); continue; /* try next page */ } FCT_VERIFY_RSCN(); } else { for (idx = 0; idx < FCT_HASH_TABLE_SIZE; idx++) { for (irp = iport->iport_rp_tb[idx]; irp; irp = irp->irp_next) { if (FC_WELL_KNOWN_ADDR(irp->irp_portid)) continue; /* try next irp */ if (!(irp->irp_flags & IRP_PLOGI_DONE)) continue; /* try next irp */ if (irp->irp_flags & IRP_RSCN_QUEUED) { continue; /* try next irp */ } #ifndef lint if (!((0xFFFFFF << (page_format * 8)) & (page_portid ^ irp->irp_portid))) { FCT_VERIFY_RSCN(); } #endif } } } rw_exit(&iport->iport_lock); } }