/* * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 RackTop Systems, Inc. * * NOT a DDI compliant Sun Fibre Channel port driver(fp) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* These are defined in fctl.c! */ extern int did_table_size; extern int pwwn_table_size; static struct cb_ops fp_cb_ops = { fp_open, /* open */ fp_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ fp_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* chpoll */ ddi_prop_op, /* cb_prop_op */ 0, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* cb_flag */ CB_REV, /* rev */ nodev, /* aread */ nodev /* awrite */ }; static struct dev_ops fp_ops = { DEVO_REV, /* build revision */ 0, /* reference count */ fp_getinfo, /* getinfo */ nulldev, /* identify - Obsoleted */ nulldev, /* probe */ fp_attach, /* attach */ fp_detach, /* detach */ nodev, /* reset */ &fp_cb_ops, /* cb_ops */ NULL, /* bus_ops */ fp_power, /* power */ ddi_quiesce_not_needed /* quiesce */ }; #define FP_VERSION "20091123-1.101" #define FP_NAME_VERSION "SunFC Port v" FP_VERSION char *fp_version = FP_NAME_VERSION; static struct modldrv modldrv = { &mod_driverops, /* Type of Module */ FP_NAME_VERSION, /* Name/Version of fp */ &fp_ops /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, /* Rev of the loadable modules system */ &modldrv, /* NULL terminated list of */ NULL /* Linkage structures */ }; static uint16_t ns_reg_cmds[] = { NS_RPN_ID, NS_RNN_ID, NS_RCS_ID, NS_RFT_ID, NS_RPT_ID, NS_RSPN_ID, NS_RSNN_NN }; struct fp_xlat { uchar_t xlat_state; int xlat_rval; } fp_xlat [] = { { FC_PKT_SUCCESS, FC_SUCCESS }, { FC_PKT_REMOTE_STOP, FC_FAILURE }, { FC_PKT_LOCAL_RJT, FC_FAILURE }, { FC_PKT_NPORT_RJT, FC_ELS_PREJECT }, { FC_PKT_FABRIC_RJT, FC_ELS_FREJECT }, { FC_PKT_LOCAL_BSY, FC_TRAN_BUSY }, { FC_PKT_TRAN_BSY, FC_TRAN_BUSY }, { FC_PKT_NPORT_BSY, FC_PBUSY }, { FC_PKT_FABRIC_BSY, FC_FBUSY }, { FC_PKT_LS_RJT, FC_FAILURE }, { FC_PKT_BA_RJT, FC_FAILURE }, { FC_PKT_TIMEOUT, FC_FAILURE }, { FC_PKT_TRAN_ERROR, FC_TRANSPORT_ERROR }, { FC_PKT_FAILURE, FC_FAILURE }, { FC_PKT_PORT_OFFLINE, FC_OFFLINE } }; static uchar_t fp_valid_alpas[] = { 0x01, 0x02, 0x04, 0x08, 0x0F, 0x10, 0x17, 0x18, 0x1B, 0x1D, 0x1E, 0x1F, 0x23, 0x25, 0x26, 0x27, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x39, 0x3A, 0x3C, 0x43, 0x45, 0x46, 0x47, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65, 0x66, 0x67, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0x7A, 0x7C, 0x80, 0x81, 0x82, 0x84, 0x88, 0x8F, 0x90, 0x97, 0x98, 0x9B, 0x9D, 0x9E, 0x9F, 0xA3, 0xA5, 0xA6, 0xA7, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB9, 0xBA, 0xBC, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD9, 0xDA, 0xDC, 0xE0, 0xE1, 0xE2, 0xE4, 0xE8, 0xEF }; static struct fp_perms { uint16_t fp_ioctl_cmd; uchar_t fp_open_flag; } fp_perm_list [] = { { FCIO_GET_NUM_DEVS, FP_OPEN }, { FCIO_GET_DEV_LIST, FP_OPEN }, { FCIO_GET_SYM_PNAME, FP_OPEN }, { FCIO_GET_SYM_NNAME, FP_OPEN }, { FCIO_SET_SYM_PNAME, FP_EXCL }, { FCIO_SET_SYM_NNAME, FP_EXCL }, { FCIO_GET_LOGI_PARAMS, FP_OPEN }, { FCIO_DEV_LOGIN, FP_EXCL }, { FCIO_DEV_LOGOUT, FP_EXCL }, { FCIO_GET_STATE, FP_OPEN }, { FCIO_DEV_REMOVE, FP_EXCL }, { FCIO_GET_FCODE_REV, FP_OPEN }, { FCIO_GET_FW_REV, FP_OPEN }, { FCIO_GET_DUMP_SIZE, FP_OPEN }, { FCIO_FORCE_DUMP, FP_EXCL }, { FCIO_GET_DUMP, FP_OPEN }, { FCIO_GET_TOPOLOGY, FP_OPEN }, { FCIO_RESET_LINK, FP_EXCL }, { FCIO_RESET_HARD, FP_EXCL }, { FCIO_RESET_HARD_CORE, FP_EXCL }, { FCIO_DIAG, FP_OPEN }, { FCIO_NS, FP_EXCL }, { FCIO_DOWNLOAD_FW, FP_EXCL }, { FCIO_DOWNLOAD_FCODE, FP_EXCL }, { FCIO_LINK_STATUS, FP_OPEN }, { FCIO_GET_HOST_PARAMS, FP_OPEN }, { FCIO_GET_NODE_ID, FP_OPEN }, { FCIO_SET_NODE_ID, FP_EXCL }, { FCIO_SEND_NODE_ID, FP_OPEN }, { FCIO_GET_ADAPTER_ATTRIBUTES, FP_OPEN }, { FCIO_GET_OTHER_ADAPTER_PORTS, FP_OPEN }, { FCIO_GET_ADAPTER_PORT_ATTRIBUTES, FP_OPEN }, { FCIO_GET_DISCOVERED_PORT_ATTRIBUTES, FP_OPEN }, { FCIO_GET_PORT_ATTRIBUTES, FP_OPEN }, { FCIO_GET_ADAPTER_PORT_STATS, FP_OPEN }, { FCIO_GET_ADAPTER_PORT_NPIV_ATTRIBUTES, FP_OPEN }, { FCIO_GET_NPIV_PORT_LIST, FP_OPEN }, { FCIO_DELETE_NPIV_PORT, FP_OPEN }, { FCIO_GET_NPIV_ATTRIBUTES, FP_OPEN }, { FCIO_CREATE_NPIV_PORT, FP_OPEN }, { FCIO_NPIV_GET_ADAPTER_ATTRIBUTES, FP_OPEN } }; static char *fp_pm_comps[] = { "NAME=FC Port", "0=Port Down", "1=Port Up" }; #ifdef _LITTLE_ENDIAN #define MAKE_BE_32(x) { \ uint32_t *ptr1, i; \ ptr1 = (uint32_t *)(x); \ for (i = 0; i < sizeof (*(x)) / sizeof (uint32_t); i++) { \ *ptr1 = BE_32(*ptr1); \ ptr1++; \ } \ } #else #define MAKE_BE_32(x) #endif static uchar_t fp_verbosity = (FP_WARNING_MESSAGES | FP_FATAL_MESSAGES); static uint32_t fp_options = 0; static int fp_cmd_wait_cnt = FP_CMDWAIT_DELAY; static int fp_retry_delay = FP_RETRY_DELAY; /* retry after this delay */ static int fp_retry_count = FP_RETRY_COUNT; /* number of retries */ unsigned int fp_offline_ticker; /* seconds */ /* * Driver global variable to anchor the list of soft state structs for * all fp driver instances. Used with the Solaris DDI soft state functions. */ static void *fp_driver_softstate; static clock_t fp_retry_ticks; static clock_t fp_offline_ticks; static int fp_retry_ticker; static uint32_t fp_unsol_buf_count = FP_UNSOL_BUF_COUNT; static uint32_t fp_unsol_buf_size = FP_UNSOL_BUF_SIZE; static int fp_log_size = FP_LOG_SIZE; static int fp_trace = FP_TRACE_DEFAULT; static fc_trace_logq_t *fp_logq = NULL; int fp_get_adapter_paths(char *pathList, int count); static void fp_log_port_event(fc_local_port_t *port, char *subclass); static void fp_log_target_event(fc_local_port_t *port, char *subclass, la_wwn_t tgt_pwwn, uint32_t port_id); static uint32_t fp_map_remote_port_state(uint32_t rm_state); static void fp_init_symbolic_names(fc_local_port_t *port); /* * Perform global initialization */ int _init(void) { int ret; if ((ret = ddi_soft_state_init(&fp_driver_softstate, sizeof (struct fc_local_port), 8)) != 0) { return (ret); } if ((ret = scsi_hba_init(&modlinkage)) != 0) { ddi_soft_state_fini(&fp_driver_softstate); return (ret); } fp_logq = fc_trace_alloc_logq(fp_log_size); if ((ret = mod_install(&modlinkage)) != 0) { fc_trace_free_logq(fp_logq); ddi_soft_state_fini(&fp_driver_softstate); scsi_hba_fini(&modlinkage); } return (ret); } /* * Prepare for driver unload */ int _fini(void) { int ret; if ((ret = mod_remove(&modlinkage)) == 0) { fc_trace_free_logq(fp_logq); ddi_soft_state_fini(&fp_driver_softstate); scsi_hba_fini(&modlinkage); } return (ret); } /* * Request mod_info() to handle all cases */ int _info(struct modinfo *modinfo) { return (mod_info(&modlinkage, modinfo)); } /* * fp_attach: * * The respective cmd handlers take care of performing * ULP related invocations */ static int fp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int rval; /* * We check the value of fp_offline_ticker at this * point. The variable is global for the driver and * not specific to an instance. * * If there is no user-defined value found in /etc/system * or fp.conf, then we use 90 seconds (FP_OFFLINE_TICKER). * The minimum setting for this offline timeout according * to the FC-FS2 standard (Fibre Channel Framing and * Signalling-2, see www.t11.org) is R_T_TOV == 100msec. * * We do not recommend setting the value to less than 10 * seconds (RA_TOV) or more than 90 seconds. If this * variable is greater than 90 seconds then drivers above * fp (fcp, sd, scsi_vhci, vxdmp et al) might complain. */ fp_offline_ticker = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "fp_offline_ticker", FP_OFFLINE_TICKER); if ((fp_offline_ticker < 10) || (fp_offline_ticker > 90)) { cmn_err(CE_WARN, "Setting fp_offline_ticker to " "%d second(s). This is outside the " "recommended range of 10..90 seconds", fp_offline_ticker); } /* * Tick every second when there are commands to retry. * It should tick at the least granular value of pkt_timeout * (which is one second) */ fp_retry_ticker = 1; fp_retry_ticks = drv_usectohz(fp_retry_ticker * 1000 * 1000); fp_offline_ticks = drv_usectohz(fp_offline_ticker * 1000 * 1000); switch (cmd) { case DDI_ATTACH: rval = fp_attach_handler(dip); break; case DDI_RESUME: rval = fp_resume_handler(dip); break; default: rval = DDI_FAILURE; break; } return (rval); } /* * fp_detach: * * If a ULP fails to handle cmd request converse of * cmd is invoked for ULPs that previously succeeded * cmd request. */ static int fp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int rval = DDI_FAILURE; fc_local_port_t *port; fc_attach_cmd_t converse; uint8_t cnt; if ((port = ddi_get_soft_state(fp_driver_softstate, ddi_get_instance(dip))) == NULL) { return (DDI_FAILURE); } mutex_enter(&port->fp_mutex); if (port->fp_ulp_attach) { mutex_exit(&port->fp_mutex); return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: if (port->fp_task != FP_TASK_IDLE) { mutex_exit(&port->fp_mutex); return (DDI_FAILURE); } /* Let's attempt to quit the job handler gracefully */ port->fp_soft_state |= FP_DETACH_INPROGRESS; mutex_exit(&port->fp_mutex); converse = FC_CMD_ATTACH; if (fctl_detach_ulps(port, FC_CMD_DETACH, &modlinkage) != FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_soft_state &= ~FP_DETACH_INPROGRESS; mutex_exit(&port->fp_mutex); rval = DDI_FAILURE; break; } mutex_enter(&port->fp_mutex); for (cnt = 0; (port->fp_job_head) && (cnt < fp_cmd_wait_cnt); cnt++) { mutex_exit(&port->fp_mutex); delay(drv_usectohz(1000000)); mutex_enter(&port->fp_mutex); } if (port->fp_job_head) { mutex_exit(&port->fp_mutex); rval = DDI_FAILURE; break; } mutex_exit(&port->fp_mutex); rval = fp_detach_handler(port); break; case DDI_SUSPEND: mutex_exit(&port->fp_mutex); converse = FC_CMD_RESUME; if (fctl_detach_ulps(port, FC_CMD_SUSPEND, &modlinkage) != FC_SUCCESS) { rval = DDI_FAILURE; break; } if ((rval = fp_suspend_handler(port)) != DDI_SUCCESS) { (void) callb_generic_cpr(&port->fp_cpr_info, CB_CODE_CPR_RESUME); } break; default: mutex_exit(&port->fp_mutex); break; } /* * Use softint to perform reattach. Mark fp_ulp_attach so we * don't attempt to do this repeatedly on behalf of some persistent * caller. */ if (rval != DDI_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_ulp_attach = 1; /* * If the port is in the low power mode then there is * possibility that fca too could be in low power mode. * Try to raise the power before calling attach ulps. */ if ((port->fp_soft_state & FP_SOFT_POWER_DOWN) && (!(port->fp_soft_state & FP_SOFT_NO_PMCOMP))) { mutex_exit(&port->fp_mutex); (void) pm_raise_power(port->fp_port_dip, FP_PM_COMPONENT, FP_PM_PORT_UP); } else { mutex_exit(&port->fp_mutex); } fp_attach_ulps(port, converse); mutex_enter(&port->fp_mutex); while (port->fp_ulp_attach) { cv_wait(&port->fp_attach_cv, &port->fp_mutex); } port->fp_soft_state &= ~FP_DETACH_INPROGRESS; /* * Mark state as detach failed so asynchronous ULP attach * events (downstream, not the ones we're initiating with * the call to fp_attach_ulps) are not honored. We're * really still in pending detach. */ port->fp_soft_state |= FP_DETACH_FAILED; mutex_exit(&port->fp_mutex); } return (rval); } /* * fp_getinfo: * Given the device number, return either the * dev_info_t pointer or the instance number. */ /* ARGSUSED */ static int fp_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { int rval; minor_t instance; fc_local_port_t *port; rval = DDI_SUCCESS; instance = getminor((dev_t)arg); switch (cmd) { case DDI_INFO_DEVT2DEVINFO: if ((port = ddi_get_soft_state(fp_driver_softstate, instance)) == NULL) { rval = DDI_FAILURE; break; } *result = (void *)port->fp_port_dip; break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)instance; break; default: rval = DDI_FAILURE; break; } return (rval); } /* * Entry point for power up and power down request from kernel */ static int fp_power(dev_info_t *dip, int comp, int level) { int rval = DDI_FAILURE; fc_local_port_t *port; port = ddi_get_soft_state(fp_driver_softstate, ddi_get_instance(dip)); if (port == NULL || comp != FP_PM_COMPONENT) { return (rval); } switch (level) { case FP_PM_PORT_UP: rval = DDI_SUCCESS; /* * If the port is DDI_SUSPENDed, let the DDI_RESUME * code complete the rediscovery. */ mutex_enter(&port->fp_mutex); if (port->fp_soft_state & FP_SOFT_SUSPEND) { port->fp_soft_state &= ~FP_SOFT_POWER_DOWN; port->fp_pm_level = FP_PM_PORT_UP; mutex_exit(&port->fp_mutex); fctl_attach_ulps(port, FC_CMD_POWER_UP, &modlinkage); break; } if (port->fp_soft_state & FP_SOFT_POWER_DOWN) { ASSERT(port->fp_pm_level == FP_PM_PORT_DOWN); port->fp_pm_level = FP_PM_PORT_UP; rval = fp_power_up(port); if (rval != DDI_SUCCESS) { port->fp_pm_level = FP_PM_PORT_DOWN; } } else { port->fp_pm_level = FP_PM_PORT_UP; } mutex_exit(&port->fp_mutex); break; case FP_PM_PORT_DOWN: mutex_enter(&port->fp_mutex); ASSERT(!(port->fp_soft_state & FP_SOFT_NO_PMCOMP)); if (port->fp_soft_state & FP_SOFT_NO_PMCOMP) { /* * PM framework goofed up. We have don't * have any PM components. Let's never go down. */ mutex_exit(&port->fp_mutex); break; } if (port->fp_ulp_attach) { /* We shouldn't let the power go down */ mutex_exit(&port->fp_mutex); break; } /* * Not a whole lot to do if we are detaching */ if (port->fp_soft_state & FP_SOFT_IN_DETACH) { port->fp_pm_level = FP_PM_PORT_DOWN; mutex_exit(&port->fp_mutex); rval = DDI_SUCCESS; break; } if (!port->fp_pm_busy && !port->fp_pm_busy_nocomp) { port->fp_pm_level = FP_PM_PORT_DOWN; rval = fp_power_down(port); if (rval != DDI_SUCCESS) { port->fp_pm_level = FP_PM_PORT_UP; ASSERT(!(port->fp_soft_state & FP_SOFT_POWER_DOWN)); } else { ASSERT(port->fp_soft_state & FP_SOFT_POWER_DOWN); } } mutex_exit(&port->fp_mutex); break; default: break; } return (rval); } /* * Open FC port devctl node */ static int fp_open(dev_t *devp, int flag, int otype, cred_t *credp) { int instance; fc_local_port_t *port; if (otype != OTYP_CHR) { return (EINVAL); } /* * This is not a toy to play with. Allow only powerful * users (hopefully knowledgeable) to access the port * (A hacker potentially could download a sick binary * file into FCA) */ if (drv_priv(credp)) { return (EPERM); } instance = (int)getminor(*devp); port = ddi_get_soft_state(fp_driver_softstate, instance); if (port == NULL) { return (ENXIO); } mutex_enter(&port->fp_mutex); if (port->fp_flag & FP_EXCL) { /* * It is already open for exclusive access. * So shut the door on this caller. */ mutex_exit(&port->fp_mutex); return (EBUSY); } if (flag & FEXCL) { if (port->fp_flag & FP_OPEN) { /* * Exclusive operation not possible * as it is already opened */ mutex_exit(&port->fp_mutex); return (EBUSY); } port->fp_flag |= FP_EXCL; } port->fp_flag |= FP_OPEN; mutex_exit(&port->fp_mutex); return (0); } /* * The driver close entry point is called on the last close() * of a device. So it is perfectly alright to just clobber the * open flag and reset it to idle (instead of having to reset * each flag bits). For any confusion, check out close(9E). */ /* ARGSUSED */ static int fp_close(dev_t dev, int flag, int otype, cred_t *credp) { int instance; fc_local_port_t *port; if (otype != OTYP_CHR) { return (EINVAL); } instance = (int)getminor(dev); port = ddi_get_soft_state(fp_driver_softstate, instance); if (port == NULL) { return (ENXIO); } mutex_enter(&port->fp_mutex); if ((port->fp_flag & FP_OPEN) == 0) { mutex_exit(&port->fp_mutex); return (ENODEV); } port->fp_flag = FP_IDLE; mutex_exit(&port->fp_mutex); return (0); } /* * Handle IOCTL requests */ /* ARGSUSED */ static int fp_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval) { int instance; int ret = 0; fcio_t fcio; fc_local_port_t *port; instance = (int)getminor(dev); port = ddi_get_soft_state(fp_driver_softstate, instance); if (port == NULL) { return (ENXIO); } mutex_enter(&port->fp_mutex); if ((port->fp_flag & FP_OPEN) == 0) { mutex_exit(&port->fp_mutex); return (ENXIO); } if (port->fp_soft_state & FP_SOFT_SUSPEND) { mutex_exit(&port->fp_mutex); return (ENXIO); } mutex_exit(&port->fp_mutex); /* this will raise power if necessary */ ret = fctl_busy_port(port); if (ret != 0) { return (ret); } ASSERT(port->fp_pm_level == FP_PM_PORT_UP); switch (cmd) { case FCIO_CMD: { #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcio32 fcio32; if (ddi_copyin((void *)data, (void *)&fcio32, sizeof (struct fcio32), mode)) { ret = EFAULT; break; } fcio.fcio_xfer = fcio32.fcio_xfer; fcio.fcio_cmd = fcio32.fcio_cmd; fcio.fcio_flags = fcio32.fcio_flags; fcio.fcio_cmd_flags = fcio32.fcio_cmd_flags; fcio.fcio_ilen = (size_t)fcio32.fcio_ilen; fcio.fcio_ibuf = (caddr_t)(uintptr_t)fcio32.fcio_ibuf; fcio.fcio_olen = (size_t)fcio32.fcio_olen; fcio.fcio_obuf = (caddr_t)(uintptr_t)fcio32.fcio_obuf; fcio.fcio_alen = (size_t)fcio32.fcio_alen; fcio.fcio_abuf = (caddr_t)(uintptr_t)fcio32.fcio_abuf; fcio.fcio_errno = fcio32.fcio_errno; break; } case DDI_MODEL_NONE: if (ddi_copyin((void *)data, (void *)&fcio, sizeof (fcio_t), mode)) { ret = EFAULT; } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyin((void *)data, (void *)&fcio, sizeof (fcio_t), mode)) { ret = EFAULT; break; } #endif /* _MULTI_DATAMODEL */ if (!ret) { ret = fp_fciocmd(port, data, mode, &fcio); } break; } default: ret = fctl_ulp_port_ioctl(port, dev, cmd, data, mode, credp, rval); } fctl_idle_port(port); return (ret); } /* * Init Symbolic Port Name and Node Name * LV will try to get symbolic names from FCA driver * and register these to name server, * if LV fails to get these, * LV will register its default symbolic names to name server. * The Default symbolic node name format is : * :(instance) * The Default symbolic port name format is : * */ static void fp_init_symbolic_names(fc_local_port_t *port) { const char *vendorname = ddi_driver_name(port->fp_fca_dip); char *sym_name; char fcaname[50] = {0}; int hostnlen, fcanlen; if (port->fp_sym_node_namelen == 0) { hostnlen = strlen(utsname.nodename); (void) snprintf(fcaname, sizeof (fcaname), "%s%d", vendorname, ddi_get_instance(port->fp_fca_dip)); fcanlen = strlen(fcaname); sym_name = kmem_zalloc(hostnlen + fcanlen + 2, KM_SLEEP); (void) sprintf(sym_name, "%s:%s", utsname.nodename, fcaname); port->fp_sym_node_namelen = strlen(sym_name); if (port->fp_sym_node_namelen >= FCHBA_SYMB_NAME_LEN) { port->fp_sym_node_namelen = FCHBA_SYMB_NAME_LEN; } (void) strncpy(port->fp_sym_node_name, sym_name, port->fp_sym_node_namelen); kmem_free(sym_name, hostnlen + fcanlen + 2); } if (port->fp_sym_port_namelen == 0) { char *pathname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(port->fp_port_dip, pathname); port->fp_sym_port_namelen = strlen(pathname); if (port->fp_sym_port_namelen >= FCHBA_SYMB_NAME_LEN) { port->fp_sym_port_namelen = FCHBA_SYMB_NAME_LEN; } (void) strncpy(port->fp_sym_port_name, pathname, port->fp_sym_port_namelen); kmem_free(pathname, MAXPATHLEN); } } /* * Perform port attach */ static int fp_attach_handler(dev_info_t *dip) { int rval; int instance; int port_num; int port_len; char name[30]; char i_pwwn[17]; fp_cmd_t *pkt; uint32_t ub_count; fc_local_port_t *port; job_request_t *job; fc_local_port_t *phyport = NULL; int portpro1; char pwwn[17], nwwn[17]; instance = ddi_get_instance(dip); port_len = sizeof (port_num); rval = ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "port", (caddr_t)&port_num, &port_len); if (rval != DDI_SUCCESS) { cmn_err(CE_WARN, "fp(%d): No port property in devinfo", instance); return (DDI_FAILURE); } if (ddi_create_minor_node(dip, "devctl", S_IFCHR, instance, DDI_NT_NEXUS, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "fp(%d): failed to create devctl minor node", instance); return (DDI_FAILURE); } if (ddi_create_minor_node(dip, "fc", S_IFCHR, instance, DDI_NT_FC_ATTACHMENT_POINT, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "fp(%d): failed to create fc attachment" " point minor node", instance); ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } if (ddi_soft_state_zalloc(fp_driver_softstate, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "fp(%d): failed to alloc soft state", instance); ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } port = ddi_get_soft_state(fp_driver_softstate, instance); (void) sprintf(port->fp_ibuf, "fp(%d)", instance); port->fp_instance = instance; port->fp_ulp_attach = 1; port->fp_port_num = port_num; port->fp_verbose = fp_verbosity; port->fp_options = fp_options; port->fp_fca_dip = ddi_get_parent(dip); port->fp_port_dip = dip; port->fp_fca_tran = (fc_fca_tran_t *) ddi_get_driver_private(port->fp_fca_dip); port->fp_task = port->fp_last_task = FP_TASK_IDLE; /* * Init the starting value of fp_rscn_count. Note that if * FC_INVALID_RSCN_COUNT is 0 (which is what it currently is), the * actual # of RSCNs will be (fp_rscn_count - 1) */ port->fp_rscn_count = FC_INVALID_RSCN_COUNT + 1; mutex_init(&port->fp_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&port->fp_cv, NULL, CV_DRIVER, NULL); cv_init(&port->fp_attach_cv, NULL, CV_DRIVER, NULL); (void) sprintf(name, "fp%d_cache", instance); if ((portpro1 = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "phyport-instance", -1)) != -1) { phyport = ddi_get_soft_state(fp_driver_softstate, portpro1); fc_wwn_to_str(&phyport->fp_service_params.nport_ww_name, pwwn); fc_wwn_to_str(&phyport->fp_service_params.node_ww_name, nwwn); port->fp_npiv_type = FC_NPIV_PORT; } /* * Allocate the pool of fc_packet_t structs to be used with * this fp instance. */ port->fp_pkt_cache = kmem_cache_create(name, (port->fp_fca_tran->fca_pkt_size) + sizeof (fp_cmd_t), 8, fp_cache_constructor, fp_cache_destructor, NULL, (void *)port, NULL, 0); port->fp_out_fpcmds = 0; if (port->fp_pkt_cache == NULL) { goto cache_alloc_failed; } /* * Allocate the d_id and pwwn hash tables for all remote ports * connected to this local port. */ port->fp_did_table = kmem_zalloc(did_table_size * sizeof (struct d_id_hash), KM_SLEEP); port->fp_pwwn_table = kmem_zalloc(pwwn_table_size * sizeof (struct pwwn_hash), KM_SLEEP); port->fp_taskq = taskq_create("fp_ulp_callback", 1, MINCLSYSPRI, 1, 16, 0); /* Indicate that don't have the pm components yet */ port->fp_soft_state |= FP_SOFT_NO_PMCOMP; /* * Bind the callbacks with the FCA driver. This will open the gate * for asynchronous callbacks, so after this call the fp_mutex * must be held when updating the fc_local_port_t struct. * * This is done _before_ setting up the job thread so we can avoid * cleaning up after the thread_create() in the error path. This * also means fp will be operating with fp_els_resp_pkt set to NULL. */ if (fp_bind_callbacks(port) != DDI_SUCCESS) { goto bind_callbacks_failed; } if (phyport) { mutex_enter(&phyport->fp_mutex); if (phyport->fp_port_next) { phyport->fp_port_next->fp_port_prev = port; port->fp_port_next = phyport->fp_port_next; phyport->fp_port_next = port; port->fp_port_prev = phyport; } else { phyport->fp_port_next = port; phyport->fp_port_prev = port; port->fp_port_next = phyport; port->fp_port_prev = phyport; } mutex_exit(&phyport->fp_mutex); } /* * Init Symbolic Names */ fp_init_symbolic_names(port); pkt = fp_alloc_pkt(port, sizeof (la_els_logi_t), sizeof (la_els_logi_t), KM_SLEEP, NULL); if (pkt == NULL) { cmn_err(CE_WARN, "fp(%d): failed to allocate ELS packet", instance); goto alloc_els_packet_failed; } (void) thread_create(NULL, 0, fp_job_handler, port, 0, &p0, TS_RUN, v.v_maxsyspri - 2); fc_wwn_to_str(&port->fp_service_params.nport_ww_name, i_pwwn); if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, "initiator-port", i_pwwn) != DDI_PROP_SUCCESS) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp(%d): Updating 'initiator-port' property" " on fp dev_info node failed", instance); } fc_wwn_to_str(&port->fp_service_params.node_ww_name, i_pwwn); if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, "initiator-node", i_pwwn) != DDI_PROP_SUCCESS) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp(%d): Updating 'initiator-node' property" " on fp dev_info node failed", instance); } mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt = pkt; mutex_exit(&port->fp_mutex); /* * Determine the count of unsolicited buffers this FCA can support */ fp_retrieve_caps(port); /* * Allocate unsolicited buffer tokens */ if (port->fp_ub_count) { ub_count = port->fp_ub_count; port->fp_ub_tokens = kmem_zalloc(ub_count * sizeof (*port->fp_ub_tokens), KM_SLEEP); /* * Do not fail the attach if unsolicited buffer allocation * fails; Just try to get along with whatever the FCA can do. */ if (fc_ulp_uballoc(port, &ub_count, fp_unsol_buf_size, FC_TYPE_EXTENDED_LS, port->fp_ub_tokens) != FC_SUCCESS || ub_count != port->fp_ub_count) { cmn_err(CE_WARN, "fp(%d): failed to allocate " " Unsolicited buffers. proceeding with attach...", instance); kmem_free(port->fp_ub_tokens, sizeof (*port->fp_ub_tokens) * port->fp_ub_count); port->fp_ub_tokens = NULL; } } fp_load_ulp_modules(dip, port); /* * Enable DDI_SUSPEND and DDI_RESUME for this instance. */ (void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, "pm-hardware-state", "needs-suspend-resume", strlen("needs-suspend-resume") + 1); /* * fctl maintains a list of all port handles, so * help fctl add this one to its list now. */ mutex_enter(&port->fp_mutex); fctl_add_port(port); /* * If a state change is already in progress, set the bind state t * OFFLINE as well, so further state change callbacks into ULPs * will pass the appropriate states */ if (FC_PORT_STATE_MASK(port->fp_bind_state) == FC_STATE_OFFLINE || port->fp_statec_busy) { port->fp_bind_state = FC_STATE_OFFLINE; mutex_exit(&port->fp_mutex); fp_startup_done((opaque_t)port, FC_PKT_SUCCESS); } else { /* * Without dropping the mutex, ensure that the port * startup happens ahead of state change callback * processing */ ASSERT(port->fp_job_tail == NULL && port->fp_job_head == NULL); port->fp_last_task = port->fp_task; port->fp_task = FP_TASK_PORT_STARTUP; job = fctl_alloc_job(JOB_PORT_STARTUP, JOB_TYPE_FCTL_ASYNC, fp_startup_done, (opaque_t)port, KM_SLEEP); port->fp_job_head = port->fp_job_tail = job; cv_signal(&port->fp_cv); mutex_exit(&port->fp_mutex); } mutex_enter(&port->fp_mutex); while (port->fp_ulp_attach) { cv_wait(&port->fp_attach_cv, &port->fp_mutex); } mutex_exit(&port->fp_mutex); if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip, "pm-components", fp_pm_comps, sizeof (fp_pm_comps) / sizeof (fp_pm_comps[0])) != DDI_PROP_SUCCESS) { FP_TRACE(FP_NHEAD2(9, 0), "Failed to create PM" " components property, PM disabled on this port."); mutex_enter(&port->fp_mutex); port->fp_pm_level = FP_PM_PORT_UP; mutex_exit(&port->fp_mutex); } else { if (pm_raise_power(dip, FP_PM_COMPONENT, FP_PM_PORT_UP) != DDI_SUCCESS) { FP_TRACE(FP_NHEAD2(9, 0), "Failed to raise" " power level"); mutex_enter(&port->fp_mutex); port->fp_pm_level = FP_PM_PORT_UP; mutex_exit(&port->fp_mutex); } /* * Don't unset the FP_SOFT_NO_PMCOMP flag until after * the call to pm_raise_power. The PM framework can't * handle multiple threads calling into it during attach. */ mutex_enter(&port->fp_mutex); port->fp_soft_state &= ~FP_SOFT_NO_PMCOMP; mutex_exit(&port->fp_mutex); } ddi_report_dev(dip); fp_log_port_event(port, ESC_SUNFC_PORT_ATTACH); return (DDI_SUCCESS); /* * Unwind any/all preceeding allocations in the event of an error. */ alloc_els_packet_failed: if (port->fp_fca_handle != NULL) { port->fp_fca_tran->fca_unbind_port(port->fp_fca_handle); port->fp_fca_handle = NULL; } if (port->fp_ub_tokens != NULL) { (void) fc_ulp_ubfree(port, port->fp_ub_count, port->fp_ub_tokens); kmem_free(port->fp_ub_tokens, port->fp_ub_count * sizeof (*port->fp_ub_tokens)); port->fp_ub_tokens = NULL; } if (port->fp_els_resp_pkt != NULL) { fp_free_pkt(port->fp_els_resp_pkt); port->fp_els_resp_pkt = NULL; } bind_callbacks_failed: if (port->fp_taskq != NULL) { taskq_destroy(port->fp_taskq); } if (port->fp_pwwn_table != NULL) { kmem_free(port->fp_pwwn_table, pwwn_table_size * sizeof (struct pwwn_hash)); port->fp_pwwn_table = NULL; } if (port->fp_did_table != NULL) { kmem_free(port->fp_did_table, did_table_size * sizeof (struct d_id_hash)); port->fp_did_table = NULL; } if (port->fp_pkt_cache != NULL) { kmem_cache_destroy(port->fp_pkt_cache); port->fp_pkt_cache = NULL; } cache_alloc_failed: cv_destroy(&port->fp_attach_cv); cv_destroy(&port->fp_cv); mutex_destroy(&port->fp_mutex); ddi_remove_minor_node(port->fp_port_dip, NULL); ddi_soft_state_free(fp_driver_softstate, instance); ddi_prop_remove_all(dip); return (DDI_FAILURE); } /* * Handle DDI_RESUME request */ static int fp_resume_handler(dev_info_t *dip) { int rval; fc_local_port_t *port; port = ddi_get_soft_state(fp_driver_softstate, ddi_get_instance(dip)); ASSERT(port != NULL); #ifdef DEBUG mutex_enter(&port->fp_mutex); ASSERT(port->fp_soft_state & FP_SOFT_SUSPEND); mutex_exit(&port->fp_mutex); #endif /* * If the port was power suspended, raise the power level */ mutex_enter(&port->fp_mutex); if ((port->fp_soft_state & FP_SOFT_POWER_DOWN) && (!(port->fp_soft_state & FP_SOFT_NO_PMCOMP))) { ASSERT(port->fp_pm_level == FP_PM_PORT_DOWN); mutex_exit(&port->fp_mutex); if (pm_raise_power(dip, FP_PM_COMPONENT, FP_PM_PORT_UP) != DDI_SUCCESS) { FP_TRACE(FP_NHEAD2(9, 0), "Failed to raise the power level"); return (DDI_FAILURE); } mutex_enter(&port->fp_mutex); } port->fp_soft_state &= ~FP_SOFT_SUSPEND; mutex_exit(&port->fp_mutex); /* * All the discovery is initiated and handled by per-port thread. * Further all the discovery is done in handled in callback mode * (not polled mode); In a specific case such as this, the discovery * is required to happen in polled mode. The easiest way out is * to bail out port thread and get started. Come back and fix this * to do on demand discovery initiated by ULPs. ULPs such as FCP * will do on-demand discovery during pre-power-up busctl handling * which will only be possible when SCSA provides a new HBA vector * for sending down the PM busctl requests. */ (void) callb_generic_cpr(&port->fp_cpr_info, CB_CODE_CPR_RESUME); rval = fp_resume_all(port, FC_CMD_RESUME); if (rval != DDI_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_soft_state |= FP_SOFT_SUSPEND; mutex_exit(&port->fp_mutex); (void) callb_generic_cpr(&port->fp_cpr_info, CB_CODE_CPR_CHKPT); } return (rval); } /* * Perform FC Port power on initialization */ static int fp_power_up(fc_local_port_t *port) { int rval; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT((port->fp_soft_state & FP_SOFT_SUSPEND) == 0); ASSERT(port->fp_soft_state & FP_SOFT_POWER_DOWN); port->fp_soft_state &= ~FP_SOFT_POWER_DOWN; mutex_exit(&port->fp_mutex); rval = fp_resume_all(port, FC_CMD_POWER_UP); if (rval != DDI_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_soft_state |= FP_SOFT_POWER_DOWN; } else { mutex_enter(&port->fp_mutex); } return (rval); } /* * It is important to note that the power may possibly be removed between * SUSPEND and the ensuing RESUME operation. In such a context the underlying * FC port hardware would have gone through an OFFLINE to ONLINE transition * (hardware state). In this case, the port driver may need to rediscover the * topology, perform LOGINs, register with the name server again and perform * any such port initialization procedures. To perform LOGINs, the driver could * use the port device handle to see if a LOGIN needs to be performed and use * the D_ID and WWN in it. The LOGINs may fail (if the hardware is reconfigured * or removed) which will be reflected in the map the ULPs will see. */ static int fp_resume_all(fc_local_port_t *port, fc_attach_cmd_t cmd) { ASSERT(!MUTEX_HELD(&port->fp_mutex)); if (fp_bind_callbacks(port) != DDI_SUCCESS) { return (DDI_FAILURE); } mutex_enter(&port->fp_mutex); /* * If there are commands queued for delayed retry, instead of * working the hard way to figure out which ones are good for * restart and which ones not (ELSs are definitely not good * as the port will have to go through a new spin of rediscovery * now), so just flush them out. */ if (port->fp_restore & FP_RESTORE_WAIT_TIMEOUT) { fp_cmd_t *cmd; port->fp_restore &= ~FP_RESTORE_WAIT_TIMEOUT; mutex_exit(&port->fp_mutex); while ((cmd = fp_deque_cmd(port)) != NULL) { cmd->cmd_pkt.pkt_state = FC_PKT_TRAN_ERROR; fp_iodone(cmd); } mutex_enter(&port->fp_mutex); } if (FC_PORT_STATE_MASK(port->fp_bind_state) == FC_STATE_OFFLINE) { if ((port->fp_restore & FP_RESTORE_OFFLINE_TIMEOUT) || port->fp_dev_count) { port->fp_restore &= ~FP_RESTORE_OFFLINE_TIMEOUT; port->fp_offline_tid = timeout(fp_offline_timeout, (caddr_t)port, fp_offline_ticks); } if (port->fp_job_head) { cv_signal(&port->fp_cv); } mutex_exit(&port->fp_mutex); fctl_attach_ulps(port, cmd, &modlinkage); } else { struct job_request *job; /* * If an OFFLINE timer was running at the time of * suspending, there is no need to restart it as * the port is ONLINE now. */ port->fp_restore &= ~FP_RESTORE_OFFLINE_TIMEOUT; if (port->fp_statec_busy == 0) { port->fp_soft_state |= FP_SOFT_IN_STATEC_CB; } port->fp_statec_busy++; mutex_exit(&port->fp_mutex); job = fctl_alloc_job(JOB_PORT_ONLINE, JOB_CANCEL_ULP_NOTIFICATION, NULL, NULL, KM_SLEEP); fctl_enque_job(port, job); fctl_jobwait(job); fctl_remove_oldies(port); fctl_attach_ulps(port, cmd, &modlinkage); fctl_dealloc_job(job); } return (DDI_SUCCESS); } /* * At this time, there shouldn't be any I/O requests on this port. * But the unsolicited callbacks from the underlying FCA port need * to be handled very carefully. The steps followed to handle the * DDI_DETACH are: * + Grab the port driver mutex, check if the unsolicited * callback is currently under processing. If true, fail * the DDI_DETACH request by printing a message; If false * mark the DDI_DETACH as under progress, so that any * further unsolicited callbacks get bounced. * + Perform PRLO/LOGO if necessary, cleanup all the data * structures. * + Get the job_handler thread to gracefully exit. * + Unregister callbacks with the FCA port. * + Now that some peace is found, notify all the ULPs of * DDI_DETACH request (using ulp_port_detach entry point) * + Free all mutexes, semaphores, conditional variables. * + Free the soft state, return success. * * Important considerations: * Port driver de-registers state change and unsolicited * callbacks before taking up the task of notifying ULPs * and performing PRLO and LOGOs. * * A port may go offline at the time PRLO/LOGO is being * requested. It is expected of all FCA drivers to fail * such requests either immediately with a FC_OFFLINE * return code to fc_fca_transport() or return the packet * asynchronously with pkt state set to FC_PKT_PORT_OFFLINE */ static int fp_detach_handler(fc_local_port_t *port) { job_request_t *job; uint32_t delay_count; fc_orphan_t *orp, *tmporp; /* * In a Fabric topology with many host ports connected to * a switch, another detaching instance of fp might have * triggered a LOGO (which is an unsolicited request to * this instance). So in order to be able to successfully * detach by taking care of such cases a delay of about * 30 seconds is introduced. */ delay_count = 0; mutex_enter(&port->fp_mutex); if (port->fp_out_fpcmds != 0) { /* * At this time we can only check fp internal commands, because * sd/ssd/scsi_vhci should have finsihed all their commands, * fcp/fcip/fcsm should have finished all their commands. * * It seems that all fp internal commands are asynchronous now. */ port->fp_soft_state &= ~FP_DETACH_INPROGRESS; mutex_exit(&port->fp_mutex); cmn_err(CE_WARN, "fp(%d): %d fp_cmd(s) is/are in progress" " Failing detach", port->fp_instance, port->fp_out_fpcmds); return (DDI_FAILURE); } while ((port->fp_soft_state & (FP_SOFT_IN_STATEC_CB | FP_SOFT_IN_UNSOL_CB)) && (delay_count < 30)) { mutex_exit(&port->fp_mutex); delay_count++; delay(drv_usectohz(1000000)); mutex_enter(&port->fp_mutex); } if (port->fp_soft_state & (FP_SOFT_IN_STATEC_CB | FP_SOFT_IN_UNSOL_CB)) { port->fp_soft_state &= ~FP_DETACH_INPROGRESS; mutex_exit(&port->fp_mutex); cmn_err(CE_WARN, "fp(%d): FCA callback in progress: " " Failing detach", port->fp_instance); return (DDI_FAILURE); } port->fp_soft_state |= FP_SOFT_IN_DETACH; port->fp_soft_state &= ~FP_DETACH_INPROGRESS; mutex_exit(&port->fp_mutex); /* * If we're powered down, we need to raise power prior to submitting * the JOB_PORT_SHUTDOWN job. Otherwise, the job handler will never * process the shutdown job. */ if (fctl_busy_port(port) != 0) { cmn_err(CE_WARN, "fp(%d): fctl_busy_port failed", port->fp_instance); mutex_enter(&port->fp_mutex); port->fp_soft_state &= ~FP_SOFT_IN_DETACH; mutex_exit(&port->fp_mutex); return (DDI_FAILURE); } /* * This will deallocate data structs and cause the "job" thread * to exit, in preparation for DDI_DETACH on the instance. * This can sleep for an arbitrary duration, since it waits for * commands over the wire, timeout(9F) callbacks, etc. * * CAUTION: There is still a race here, where the "job" thread * can still be executing code even tho the fctl_jobwait() call * below has returned to us. In theory the fp driver could even be * modunloaded even tho the job thread isn't done executing. * without creating the race condition. */ job = fctl_alloc_job(JOB_PORT_SHUTDOWN, 0, NULL, (opaque_t)port, KM_SLEEP); fctl_enque_job(port, job); fctl_jobwait(job); fctl_dealloc_job(job); (void) pm_lower_power(port->fp_port_dip, FP_PM_COMPONENT, FP_PM_PORT_DOWN); if (port->fp_taskq) { taskq_destroy(port->fp_taskq); } ddi_prop_remove_all(port->fp_port_dip); ddi_remove_minor_node(port->fp_port_dip, NULL); fctl_remove_port(port); fp_free_pkt(port->fp_els_resp_pkt); if (port->fp_ub_tokens) { if (fc_ulp_ubfree(port, port->fp_ub_count, port->fp_ub_tokens) != FC_SUCCESS) { cmn_err(CE_WARN, "fp(%d): couldn't free " " unsolicited buffers", port->fp_instance); } kmem_free(port->fp_ub_tokens, sizeof (*port->fp_ub_tokens) * port->fp_ub_count); port->fp_ub_tokens = NULL; } if (port->fp_pkt_cache != NULL) { kmem_cache_destroy(port->fp_pkt_cache); } port->fp_fca_tran->fca_unbind_port(port->fp_fca_handle); mutex_enter(&port->fp_mutex); if (port->fp_did_table) { kmem_free(port->fp_did_table, did_table_size * sizeof (struct d_id_hash)); } if (port->fp_pwwn_table) { kmem_free(port->fp_pwwn_table, pwwn_table_size * sizeof (struct pwwn_hash)); } orp = port->fp_orphan_list; while (orp) { tmporp = orp; orp = orp->orp_next; kmem_free(tmporp, sizeof (*orp)); } mutex_exit(&port->fp_mutex); fp_log_port_event(port, ESC_SUNFC_PORT_DETACH); mutex_destroy(&port->fp_mutex); cv_destroy(&port->fp_attach_cv); cv_destroy(&port->fp_cv); ddi_soft_state_free(fp_driver_softstate, port->fp_instance); return (DDI_SUCCESS); } /* * Steps to perform DDI_SUSPEND operation on a FC port * * - If already suspended return DDI_FAILURE * - If already power-suspended return DDI_SUCCESS * - If an unsolicited callback or state change handling is in * in progress, throw a warning message, return DDI_FAILURE * - Cancel timeouts * - SUSPEND the job_handler thread (means do nothing as it is * taken care of by the CPR frame work) */ static int fp_suspend_handler(fc_local_port_t *port) { uint32_t delay_count; mutex_enter(&port->fp_mutex); /* * The following should never happen, but * let the driver be more defensive here */ if (port->fp_soft_state & FP_SOFT_SUSPEND) { mutex_exit(&port->fp_mutex); return (DDI_FAILURE); } /* * If the port is already power suspended, there * is nothing else to do, So return DDI_SUCCESS, * but mark the SUSPEND bit in the soft state * before leaving. */ if (port->fp_soft_state & FP_SOFT_POWER_DOWN) { port->fp_soft_state |= FP_SOFT_SUSPEND; mutex_exit(&port->fp_mutex); return (DDI_SUCCESS); } /* * Check if an unsolicited callback or state change handling is * in progress. If true, fail the suspend operation; also throw * a warning message notifying the failure. Note that Sun PCI * hotplug spec recommends messages in cases of failure (but * not flooding the console) * * Busy waiting for a short interval (500 millisecond ?) to see * if the callback processing completes may be another idea. Since * most of the callback processing involves a lot of work, it * is safe to just fail the SUSPEND operation. It is definitely * not bad to fail the SUSPEND operation if the driver is busy. */ delay_count = 0; while ((port->fp_soft_state & (FP_SOFT_IN_STATEC_CB | FP_SOFT_IN_UNSOL_CB)) && (delay_count < 30)) { mutex_exit(&port->fp_mutex); delay_count++; delay(drv_usectohz(1000000)); mutex_enter(&port->fp_mutex); } if (port->fp_soft_state & (FP_SOFT_IN_STATEC_CB | FP_SOFT_IN_UNSOL_CB)) { mutex_exit(&port->fp_mutex); cmn_err(CE_WARN, "fp(%d): FCA callback in progress: " " Failing suspend", port->fp_instance); return (DDI_FAILURE); } /* * Check of FC port thread is busy */ if (port->fp_job_head) { mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, 0), "FC port thread is busy: Failing suspend"); return (DDI_FAILURE); } port->fp_soft_state |= FP_SOFT_SUSPEND; fp_suspend_all(port); mutex_exit(&port->fp_mutex); return (DDI_SUCCESS); } /* * Prepare for graceful power down of a FC port */ static int fp_power_down(fc_local_port_t *port) { ASSERT(MUTEX_HELD(&port->fp_mutex)); /* * Power down request followed by a DDI_SUSPEND should * never happen; If it does return DDI_SUCCESS */ if (port->fp_soft_state & FP_SOFT_SUSPEND) { port->fp_soft_state |= FP_SOFT_POWER_DOWN; return (DDI_SUCCESS); } /* * If the port is already power suspended, there * is nothing else to do, So return DDI_SUCCESS, */ if (port->fp_soft_state & FP_SOFT_POWER_DOWN) { return (DDI_SUCCESS); } /* * Check if an unsolicited callback or state change handling * is in progress. If true, fail the PM suspend operation. * But don't print a message unless the verbosity of the * driver desires otherwise. */ if ((port->fp_soft_state & FP_SOFT_IN_STATEC_CB) || (port->fp_soft_state & FP_SOFT_IN_UNSOL_CB)) { FP_TRACE(FP_NHEAD2(9, 0), "Unsolicited callback in progress: Failing power down"); return (DDI_FAILURE); } /* * Check of FC port thread is busy */ if (port->fp_job_head) { FP_TRACE(FP_NHEAD2(9, 0), "FC port thread is busy: Failing power down"); return (DDI_FAILURE); } port->fp_soft_state |= FP_SOFT_POWER_DOWN; /* * check if the ULPs are ready for power down */ mutex_exit(&port->fp_mutex); if (fctl_detach_ulps(port, FC_CMD_POWER_DOWN, &modlinkage) != FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_soft_state &= ~FP_SOFT_POWER_DOWN; mutex_exit(&port->fp_mutex); /* * Power back up the obedient ULPs that went down */ fp_attach_ulps(port, FC_CMD_POWER_UP); FP_TRACE(FP_NHEAD2(9, 0), "ULP(s) busy, detach_ulps failed. Failing power down"); mutex_enter(&port->fp_mutex); return (DDI_FAILURE); } mutex_enter(&port->fp_mutex); fp_suspend_all(port); return (DDI_SUCCESS); } /* * Suspend the entire FC port */ static void fp_suspend_all(fc_local_port_t *port) { int index; struct pwwn_hash *head; fc_remote_port_t *pd; ASSERT(MUTEX_HELD(&port->fp_mutex)); if (port->fp_wait_tid != 0) { timeout_id_t tid; tid = port->fp_wait_tid; port->fp_wait_tid = (timeout_id_t)NULL; mutex_exit(&port->fp_mutex); (void) untimeout(tid); mutex_enter(&port->fp_mutex); port->fp_restore |= FP_RESTORE_WAIT_TIMEOUT; } if (port->fp_offline_tid) { timeout_id_t tid; tid = port->fp_offline_tid; port->fp_offline_tid = (timeout_id_t)NULL; mutex_exit(&port->fp_mutex); (void) untimeout(tid); mutex_enter(&port->fp_mutex); port->fp_restore |= FP_RESTORE_OFFLINE_TIMEOUT; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_unbind_port(port->fp_fca_handle); mutex_enter(&port->fp_mutex); /* * Mark all devices as OLD, and reset the LOGIN state as well * (this will force the ULPs to perform a LOGIN after calling * fc_portgetmap() during RESUME/PM_RESUME) */ for (index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); fp_remote_port_offline(pd); fctl_delist_did_table(port, pd); pd->pd_state = PORT_DEVICE_VALID; pd->pd_login_count = 0; mutex_exit(&pd->pd_mutex); pd = pd->pd_wwn_hnext; } } } /* * fp_cache_constructor: Constructor function for kmem_cache_create(9F). * Performs intializations for fc_packet_t structs. * Returns 0 for success or -1 for failure. * * This function allocates DMA handles for both command and responses. * Most of the ELSs used have both command and responses so it is strongly * desired to move them to cache constructor routine. * * Context: Can sleep iff called with KM_SLEEP flag. */ static int fp_cache_constructor(void *buf, void *cdarg, int kmflags) { int (*cb) (caddr_t); fc_packet_t *pkt; fp_cmd_t *cmd = (fp_cmd_t *)buf; fc_local_port_t *port = (fc_local_port_t *)cdarg; cb = (kmflags == KM_SLEEP) ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT; cmd->cmd_next = NULL; cmd->cmd_flags = 0; cmd->cmd_dflags = 0; cmd->cmd_job = NULL; cmd->cmd_port = port; pkt = &cmd->cmd_pkt; if (!(port->fp_soft_state & FP_SOFT_FCA_IS_NODMA)) { if (ddi_dma_alloc_handle(port->fp_fca_dip, port->fp_fca_tran->fca_dma_attr, cb, NULL, &pkt->pkt_cmd_dma) != DDI_SUCCESS) { return (-1); } if (ddi_dma_alloc_handle(port->fp_fca_dip, port->fp_fca_tran->fca_dma_attr, cb, NULL, &pkt->pkt_resp_dma) != DDI_SUCCESS) { ddi_dma_free_handle(&pkt->pkt_cmd_dma); return (-1); } } else { pkt->pkt_cmd_dma = 0; pkt->pkt_resp_dma = 0; } pkt->pkt_cmd_acc = pkt->pkt_resp_acc = NULL; pkt->pkt_cmd_cookie_cnt = pkt->pkt_resp_cookie_cnt = pkt->pkt_data_cookie_cnt = 0; pkt->pkt_cmd_cookie = pkt->pkt_resp_cookie = pkt->pkt_data_cookie = NULL; pkt->pkt_fca_private = (caddr_t)buf + sizeof (fp_cmd_t); return (0); } /* * fp_cache_destructor: Destructor function for kmem_cache_create(). * Performs un-intializations for fc_packet_t structs. */ /* ARGSUSED */ static void fp_cache_destructor(void *buf, void *cdarg) { fp_cmd_t *cmd = (fp_cmd_t *)buf; fc_packet_t *pkt; pkt = &cmd->cmd_pkt; if (pkt->pkt_cmd_dma) { ddi_dma_free_handle(&pkt->pkt_cmd_dma); } if (pkt->pkt_resp_dma) { ddi_dma_free_handle(&pkt->pkt_resp_dma); } } /* * Packet allocation for ELS and any other port driver commands * * Some ELSs like FLOGI and PLOGI are critical for topology and * device discovery and a system's inability to allocate memory * or DVMA resources while performing some of these critical ELSs * cause a lot of problem. While memory allocation failures are * rare, DVMA resource failures are common as the applications * are becoming more and more powerful on huge servers. So it * is desirable to have a framework support to reserve a fragment * of DVMA. So until this is fixed the correct way, the suffering * is huge whenever a LIP happens at a time DVMA resources are * drained out completely - So an attempt needs to be made to * KM_SLEEP while requesting for these resources, hoping that * the requests won't hang forever. * * The fc_remote_port_t argument is stored into the pkt_pd field in the * fc_packet_t struct prior to the fc_ulp_init_packet() call. This * ensures that the pd_ref_count for the fc_remote_port_t is valid. * If there is no fc_remote_port_t associated with the fc_packet_t, then * fp_alloc_pkt() must be called with pd set to NULL. * * fp/fctl will resue fp_cmd_t somewhere, and change pkt_cmdlen/rsplen, * actually, it's a design fault. But there's no problem for physical * FCAs. But it will cause memory leak or panic for virtual FCAs like fcoei. * * For FCAs that don't support DMA, such as fcoei, we will use * pkt_fctl_rsvd1/rsvd2 to keep the real cmd_len/resp_len. */ static fp_cmd_t * fp_alloc_pkt(fc_local_port_t *port, int cmd_len, int resp_len, int kmflags, fc_remote_port_t *pd) { int rval; ulong_t real_len; fp_cmd_t *cmd; fc_packet_t *pkt; int (*cb) (caddr_t); ddi_dma_cookie_t pkt_cookie; ddi_dma_cookie_t *cp; uint32_t cnt; ASSERT(!MUTEX_HELD(&port->fp_mutex)); cb = (kmflags == KM_SLEEP) ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT; cmd = (fp_cmd_t *)kmem_cache_alloc(port->fp_pkt_cache, kmflags); if (cmd == NULL) { return (cmd); } cmd->cmd_ulp_pkt = NULL; cmd->cmd_flags = 0; pkt = &cmd->cmd_pkt; ASSERT(cmd->cmd_dflags == 0); pkt->pkt_datalen = 0; pkt->pkt_data = NULL; pkt->pkt_state = 0; pkt->pkt_action = 0; pkt->pkt_reason = 0; pkt->pkt_expln = 0; pkt->pkt_cmd = NULL; pkt->pkt_resp = NULL; pkt->pkt_fctl_rsvd1 = NULL; pkt->pkt_fctl_rsvd2 = NULL; /* * Init pkt_pd with the given pointer; this must be done _before_ * the call to fc_ulp_init_packet(). */ pkt->pkt_pd = pd; /* Now call the FCA driver to init its private, per-packet fields */ if (fc_ulp_init_packet((opaque_t)port, pkt, kmflags) != FC_SUCCESS) { goto alloc_pkt_failed; } if (cmd_len && !(port->fp_soft_state & FP_SOFT_FCA_IS_NODMA)) { ASSERT(pkt->pkt_cmd_dma != NULL); rval = ddi_dma_mem_alloc(pkt->pkt_cmd_dma, cmd_len, port->fp_fca_tran->fca_acc_attr, DDI_DMA_CONSISTENT, cb, NULL, (caddr_t *)&pkt->pkt_cmd, &real_len, &pkt->pkt_cmd_acc); if (rval != DDI_SUCCESS) { goto alloc_pkt_failed; } cmd->cmd_dflags |= FP_CMD_VALID_DMA_MEM; if (real_len < cmd_len) { goto alloc_pkt_failed; } rval = ddi_dma_addr_bind_handle(pkt->pkt_cmd_dma, NULL, pkt->pkt_cmd, real_len, DDI_DMA_WRITE | DDI_DMA_CONSISTENT, cb, NULL, &pkt_cookie, &pkt->pkt_cmd_cookie_cnt); if (rval != DDI_DMA_MAPPED) { goto alloc_pkt_failed; } cmd->cmd_dflags |= FP_CMD_VALID_DMA_BIND; if (pkt->pkt_cmd_cookie_cnt > port->fp_fca_tran->fca_dma_attr->dma_attr_sgllen) { goto alloc_pkt_failed; } ASSERT(pkt->pkt_cmd_cookie_cnt != 0); cp = pkt->pkt_cmd_cookie = (ddi_dma_cookie_t *)kmem_alloc( pkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie), KM_NOSLEEP); if (cp == NULL) { goto alloc_pkt_failed; } *cp = pkt_cookie; cp++; for (cnt = 1; cnt < pkt->pkt_cmd_cookie_cnt; cnt++, cp++) { ddi_dma_nextcookie(pkt->pkt_cmd_dma, &pkt_cookie); *cp = pkt_cookie; } } else if (cmd_len != 0) { pkt->pkt_cmd = kmem_alloc(cmd_len, KM_SLEEP); pkt->pkt_fctl_rsvd1 = (opaque_t)(uintptr_t)cmd_len; } if (resp_len && !(port->fp_soft_state & FP_SOFT_FCA_IS_NODMA)) { ASSERT(pkt->pkt_resp_dma != NULL); rval = ddi_dma_mem_alloc(pkt->pkt_resp_dma, resp_len, port->fp_fca_tran->fca_acc_attr, DDI_DMA_CONSISTENT, cb, NULL, (caddr_t *)&pkt->pkt_resp, &real_len, &pkt->pkt_resp_acc); if (rval != DDI_SUCCESS) { goto alloc_pkt_failed; } cmd->cmd_dflags |= FP_RESP_VALID_DMA_MEM; if (real_len < resp_len) { goto alloc_pkt_failed; } rval = ddi_dma_addr_bind_handle(pkt->pkt_resp_dma, NULL, pkt->pkt_resp, real_len, DDI_DMA_READ | DDI_DMA_CONSISTENT, cb, NULL, &pkt_cookie, &pkt->pkt_resp_cookie_cnt); if (rval != DDI_DMA_MAPPED) { goto alloc_pkt_failed; } cmd->cmd_dflags |= FP_RESP_VALID_DMA_BIND; if (pkt->pkt_resp_cookie_cnt > port->fp_fca_tran->fca_dma_attr->dma_attr_sgllen) { goto alloc_pkt_failed; } ASSERT(pkt->pkt_cmd_cookie_cnt != 0); cp = pkt->pkt_resp_cookie = (ddi_dma_cookie_t *)kmem_alloc( pkt->pkt_resp_cookie_cnt * sizeof (pkt_cookie), KM_NOSLEEP); if (cp == NULL) { goto alloc_pkt_failed; } *cp = pkt_cookie; cp++; for (cnt = 1; cnt < pkt->pkt_resp_cookie_cnt; cnt++, cp++) { ddi_dma_nextcookie(pkt->pkt_resp_dma, &pkt_cookie); *cp = pkt_cookie; } } else if (resp_len != 0) { pkt->pkt_resp = kmem_alloc(resp_len, KM_SLEEP); pkt->pkt_fctl_rsvd2 = (opaque_t)(uintptr_t)resp_len; } pkt->pkt_cmdlen = cmd_len; pkt->pkt_rsplen = resp_len; pkt->pkt_ulp_private = cmd; return (cmd); alloc_pkt_failed: fp_free_dma(cmd); if (pkt->pkt_cmd_cookie != NULL) { kmem_free(pkt->pkt_cmd_cookie, pkt->pkt_cmd_cookie_cnt * sizeof (ddi_dma_cookie_t)); pkt->pkt_cmd_cookie = NULL; } if (pkt->pkt_resp_cookie != NULL) { kmem_free(pkt->pkt_resp_cookie, pkt->pkt_resp_cookie_cnt * sizeof (ddi_dma_cookie_t)); pkt->pkt_resp_cookie = NULL; } if (port->fp_soft_state & FP_SOFT_FCA_IS_NODMA) { if (pkt->pkt_cmd) { kmem_free(pkt->pkt_cmd, cmd_len); } if (pkt->pkt_resp) { kmem_free(pkt->pkt_resp, resp_len); } } kmem_cache_free(port->fp_pkt_cache, cmd); return (NULL); } /* * Free FC packet */ static void fp_free_pkt(fp_cmd_t *cmd) { fc_local_port_t *port; fc_packet_t *pkt; ASSERT(!MUTEX_HELD(&cmd->cmd_port->fp_mutex)); cmd->cmd_next = NULL; cmd->cmd_job = NULL; pkt = &cmd->cmd_pkt; pkt->pkt_ulp_private = 0; pkt->pkt_tran_flags = 0; pkt->pkt_tran_type = 0; port = cmd->cmd_port; if (pkt->pkt_cmd_cookie != NULL) { kmem_free(pkt->pkt_cmd_cookie, pkt->pkt_cmd_cookie_cnt * sizeof (ddi_dma_cookie_t)); pkt->pkt_cmd_cookie = NULL; } if (pkt->pkt_resp_cookie != NULL) { kmem_free(pkt->pkt_resp_cookie, pkt->pkt_resp_cookie_cnt * sizeof (ddi_dma_cookie_t)); pkt->pkt_resp_cookie = NULL; } if (port->fp_soft_state & FP_SOFT_FCA_IS_NODMA) { if (pkt->pkt_cmd) { kmem_free(pkt->pkt_cmd, (uint32_t)(uintptr_t)pkt->pkt_fctl_rsvd1); } if (pkt->pkt_resp) { kmem_free(pkt->pkt_resp, (uint32_t)(uintptr_t)pkt->pkt_fctl_rsvd2); } } fp_free_dma(cmd); (void) fc_ulp_uninit_packet((opaque_t)port, pkt); kmem_cache_free(port->fp_pkt_cache, (void *)cmd); } /* * Release DVMA resources */ static void fp_free_dma(fp_cmd_t *cmd) { fc_packet_t *pkt = &cmd->cmd_pkt; pkt->pkt_cmdlen = 0; pkt->pkt_rsplen = 0; pkt->pkt_tran_type = 0; pkt->pkt_tran_flags = 0; if (cmd->cmd_dflags & FP_CMD_VALID_DMA_BIND) { (void) ddi_dma_unbind_handle(pkt->pkt_cmd_dma); } if (cmd->cmd_dflags & FP_CMD_VALID_DMA_MEM) { if (pkt->pkt_cmd_acc) { ddi_dma_mem_free(&pkt->pkt_cmd_acc); } } if (cmd->cmd_dflags & FP_RESP_VALID_DMA_BIND) { (void) ddi_dma_unbind_handle(pkt->pkt_resp_dma); } if (cmd->cmd_dflags & FP_RESP_VALID_DMA_MEM) { if (pkt->pkt_resp_acc) { ddi_dma_mem_free(&pkt->pkt_resp_acc); } } cmd->cmd_dflags = 0; } /* * Dedicated thread to perform various activities. One thread for * each fc_local_port_t (driver soft state) instance. * Note, this effectively works out to one thread for each local * port, but there are also some Solaris taskq threads in use on a per-local * port basis; these also need to be taken into consideration. */ static void fp_job_handler(fc_local_port_t *port) { int rval; uint32_t *d_id; fc_remote_port_t *pd; job_request_t *job; #ifndef __lock_lint /* * Solaris-internal stuff for proper operation of kernel threads * with Solaris CPR. */ CALLB_CPR_INIT(&port->fp_cpr_info, &port->fp_mutex, callb_generic_cpr, "fp_job_handler"); #endif /* Loop forever waiting for work to do */ for (;;) { mutex_enter(&port->fp_mutex); /* * Sleep if no work to do right now, or if we want * to suspend or power-down. */ while (port->fp_job_head == NULL || (port->fp_soft_state & (FP_SOFT_POWER_DOWN | FP_SOFT_SUSPEND))) { CALLB_CPR_SAFE_BEGIN(&port->fp_cpr_info); cv_wait(&port->fp_cv, &port->fp_mutex); CALLB_CPR_SAFE_END(&port->fp_cpr_info, &port->fp_mutex); } /* * OK, we've just been woken up, so retrieve the next entry * from the head of the job queue for this local port. */ job = fctl_deque_job(port); /* * Handle all the fp driver's supported job codes here * in this big honkin' switch. */ switch (job->job_code) { case JOB_PORT_SHUTDOWN: /* * fp_port_shutdown() is only called from here. This * will prepare the local port instance (softstate) * for detaching. This cancels timeout callbacks, * executes LOGOs with remote ports, cleans up tables, * and deallocates data structs. */ fp_port_shutdown(port, job); /* * This will exit the job thread. */ #ifndef __lock_lint CALLB_CPR_EXIT(&(port->fp_cpr_info)); #else mutex_exit(&port->fp_mutex); #endif fctl_jobdone(job); thread_exit(); /* NOTREACHED */ case JOB_ATTACH_ULP: { /* * This job is spawned in response to a ULP calling * fc_ulp_add(). */ boolean_t do_attach_ulps = B_TRUE; /* * If fp is detaching, we don't want to call * fp_startup_done as this asynchronous * notification may interfere with the re-attach. */ if (port->fp_soft_state & (FP_DETACH_INPROGRESS | FP_SOFT_IN_DETACH | FP_DETACH_FAILED)) { do_attach_ulps = B_FALSE; } else { /* * We are going to force the transport * to attach to the ULPs, so set * fp_ulp_attach. This will keep any * potential detach from occurring until * we are done. */ port->fp_ulp_attach = 1; } mutex_exit(&port->fp_mutex); /* * NOTE: Since we just dropped the mutex, there is now * a race window where the fp_soft_state check above * could change here. This race is covered because an * additional check was added in the functions hidden * under fp_startup_done(). */ if (do_attach_ulps == B_TRUE) { /* * This goes thru a bit of a convoluted call * chain before spawning off a DDI taskq * request to perform the actual attach * operations. Blocking can occur at a number * of points. */ fp_startup_done((opaque_t)port, FC_PKT_SUCCESS); } job->job_result = FC_SUCCESS; fctl_jobdone(job); break; } case JOB_ULP_NOTIFY: { /* * Pass state change notifications up to any/all * registered ULPs. */ uint32_t statec; statec = job->job_ulp_listlen; if (statec == FC_STATE_RESET_REQUESTED) { port->fp_last_task = port->fp_task; port->fp_task = FP_TASK_OFFLINE; fp_port_offline(port, 0); port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; } if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); job->job_result = fp_ulp_notify(port, statec, KM_SLEEP); fctl_jobdone(job); break; } case JOB_PLOGI_ONE: /* * Issue a PLOGI to a single remote port. Multiple * PLOGIs to different remote ports may occur in * parallel. * This can create the fc_remote_port_t if it does not * already exist. */ mutex_exit(&port->fp_mutex); d_id = (uint32_t *)job->job_private; pd = fctl_get_remote_port_by_did(port, *d_id); if (pd) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { pd->pd_login_count++; mutex_exit(&pd->pd_mutex); job->job_result = FC_SUCCESS; fctl_jobdone(job); break; } mutex_exit(&pd->pd_mutex); } else { mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); pd = fp_create_remote_port_by_ns(port, *d_id, KM_SLEEP); if (pd == NULL) { job->job_result = FC_FAILURE; fctl_jobdone(job); break; } } else { mutex_exit(&port->fp_mutex); } } job->job_flags |= JOB_TYPE_FP_ASYNC; job->job_counter = 1; rval = fp_port_login(port, *d_id, job, FP_CMD_PLOGI_RETAIN, KM_SLEEP, pd, NULL); if (rval != FC_SUCCESS) { job->job_result = rval; fctl_jobdone(job); } break; case JOB_LOGO_ONE: { /* * Issue a PLOGO to a single remote port. Multiple * PLOGOs to different remote ports may occur in * parallel. */ fc_remote_port_t *pd; #ifndef __lock_lint ASSERT(job->job_counter > 0); #endif pd = (fc_remote_port_t *)job->job_ulp_pkts; mutex_enter(&pd->pd_mutex); if (pd->pd_state != PORT_DEVICE_LOGGED_IN) { mutex_exit(&pd->pd_mutex); job->job_result = FC_LOGINREQ; mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } if (pd->pd_login_count > 1) { pd->pd_login_count--; mutex_exit(&pd->pd_mutex); job->job_result = FC_SUCCESS; mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); job->job_flags |= JOB_TYPE_FP_ASYNC; (void) fp_logout(port, pd, job); break; } case JOB_FCIO_LOGIN: /* * PLOGI initiated at ioctl request. */ mutex_exit(&port->fp_mutex); job->job_result = fp_fcio_login(port, job->job_private, job); fctl_jobdone(job); break; case JOB_FCIO_LOGOUT: /* * PLOGO initiated at ioctl request. */ mutex_exit(&port->fp_mutex); job->job_result = fp_fcio_logout(port, job->job_private, job); fctl_jobdone(job); break; case JOB_PORT_GETMAP: case JOB_PORT_GETMAP_PLOGI_ALL: { port->fp_last_task = port->fp_task; port->fp_task = FP_TASK_GETMAP; switch (port->fp_topology) { case FC_TOP_PRIVATE_LOOP: job->job_counter = 1; fp_get_loopmap(port, job); mutex_exit(&port->fp_mutex); fp_jobwait(job); fctl_fillout_map(port, (fc_portmap_t **)job->job_private, (uint32_t *)job->job_arg, 1, 0, 0); fctl_jobdone(job); mutex_enter(&port->fp_mutex); break; case FC_TOP_PUBLIC_LOOP: case FC_TOP_FABRIC: mutex_exit(&port->fp_mutex); job->job_counter = 1; job->job_result = fp_ns_getmap(port, job, (fc_portmap_t **)job->job_private, (uint32_t *)job->job_arg, FCTL_GAN_START_ID); fctl_jobdone(job); mutex_enter(&port->fp_mutex); break; case FC_TOP_PT_PT: mutex_exit(&port->fp_mutex); fctl_fillout_map(port, (fc_portmap_t **)job->job_private, (uint32_t *)job->job_arg, 1, 0, 0); fctl_jobdone(job); mutex_enter(&port->fp_mutex); break; default: mutex_exit(&port->fp_mutex); fctl_jobdone(job); mutex_enter(&port->fp_mutex); break; } port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; mutex_exit(&port->fp_mutex); break; } case JOB_PORT_OFFLINE: { fp_log_port_event(port, ESC_SUNFC_PORT_OFFLINE); port->fp_last_task = port->fp_task; port->fp_task = FP_TASK_OFFLINE; if (port->fp_statec_busy > 2) { job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; fp_port_offline(port, 0); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } } else { fp_port_offline(port, 1); } port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } case JOB_PORT_STARTUP: { if ((rval = fp_port_startup(port, job)) != FC_SUCCESS) { if (port->fp_statec_busy > 1) { mutex_exit(&port->fp_mutex); break; } mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, rval), "Topology discovery failed"); break; } /* * Attempt building device handles in case * of private Loop. */ if (port->fp_topology == FC_TOP_PRIVATE_LOOP) { job->job_counter = 1; fp_get_loopmap(port, job); mutex_exit(&port->fp_mutex); fp_jobwait(job); mutex_enter(&port->fp_mutex); if (port->fp_lilp_map.lilp_magic < MAGIC_LIRP) { ASSERT(port->fp_total_devices == 0); port->fp_total_devices = port->fp_dev_count; } } else if (FC_IS_TOP_SWITCH(port->fp_topology)) { /* * Hack to avoid state changes going up early */ port->fp_statec_busy++; port->fp_soft_state |= FP_SOFT_IN_STATEC_CB; job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; fp_fabric_online(port, job); job->job_flags &= ~JOB_CANCEL_ULP_NOTIFICATION; } mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } case JOB_PORT_ONLINE: { char *newtop; char *oldtop; uint32_t old_top; fp_log_port_event(port, ESC_SUNFC_PORT_ONLINE); /* * Bail out early if there are a lot of * state changes in the pipeline */ if (port->fp_statec_busy > 1) { --port->fp_statec_busy; mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } switch (old_top = port->fp_topology) { case FC_TOP_PRIVATE_LOOP: oldtop = "Private Loop"; break; case FC_TOP_PUBLIC_LOOP: oldtop = "Public Loop"; break; case FC_TOP_PT_PT: oldtop = "Point to Point"; break; case FC_TOP_FABRIC: oldtop = "Fabric"; break; default: oldtop = NULL; break; } port->fp_last_task = port->fp_task; port->fp_task = FP_TASK_ONLINE; if ((rval = fp_port_startup(port, job)) != FC_SUCCESS) { port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; if (port->fp_statec_busy > 1) { --port->fp_statec_busy; mutex_exit(&port->fp_mutex); break; } port->fp_state = FC_STATE_OFFLINE; FP_TRACE(FP_NHEAD2(9, rval), "Topology discovery failed"); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } if (port->fp_offline_tid == NULL) { port->fp_offline_tid = timeout(fp_offline_timeout, (caddr_t)port, fp_offline_ticks); } mutex_exit(&port->fp_mutex); break; } switch (port->fp_topology) { case FC_TOP_PRIVATE_LOOP: newtop = "Private Loop"; break; case FC_TOP_PUBLIC_LOOP: newtop = "Public Loop"; break; case FC_TOP_PT_PT: newtop = "Point to Point"; break; case FC_TOP_FABRIC: newtop = "Fabric"; break; default: newtop = NULL; break; } if (oldtop && newtop && strcmp(oldtop, newtop)) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "Change in FC Topology old = %s new = %s", oldtop, newtop); } switch (port->fp_topology) { case FC_TOP_PRIVATE_LOOP: { int orphan = (old_top == FC_TOP_FABRIC || old_top == FC_TOP_PUBLIC_LOOP) ? 1 : 0; mutex_exit(&port->fp_mutex); fp_loop_online(port, job, orphan); break; } case FC_TOP_PUBLIC_LOOP: /* FALLTHROUGH */ case FC_TOP_FABRIC: fp_fabric_online(port, job); mutex_exit(&port->fp_mutex); break; case FC_TOP_PT_PT: fp_p2p_online(port, job); mutex_exit(&port->fp_mutex); break; default: if (--port->fp_statec_busy != 0) { /* * Watch curiously at what the next * state transition can do. */ mutex_exit(&port->fp_mutex); break; } FP_TRACE(FP_NHEAD2(9, 0), "Topology Unknown, Offlining the port.."); port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; port->fp_state = FC_STATE_OFFLINE; if (port->fp_offline_tid == NULL) { port->fp_offline_tid = timeout(fp_offline_timeout, (caddr_t)port, fp_offline_ticks); } mutex_exit(&port->fp_mutex); break; } mutex_enter(&port->fp_mutex); port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; mutex_exit(&port->fp_mutex); fctl_jobdone(job); break; } case JOB_PLOGI_GROUP: { mutex_exit(&port->fp_mutex); fp_plogi_group(port, job); break; } case JOB_UNSOL_REQUEST: { mutex_exit(&port->fp_mutex); fp_handle_unsol_buf(port, (fc_unsol_buf_t *)job->job_private, job); fctl_dealloc_job(job); break; } case JOB_NS_CMD: { fctl_ns_req_t *ns_cmd; mutex_exit(&port->fp_mutex); job->job_flags |= JOB_TYPE_FP_ASYNC; ns_cmd = (fctl_ns_req_t *)job->job_private; if (ns_cmd->ns_cmd_code < NS_GA_NXT || ns_cmd->ns_cmd_code > NS_DA_ID) { job->job_result = FC_BADCMD; fctl_jobdone(job); break; } if (FC_IS_CMD_A_REG(ns_cmd->ns_cmd_code)) { if (ns_cmd->ns_pd != NULL) { job->job_result = FC_BADOBJECT; fctl_jobdone(job); break; } job->job_counter = 1; rval = fp_ns_reg(port, ns_cmd->ns_pd, ns_cmd->ns_cmd_code, job, 0, KM_SLEEP); if (rval != FC_SUCCESS) { job->job_result = rval; fctl_jobdone(job); } break; } job->job_result = FC_SUCCESS; job->job_counter = 1; rval = fp_ns_query(port, ns_cmd, job, 0, KM_SLEEP); if (rval != FC_SUCCESS) { fctl_jobdone(job); } break; } case JOB_LINK_RESET: { la_wwn_t *pwwn; uint32_t topology; pwwn = (la_wwn_t *)job->job_private; ASSERT(pwwn != NULL); topology = port->fp_topology; mutex_exit(&port->fp_mutex); if (fctl_is_wwn_zero(pwwn) == FC_SUCCESS || topology == FC_TOP_PRIVATE_LOOP) { job->job_flags |= JOB_TYPE_FP_ASYNC; rval = port->fp_fca_tran->fca_reset( port->fp_fca_handle, FC_FCA_LINK_RESET); job->job_result = rval; fp_jobdone(job); } else { ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); if (FC_IS_TOP_SWITCH(topology)) { rval = fp_remote_lip(port, pwwn, KM_SLEEP, job); } else { rval = FC_FAILURE; } if (rval != FC_SUCCESS) { job->job_result = rval; } fctl_jobdone(job); } break; } default: mutex_exit(&port->fp_mutex); job->job_result = FC_BADCMD; fctl_jobdone(job); break; } } /* NOTREACHED */ } /* * Perform FC port bring up initialization */ static int fp_port_startup(fc_local_port_t *port, job_request_t *job) { int rval; uint32_t state; uint32_t src_id; fc_lilpmap_t *lilp_map; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); FP_DTRACE(FP_NHEAD1(2, 0), "Entering fp_port_startup;" " port=%p, job=%p", port, job); port->fp_topology = FC_TOP_UNKNOWN; port->fp_port_id.port_id = 0; state = FC_PORT_STATE_MASK(port->fp_state); if (state == FC_STATE_OFFLINE) { port->fp_port_type.port_type = FC_NS_PORT_UNKNOWN; job->job_result = FC_OFFLINE; mutex_exit(&port->fp_mutex); fctl_jobdone(job); mutex_enter(&port->fp_mutex); return (FC_OFFLINE); } if (state == FC_STATE_LOOP) { port->fp_port_type.port_type = FC_NS_PORT_NL; mutex_exit(&port->fp_mutex); lilp_map = &port->fp_lilp_map; if ((rval = fp_get_lilpmap(port, lilp_map)) != FC_SUCCESS) { job->job_result = FC_FAILURE; fctl_jobdone(job); FP_TRACE(FP_NHEAD1(9, rval), "LILP map Invalid or not present"); mutex_enter(&port->fp_mutex); return (FC_FAILURE); } if (lilp_map->lilp_length == 0) { job->job_result = FC_NO_MAP; fctl_jobdone(job); fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "LILP map length zero"); mutex_enter(&port->fp_mutex); return (FC_NO_MAP); } src_id = lilp_map->lilp_myalpa & 0xFF; } else { fc_remote_port_t *pd; fc_fca_pm_t pm; fc_fca_p2p_info_t p2p_info; int pd_recepient; /* * Get P2P remote port info if possible */ bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_P2P_INFO; pm.pm_data_len = sizeof (fc_fca_p2p_info_t); pm.pm_data_buf = (caddr_t)&p2p_info; rval = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (rval == FC_SUCCESS) { port->fp_port_id.port_id = p2p_info.fca_d_id; port->fp_port_type.port_type = FC_NS_PORT_N; port->fp_topology = FC_TOP_PT_PT; port->fp_total_devices = 1; pd_recepient = fctl_wwn_cmp( &port->fp_service_params.nport_ww_name, &p2p_info.pwwn) < 0 ? PD_PLOGI_RECEPIENT : PD_PLOGI_INITIATOR; mutex_exit(&port->fp_mutex); pd = fctl_create_remote_port(port, &p2p_info.nwwn, &p2p_info.pwwn, p2p_info.d_id, pd_recepient, KM_NOSLEEP); FP_DTRACE(FP_NHEAD1(2, 0), "Exiting fp_port_startup;" " P2P port=%p pd=%p fp %x pd %x", port, pd, port->fp_port_id.port_id, p2p_info.d_id); mutex_enter(&port->fp_mutex); return (FC_SUCCESS); } port->fp_port_type.port_type = FC_NS_PORT_N; mutex_exit(&port->fp_mutex); src_id = 0; } job->job_counter = 1; job->job_result = FC_SUCCESS; if ((rval = fp_fabric_login(port, src_id, job, FP_CMD_PLOGI_DONT_CARE, KM_SLEEP)) != FC_SUCCESS) { port->fp_port_type.port_type = FC_NS_PORT_UNKNOWN; job->job_result = FC_FAILURE; fctl_jobdone(job); mutex_enter(&port->fp_mutex); if (port->fp_statec_busy <= 1) { mutex_exit(&port->fp_mutex); fp_printf(port, CE_NOTE, FP_LOG_ONLY, rval, NULL, "Couldn't transport FLOGI"); mutex_enter(&port->fp_mutex); } return (FC_FAILURE); } fp_jobwait(job); mutex_enter(&port->fp_mutex); if (job->job_result == FC_SUCCESS) { if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); fp_ns_init(port, job, KM_SLEEP); mutex_enter(&port->fp_mutex); } } else { if (state == FC_STATE_LOOP) { port->fp_topology = FC_TOP_PRIVATE_LOOP; port->fp_port_id.port_id = port->fp_lilp_map.lilp_myalpa & 0xFF; } } FP_DTRACE(FP_NHEAD1(2, 0), "Exiting fp_port_startup; port=%p, job=%p", port, job); return (FC_SUCCESS); } /* * Perform ULP invocations following FC port startup */ /* ARGSUSED */ static void fp_startup_done(opaque_t arg, uchar_t result) { fc_local_port_t *port = arg; fp_attach_ulps(port, FC_CMD_ATTACH); FP_DTRACE(FP_NHEAD1(2, 0), "fp_startup almost complete; port=%p", port); } /* * Perform ULP port attach */ static void fp_ulp_port_attach(void *arg) { fp_soft_attach_t *att = (fp_soft_attach_t *)arg; fc_local_port_t *port = att->att_port; FP_DTRACE(FP_NHEAD1(1, 0), "port attach of" " ULPs begin; port=%p, cmd=%x", port, att->att_cmd); fctl_attach_ulps(att->att_port, att->att_cmd, &modlinkage); if (att->att_need_pm_idle == B_TRUE) { fctl_idle_port(port); } FP_DTRACE(FP_NHEAD1(1, 0), "port attach of" " ULPs end; port=%p, cmd=%x", port, att->att_cmd); mutex_enter(&att->att_port->fp_mutex); att->att_port->fp_ulp_attach = 0; port->fp_task = port->fp_last_task; port->fp_last_task = FP_TASK_IDLE; cv_signal(&att->att_port->fp_attach_cv); mutex_exit(&att->att_port->fp_mutex); kmem_free(att, sizeof (fp_soft_attach_t)); } /* * Entry point to funnel all requests down to FCAs */ static int fp_sendcmd(fc_local_port_t *port, fp_cmd_t *cmd, opaque_t fca_handle) { int rval; mutex_enter(&port->fp_mutex); if (port->fp_statec_busy > 1 || (cmd->cmd_ulp_pkt != NULL && (port->fp_statec_busy || FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE))) { /* * This means there is more than one state change * at this point of time - Since they are processed * serially, any processing of the current one should * be failed, failed and move up in processing the next */ cmd->cmd_pkt.pkt_state = FC_PKT_ELS_IN_PROGRESS; cmd->cmd_pkt.pkt_reason = FC_REASON_OFFLINE; if (cmd->cmd_job) { /* * A state change that is going to be invalidated * by another one already in the port driver's queue * need not go up to all ULPs. This will minimize * needless processing and ripples in ULP modules */ cmd->cmd_job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; } mutex_exit(&port->fp_mutex); return (FC_STATEC_BUSY); } if (FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) { cmd->cmd_pkt.pkt_state = FC_PKT_PORT_OFFLINE; cmd->cmd_pkt.pkt_reason = FC_REASON_OFFLINE; mutex_exit(&port->fp_mutex); return (FC_OFFLINE); } mutex_exit(&port->fp_mutex); rval = cmd->cmd_transport(fca_handle, &cmd->cmd_pkt); if (rval != FC_SUCCESS) { if (rval == FC_TRAN_BUSY) { cmd->cmd_retry_interval = fp_retry_delay; rval = fp_retry_cmd(&cmd->cmd_pkt); if (rval == FC_FAILURE) { cmd->cmd_pkt.pkt_state = FC_PKT_TRAN_BSY; } } } else { mutex_enter(&port->fp_mutex); port->fp_out_fpcmds++; mutex_exit(&port->fp_mutex); } return (rval); } /* * Each time a timeout kicks in, walk the wait queue, decrement the * the retry_interval, when the retry_interval becomes less than * or equal to zero, re-transport the command: If the re-transport * fails with BUSY, enqueue the command in the wait queue. * * In order to prevent looping forever because of commands enqueued * from within this function itself, save the current tail pointer * (in cur_tail) and exit the loop after serving this command. */ static void fp_resendcmd(void *port_handle) { int rval; fc_local_port_t *port; fp_cmd_t *cmd; fp_cmd_t *cur_tail; port = port_handle; mutex_enter(&port->fp_mutex); cur_tail = port->fp_wait_tail; mutex_exit(&port->fp_mutex); while ((cmd = fp_deque_cmd(port)) != NULL) { cmd->cmd_retry_interval -= fp_retry_ticker; /* Check if we are detaching */ if (port->fp_soft_state & (FP_SOFT_IN_DETACH | FP_DETACH_INPROGRESS)) { cmd->cmd_pkt.pkt_state = FC_PKT_TRAN_ERROR; cmd->cmd_pkt.pkt_reason = 0; fp_iodone(cmd); } else if (cmd->cmd_retry_interval <= 0) { rval = cmd->cmd_transport(port->fp_fca_handle, &cmd->cmd_pkt); if (rval != FC_SUCCESS) { if (cmd->cmd_pkt.pkt_state == FC_PKT_TRAN_BSY) { if (--cmd->cmd_retry_count) { fp_enque_cmd(port, cmd); if (cmd == cur_tail) { break; } continue; } cmd->cmd_pkt.pkt_state = FC_PKT_TRAN_BSY; } else { cmd->cmd_pkt.pkt_state = FC_PKT_TRAN_ERROR; } cmd->cmd_pkt.pkt_reason = 0; fp_iodone(cmd); } else { mutex_enter(&port->fp_mutex); port->fp_out_fpcmds++; mutex_exit(&port->fp_mutex); } } else { fp_enque_cmd(port, cmd); } if (cmd == cur_tail) { break; } } mutex_enter(&port->fp_mutex); if (port->fp_wait_head) { timeout_id_t tid; mutex_exit(&port->fp_mutex); tid = timeout(fp_resendcmd, (caddr_t)port, fp_retry_ticks); mutex_enter(&port->fp_mutex); port->fp_wait_tid = tid; } else { port->fp_wait_tid = NULL; } mutex_exit(&port->fp_mutex); } /* * Handle Local, Fabric, N_Port, Transport (whatever that means) BUSY here. * * Yes, as you can see below, cmd_retry_count is used here too. That means * the retries for BUSY are less if there were transport failures (transport * failure means fca_transport failure). The goal is not to exceed overall * retries set in the cmd_retry_count (whatever may be the reason for retry) * * Return Values: * FC_SUCCESS * FC_FAILURE */ static int fp_retry_cmd(fc_packet_t *pkt) { fp_cmd_t *cmd; cmd = pkt->pkt_ulp_private; if (--cmd->cmd_retry_count) { fp_enque_cmd(cmd->cmd_port, cmd); return (FC_SUCCESS); } else { return (FC_FAILURE); } } /* * Queue up FC packet for deferred retry */ static void fp_enque_cmd(fc_local_port_t *port, fp_cmd_t *cmd) { timeout_id_t tid; ASSERT(!MUTEX_HELD(&port->fp_mutex)); #ifdef DEBUG fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, &cmd->cmd_pkt, "Retrying ELS for %x", cmd->cmd_pkt.pkt_cmd_fhdr.d_id); #endif mutex_enter(&port->fp_mutex); if (port->fp_wait_tail) { port->fp_wait_tail->cmd_next = cmd; port->fp_wait_tail = cmd; } else { ASSERT(port->fp_wait_head == NULL); port->fp_wait_head = port->fp_wait_tail = cmd; if (port->fp_wait_tid == NULL) { mutex_exit(&port->fp_mutex); tid = timeout(fp_resendcmd, (caddr_t)port, fp_retry_ticks); mutex_enter(&port->fp_mutex); port->fp_wait_tid = tid; } } mutex_exit(&port->fp_mutex); } /* * Handle all RJT codes */ static int fp_handle_reject(fc_packet_t *pkt) { int rval = FC_FAILURE; uchar_t next_class; fp_cmd_t *cmd; fc_local_port_t *port; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; switch (pkt->pkt_state) { case FC_PKT_FABRIC_RJT: case FC_PKT_NPORT_RJT: if (pkt->pkt_reason == FC_REASON_CLASS_NOT_SUPP) { next_class = fp_get_nextclass(cmd->cmd_port, FC_TRAN_CLASS(pkt->pkt_tran_flags)); if (next_class == FC_TRAN_CLASS_INVALID) { return (rval); } pkt->pkt_tran_flags = FC_TRAN_INTR | next_class; pkt->pkt_tran_type = FC_PKT_EXCHANGE; rval = fp_sendcmd(cmd->cmd_port, cmd, cmd->cmd_port->fp_fca_handle); if (rval != FC_SUCCESS) { pkt->pkt_state = FC_PKT_TRAN_ERROR; } } break; case FC_PKT_LS_RJT: case FC_PKT_BA_RJT: if ((pkt->pkt_reason == FC_REASON_LOGICAL_ERROR) || (pkt->pkt_reason == FC_REASON_LOGICAL_BSY)) { cmd->cmd_retry_interval = fp_retry_delay; rval = fp_retry_cmd(pkt); } break; case FC_PKT_FS_RJT: if ((pkt->pkt_reason == FC_REASON_FS_LOGICAL_BUSY) || ((pkt->pkt_reason == FC_REASON_FS_CMD_UNABLE) && (pkt->pkt_expln == 0x00))) { cmd->cmd_retry_interval = fp_retry_delay; rval = fp_retry_cmd(pkt); } break; case FC_PKT_LOCAL_RJT: if (pkt->pkt_reason == FC_REASON_QFULL) { cmd->cmd_retry_interval = fp_retry_delay; rval = fp_retry_cmd(pkt); } break; default: FP_TRACE(FP_NHEAD1(1, 0), "fp_handle_reject(): Invalid pkt_state"); break; } return (rval); } /* * Return the next class of service supported by the FCA */ static uchar_t fp_get_nextclass(fc_local_port_t *port, uchar_t cur_class) { uchar_t next_class; ASSERT(!MUTEX_HELD(&port->fp_mutex)); switch (cur_class) { case FC_TRAN_CLASS_INVALID: if (port->fp_cos & FC_NS_CLASS1) { next_class = FC_TRAN_CLASS1; break; } /* FALLTHROUGH */ case FC_TRAN_CLASS1: if (port->fp_cos & FC_NS_CLASS2) { next_class = FC_TRAN_CLASS2; break; } /* FALLTHROUGH */ case FC_TRAN_CLASS2: if (port->fp_cos & FC_NS_CLASS3) { next_class = FC_TRAN_CLASS3; break; } /* FALLTHROUGH */ case FC_TRAN_CLASS3: default: next_class = FC_TRAN_CLASS_INVALID; break; } return (next_class); } /* * Determine if a class of service is supported by the FCA */ static int fp_is_class_supported(uint32_t cos, uchar_t tran_class) { int rval; switch (tran_class) { case FC_TRAN_CLASS1: if (cos & FC_NS_CLASS1) { rval = FC_SUCCESS; } else { rval = FC_FAILURE; } break; case FC_TRAN_CLASS2: if (cos & FC_NS_CLASS2) { rval = FC_SUCCESS; } else { rval = FC_FAILURE; } break; case FC_TRAN_CLASS3: if (cos & FC_NS_CLASS3) { rval = FC_SUCCESS; } else { rval = FC_FAILURE; } break; default: rval = FC_FAILURE; break; } return (rval); } /* * Dequeue FC packet for retry */ static fp_cmd_t * fp_deque_cmd(fc_local_port_t *port) { fp_cmd_t *cmd; ASSERT(!MUTEX_HELD(&port->fp_mutex)); mutex_enter(&port->fp_mutex); if (port->fp_wait_head == NULL) { /* * To avoid races, NULL the fp_wait_tid as * we are about to exit the timeout thread. */ port->fp_wait_tid = NULL; mutex_exit(&port->fp_mutex); return (NULL); } cmd = port->fp_wait_head; port->fp_wait_head = cmd->cmd_next; cmd->cmd_next = NULL; if (port->fp_wait_head == NULL) { port->fp_wait_tail = NULL; } mutex_exit(&port->fp_mutex); return (cmd); } /* * Wait for job completion */ static void fp_jobwait(job_request_t *job) { sema_p(&job->job_port_sema); } /* * Convert FC packet state to FC errno */ int fp_state_to_rval(uchar_t state) { int count; for (count = 0; count < sizeof (fp_xlat) / sizeof (fp_xlat[0]); count++) { if (fp_xlat[count].xlat_state == state) { return (fp_xlat[count].xlat_rval); } } return (FC_FAILURE); } /* * For Synchronous I/O requests, the caller is * expected to do fctl_jobdone(if necessary) * * We want to preserve at least one failure in the * job_result if it happens. * */ static void fp_iodone(fp_cmd_t *cmd) { fc_packet_t *ulp_pkt = cmd->cmd_ulp_pkt; job_request_t *job = cmd->cmd_job; fc_remote_port_t *pd = cmd->cmd_pkt.pkt_pd; ASSERT(job != NULL); ASSERT(cmd->cmd_port != NULL); ASSERT(&cmd->cmd_pkt != NULL); mutex_enter(&job->job_mutex); if (job->job_result == FC_SUCCESS) { job->job_result = fp_state_to_rval(cmd->cmd_pkt.pkt_state); } mutex_exit(&job->job_mutex); if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); } if (ulp_pkt) { if (pd && cmd->cmd_flags & FP_CMD_DELDEV_ON_ERROR && FP_IS_PKT_ERROR(ulp_pkt)) { fc_local_port_t *port; fc_remote_node_t *node; port = cmd->cmd_port; mutex_enter(&pd->pd_mutex); pd->pd_state = PORT_DEVICE_INVALID; pd->pd_ref_count--; node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); ASSERT(node != NULL); ASSERT(port != NULL); if (fctl_destroy_remote_port(port, pd) == 0) { fctl_destroy_remote_node(node); } ulp_pkt->pkt_pd = NULL; } ulp_pkt->pkt_comp(ulp_pkt); } fp_free_pkt(cmd); fp_jobdone(job); } /* * Job completion handler */ static void fp_jobdone(job_request_t *job) { mutex_enter(&job->job_mutex); ASSERT(job->job_counter > 0); if (--job->job_counter != 0) { mutex_exit(&job->job_mutex); return; } if (job->job_ulp_pkts) { ASSERT(job->job_ulp_listlen > 0); kmem_free(job->job_ulp_pkts, sizeof (fc_packet_t *) * job->job_ulp_listlen); } if (job->job_flags & JOB_TYPE_FP_ASYNC) { mutex_exit(&job->job_mutex); fctl_jobdone(job); } else { mutex_exit(&job->job_mutex); sema_v(&job->job_port_sema); } } /* * Try to perform shutdown of a port during a detach. No return * value since the detach should not fail because the port shutdown * failed. */ static void fp_port_shutdown(fc_local_port_t *port, job_request_t *job) { int index; int count; int flags; fp_cmd_t *cmd; struct pwwn_hash *head; fc_remote_port_t *pd; ASSERT(MUTEX_HELD(&port->fp_mutex)); job->job_result = FC_SUCCESS; if (port->fp_taskq) { /* * We must release the mutex here to ensure that other * potential jobs can complete their processing. Many * also need this mutex. */ mutex_exit(&port->fp_mutex); taskq_wait(port->fp_taskq); mutex_enter(&port->fp_mutex); } if (port->fp_offline_tid) { timeout_id_t tid; tid = port->fp_offline_tid; port->fp_offline_tid = NULL; mutex_exit(&port->fp_mutex); (void) untimeout(tid); mutex_enter(&port->fp_mutex); } if (port->fp_wait_tid) { timeout_id_t tid; tid = port->fp_wait_tid; port->fp_wait_tid = NULL; mutex_exit(&port->fp_mutex); (void) untimeout(tid); } else { mutex_exit(&port->fp_mutex); } /* * While we cancel the timeout, let's also return the * the outstanding requests back to the callers. */ while ((cmd = fp_deque_cmd(port)) != NULL) { ASSERT(cmd->cmd_job != NULL); cmd->cmd_job->job_result = FC_OFFLINE; fp_iodone(cmd); } /* * Gracefully LOGO with all the devices logged in. */ mutex_enter(&port->fp_mutex); for (count = index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { count++; } mutex_exit(&pd->pd_mutex); pd = pd->pd_wwn_hnext; } } if (job->job_flags & JOB_TYPE_FP_ASYNC) { flags = job->job_flags; job->job_flags &= ~JOB_TYPE_FP_ASYNC; } else { flags = 0; } if (count) { job->job_counter = count; for (index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { ASSERT(pd->pd_login_count > 0); /* * Force the counter to ONE in order * for us to really send LOGO els. */ pd->pd_login_count = 1; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); (void) fp_logout(port, pd, job); mutex_enter(&port->fp_mutex); } else { mutex_exit(&pd->pd_mutex); } pd = pd->pd_wwn_hnext; } } mutex_exit(&port->fp_mutex); fp_jobwait(job); } else { mutex_exit(&port->fp_mutex); } if (job->job_result != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(9, 0), "Can't logout all devices. Proceeding with" " port shutdown"); job->job_result = FC_SUCCESS; } fctl_destroy_all_remote_ports(port); mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); fp_ns_fini(port, job); } else { mutex_exit(&port->fp_mutex); } if (flags) { job->job_flags = flags; } mutex_enter(&port->fp_mutex); } /* * Build the port driver's data structures based on the AL_PA list */ static void fp_get_loopmap(fc_local_port_t *port, job_request_t *job) { int rval; int flag; int count; uint32_t d_id; fc_remote_port_t *pd; fc_lilpmap_t *lilp_map; ASSERT(MUTEX_HELD(&port->fp_mutex)); if (FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) { job->job_result = FC_OFFLINE; mutex_exit(&port->fp_mutex); fp_jobdone(job); mutex_enter(&port->fp_mutex); return; } if (port->fp_lilp_map.lilp_length == 0) { mutex_exit(&port->fp_mutex); job->job_result = FC_NO_MAP; fp_jobdone(job); mutex_enter(&port->fp_mutex); return; } mutex_exit(&port->fp_mutex); lilp_map = &port->fp_lilp_map; job->job_counter = lilp_map->lilp_length; if (job->job_code == JOB_PORT_GETMAP_PLOGI_ALL) { flag = FP_CMD_PLOGI_RETAIN; } else { flag = FP_CMD_PLOGI_DONT_CARE; } for (count = 0; count < lilp_map->lilp_length; count++) { d_id = lilp_map->lilp_alpalist[count]; if (d_id == (lilp_map->lilp_myalpa & 0xFF)) { fp_jobdone(job); continue; } pd = fctl_get_remote_port_by_did(port, d_id); if (pd) { mutex_enter(&pd->pd_mutex); if (flag == FP_CMD_PLOGI_DONT_CARE || pd->pd_state == PORT_DEVICE_LOGGED_IN) { mutex_exit(&pd->pd_mutex); fp_jobdone(job); continue; } mutex_exit(&pd->pd_mutex); } rval = fp_port_login(port, d_id, job, flag, KM_SLEEP, pd, NULL); if (rval != FC_SUCCESS) { fp_jobdone(job); } } mutex_enter(&port->fp_mutex); } /* * Perform loop ONLINE processing */ static void fp_loop_online(fc_local_port_t *port, job_request_t *job, int orphan) { int count; int rval; uint32_t d_id; uint32_t listlen; fc_lilpmap_t *lilp_map; fc_remote_port_t *pd; fc_portmap_t *changelist; ASSERT(!MUTEX_HELD(&port->fp_mutex)); FP_TRACE(FP_NHEAD1(1, 0), "fp_loop_online begin; port=%p, job=%p", port, job); lilp_map = &port->fp_lilp_map; if (lilp_map->lilp_length) { mutex_enter(&port->fp_mutex); if (port->fp_soft_state & FP_SOFT_IN_FCA_RESET) { port->fp_soft_state &= ~FP_SOFT_IN_FCA_RESET; mutex_exit(&port->fp_mutex); delay(drv_usectohz(PLDA_RR_TOV * 1000 * 1000)); } else { mutex_exit(&port->fp_mutex); } job->job_counter = lilp_map->lilp_length; for (count = 0; count < lilp_map->lilp_length; count++) { d_id = lilp_map->lilp_alpalist[count]; if (d_id == (lilp_map->lilp_myalpa & 0xFF)) { fp_jobdone(job); continue; } pd = fctl_get_remote_port_by_did(port, d_id); if (pd != NULL) { #ifdef DEBUG mutex_enter(&pd->pd_mutex); if (pd->pd_recepient == PD_PLOGI_INITIATOR) { ASSERT(pd->pd_type != PORT_DEVICE_OLD); } mutex_exit(&pd->pd_mutex); #endif fp_jobdone(job); continue; } rval = fp_port_login(port, d_id, job, FP_CMD_PLOGI_DONT_CARE, KM_SLEEP, pd, NULL); if (rval != FC_SUCCESS) { fp_jobdone(job); } } fp_jobwait(job); } listlen = 0; changelist = NULL; if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { mutex_enter(&port->fp_mutex); ASSERT(port->fp_statec_busy > 0); if (port->fp_statec_busy == 1) { mutex_exit(&port->fp_mutex); fctl_fillout_map(port, &changelist, &listlen, 1, 0, orphan); mutex_enter(&port->fp_mutex); if (port->fp_lilp_map.lilp_magic < MAGIC_LIRP) { ASSERT(port->fp_total_devices == 0); port->fp_total_devices = port->fp_dev_count; } } else { job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; } mutex_exit(&port->fp_mutex); } if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { (void) fp_ulp_statec_cb(port, FC_STATE_ONLINE, changelist, listlen, listlen, KM_SLEEP); } else { mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } ASSERT(changelist == NULL && listlen == 0); mutex_exit(&port->fp_mutex); } FP_TRACE(FP_NHEAD1(1, 0), "fp_loop_online end; port=%p, job=%p", port, job); } /* * Get an Arbitrated Loop map from the underlying FCA */ static int fp_get_lilpmap(fc_local_port_t *port, fc_lilpmap_t *lilp_map) { int rval; FP_TRACE(FP_NHEAD1(1, 0), "fp_get_lilpmap Begin; port=%p, map=%p", port, lilp_map); bzero((caddr_t)lilp_map, sizeof (fc_lilpmap_t)); rval = port->fp_fca_tran->fca_getmap(port->fp_fca_handle, lilp_map); lilp_map->lilp_magic &= 0xFF; /* Ignore upper byte */ if (rval != FC_SUCCESS) { rval = FC_NO_MAP; } else if (lilp_map->lilp_length == 0 && (lilp_map->lilp_magic >= MAGIC_LISM && lilp_map->lilp_magic < MAGIC_LIRP)) { uchar_t lilp_length; /* * Since the map length is zero, provide all * the valid AL_PAs for NL_ports discovery. */ lilp_length = sizeof (fp_valid_alpas) / sizeof (fp_valid_alpas[0]); lilp_map->lilp_length = lilp_length; bcopy(fp_valid_alpas, lilp_map->lilp_alpalist, lilp_length); } else { rval = fp_validate_lilp_map(lilp_map); if (rval == FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_total_devices = lilp_map->lilp_length - 1; mutex_exit(&port->fp_mutex); } } mutex_enter(&port->fp_mutex); if (rval != FC_SUCCESS && !(port->fp_soft_state & FP_SOFT_BAD_LINK)) { port->fp_soft_state |= FP_SOFT_BAD_LINK; mutex_exit(&port->fp_mutex); if (port->fp_fca_tran->fca_reset(port->fp_fca_handle, FC_FCA_RESET_CORE) != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(9, 0), "FCA reset failed after LILP map was found" " to be invalid"); } } else if (rval == FC_SUCCESS) { port->fp_soft_state &= ~FP_SOFT_BAD_LINK; mutex_exit(&port->fp_mutex); } else { mutex_exit(&port->fp_mutex); } FP_TRACE(FP_NHEAD1(1, 0), "fp_get_lilpmap End; port=%p, map=%p", port, lilp_map); return (rval); } /* * Perform Fabric Login: * * Return Values: * FC_SUCCESS * FC_FAILURE * FC_NOMEM * FC_TRANSPORT_ERROR * and a lot others defined in fc_error.h */ static int fp_fabric_login(fc_local_port_t *port, uint32_t s_id, job_request_t *job, int flag, int sleep) { int rval; fp_cmd_t *cmd; uchar_t class; ASSERT(!MUTEX_HELD(&port->fp_mutex)); FP_TRACE(FP_NHEAD1(1, 0), "fp_fabric_login Begin; port=%p, job=%p", port, job); class = fp_get_nextclass(port, FC_TRAN_CLASS_INVALID); if (class == FC_TRAN_CLASS_INVALID) { return (FC_ELS_BAD); } cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), sizeof (la_els_logi_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = flag; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = NULL; fp_xlogi_init(port, cmd, s_id, 0xFFFFFE, fp_flogi_intr, job, LA_ELS_FLOGI); rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { fp_free_pkt(cmd); } FP_TRACE(FP_NHEAD1(1, 0), "fp_fabric_login End; port=%p, job=%p", port, job); return (rval); } /* * In some scenarios such as private loop device discovery period * the fc_remote_port_t data structure isn't allocated. The allocation * is done when the PLOGI is successful. In some other scenarios * such as Fabric topology, the fc_remote_port_t is already created * and initialized with appropriate values (as the NS provides * them) */ static int fp_port_login(fc_local_port_t *port, uint32_t d_id, job_request_t *job, int cmd_flag, int sleep, fc_remote_port_t *pd, fc_packet_t *ulp_pkt) { uchar_t class; fp_cmd_t *cmd; uint32_t src_id; fc_remote_port_t *tmp_pd; int relogin; int found = 0; #ifdef DEBUG if (pd == NULL) { ASSERT(fctl_get_remote_port_by_did(port, d_id) == NULL); } #endif ASSERT(job->job_counter > 0); class = fp_get_nextclass(port, FC_TRAN_CLASS_INVALID); if (class == FC_TRAN_CLASS_INVALID) { return (FC_ELS_BAD); } mutex_enter(&port->fp_mutex); tmp_pd = fctl_lookup_pd_by_did(port, d_id); mutex_exit(&port->fp_mutex); relogin = 1; if (tmp_pd) { mutex_enter(&tmp_pd->pd_mutex); if ((tmp_pd->pd_aux_flags & PD_DISABLE_RELOGIN) && !(tmp_pd->pd_aux_flags & PD_LOGGED_OUT)) { tmp_pd->pd_state = PORT_DEVICE_LOGGED_IN; relogin = 0; } mutex_exit(&tmp_pd->pd_mutex); } if (!relogin) { mutex_enter(&tmp_pd->pd_mutex); if (tmp_pd->pd_state == PORT_DEVICE_LOGGED_IN) { cmd_flag |= FP_CMD_PLOGI_RETAIN; } mutex_exit(&tmp_pd->pd_mutex); cmd = fp_alloc_pkt(port, sizeof (la_els_adisc_t), sizeof (la_els_adisc_t), sleep, tmp_pd); if (cmd == NULL) { return (FC_NOMEM); } cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = cmd_flag; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = ulp_pkt; mutex_enter(&port->fp_mutex); mutex_enter(&tmp_pd->pd_mutex); fp_adisc_init(cmd, job); mutex_exit(&tmp_pd->pd_mutex); mutex_exit(&port->fp_mutex); cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_adisc_t); cmd->cmd_pkt.pkt_rsplen = sizeof (la_els_adisc_t); } else { cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), sizeof (la_els_logi_t), sleep, pd); if (cmd == NULL) { return (FC_NOMEM); } cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = cmd_flag; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = ulp_pkt; mutex_enter(&port->fp_mutex); src_id = port->fp_port_id.port_id; mutex_exit(&port->fp_mutex); fp_xlogi_init(port, cmd, src_id, d_id, fp_plogi_intr, job, LA_ELS_PLOGI); } if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); } /* npiv check to make sure we don't log into ourself */ if (relogin && ((port->fp_npiv_type == FC_NPIV_PORT) || (port->fp_npiv_flag == FC_NPIV_ENABLE))) { if ((d_id & 0xffff00) == (port->fp_port_id.port_id & 0xffff00)) { found = 1; } } if (found || (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS)) { if (found) { fc_packet_t *pkt = &cmd->cmd_pkt; pkt->pkt_state = FC_PKT_NPORT_RJT; } if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); } if (ulp_pkt) { fc_packet_t *pkt = &cmd->cmd_pkt; ulp_pkt->pkt_state = pkt->pkt_state; ulp_pkt->pkt_reason = pkt->pkt_reason; ulp_pkt->pkt_action = pkt->pkt_action; ulp_pkt->pkt_expln = pkt->pkt_expln; } fp_iodone(cmd); } return (FC_SUCCESS); } /* * Register the LOGIN parameters with a port device */ static void fp_register_login(ddi_acc_handle_t *handle, fc_remote_port_t *pd, la_els_logi_t *acc, uchar_t class) { fc_remote_node_t *node; ASSERT(pd != NULL); mutex_enter(&pd->pd_mutex); node = pd->pd_remote_nodep; if (pd->pd_login_count == 0) { pd->pd_login_count++; } if (handle) { FC_GET_RSP(pd->pd_port, *handle, (uint8_t *)&pd->pd_csp, (uint8_t *)&acc->common_service, sizeof (acc->common_service), DDI_DEV_AUTOINCR); FC_GET_RSP(pd->pd_port, *handle, (uint8_t *)&pd->pd_clsp1, (uint8_t *)&acc->class_1, sizeof (acc->class_1), DDI_DEV_AUTOINCR); FC_GET_RSP(pd->pd_port, *handle, (uint8_t *)&pd->pd_clsp2, (uint8_t *)&acc->class_2, sizeof (acc->class_2), DDI_DEV_AUTOINCR); FC_GET_RSP(pd->pd_port, *handle, (uint8_t *)&pd->pd_clsp3, (uint8_t *)&acc->class_3, sizeof (acc->class_3), DDI_DEV_AUTOINCR); } else { pd->pd_csp = acc->common_service; pd->pd_clsp1 = acc->class_1; pd->pd_clsp2 = acc->class_2; pd->pd_clsp3 = acc->class_3; } pd->pd_state = PORT_DEVICE_LOGGED_IN; pd->pd_login_class = class; mutex_exit(&pd->pd_mutex); #ifndef __lock_lint ASSERT(fctl_get_remote_port_by_did(pd->pd_port, pd->pd_port_id.port_id) == pd); #endif mutex_enter(&node->fd_mutex); if (handle) { FC_GET_RSP(pd->pd_port, *handle, (uint8_t *)node->fd_vv, (uint8_t *)acc->vendor_version, sizeof (node->fd_vv), DDI_DEV_AUTOINCR); } else { bcopy(acc->vendor_version, node->fd_vv, sizeof (node->fd_vv)); } mutex_exit(&node->fd_mutex); } /* * Mark the remote port as OFFLINE */ static void fp_remote_port_offline(fc_remote_port_t *pd) { ASSERT(MUTEX_HELD(&pd->pd_mutex)); if (pd->pd_login_count && ((pd->pd_aux_flags & PD_DISABLE_RELOGIN) == 0)) { bzero((caddr_t)&pd->pd_csp, sizeof (struct common_service)); bzero((caddr_t)&pd->pd_clsp1, sizeof (struct service_param)); bzero((caddr_t)&pd->pd_clsp2, sizeof (struct service_param)); bzero((caddr_t)&pd->pd_clsp3, sizeof (struct service_param)); pd->pd_login_class = 0; } pd->pd_type = PORT_DEVICE_OLD; pd->pd_flags = PD_IDLE; fctl_tc_reset(&pd->pd_logo_tc); } /* * Deregistration of a port device */ static void fp_unregister_login(fc_remote_port_t *pd) { fc_remote_node_t *node; ASSERT(pd != NULL); mutex_enter(&pd->pd_mutex); pd->pd_login_count = 0; bzero((caddr_t)&pd->pd_csp, sizeof (struct common_service)); bzero((caddr_t)&pd->pd_clsp1, sizeof (struct service_param)); bzero((caddr_t)&pd->pd_clsp2, sizeof (struct service_param)); bzero((caddr_t)&pd->pd_clsp3, sizeof (struct service_param)); pd->pd_state = PORT_DEVICE_VALID; pd->pd_login_class = 0; node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); bzero(node->fd_vv, sizeof (node->fd_vv)); mutex_exit(&node->fd_mutex); } /* * Handle OFFLINE state of an FCA port */ static void fp_port_offline(fc_local_port_t *port, int notify) { int index; int statec; timeout_id_t tid; struct pwwn_hash *head; fc_remote_port_t *pd; ASSERT(MUTEX_HELD(&port->fp_mutex)); for (index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); fp_remote_port_offline(pd); fctl_delist_did_table(port, pd); mutex_exit(&pd->pd_mutex); pd = pd->pd_wwn_hnext; } } port->fp_total_devices = 0; statec = 0; if (notify) { /* * Decrement the statec busy counter as we * are almost done with handling the state * change */ ASSERT(port->fp_statec_busy > 0); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); (void) fp_ulp_statec_cb(port, FC_STATE_OFFLINE, NULL, 0, 0, KM_SLEEP); mutex_enter(&port->fp_mutex); if (port->fp_statec_busy) { statec++; } } else if (port->fp_statec_busy > 1) { statec++; } if ((tid = port->fp_offline_tid) != NULL) { mutex_exit(&port->fp_mutex); (void) untimeout(tid); mutex_enter(&port->fp_mutex); } if (!statec) { port->fp_offline_tid = timeout(fp_offline_timeout, (caddr_t)port, fp_offline_ticks); } } /* * Offline devices and send up a state change notification to ULPs */ static void fp_offline_timeout(void *port_handle) { int ret; fc_local_port_t *port = port_handle; uint32_t listlen = 0; fc_portmap_t *changelist = NULL; mutex_enter(&port->fp_mutex); if ((FC_PORT_STATE_MASK(port->fp_state) != FC_STATE_OFFLINE) || (port->fp_soft_state & (FP_SOFT_IN_DETACH | FP_SOFT_SUSPEND | FP_SOFT_POWER_DOWN)) || port->fp_dev_count == 0 || port->fp_statec_busy) { port->fp_offline_tid = NULL; mutex_exit(&port->fp_mutex); return; } mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, 0), "OFFLINE timeout"); if (port->fp_options & FP_CORE_ON_OFFLINE_TIMEOUT) { if ((ret = port->fp_fca_tran->fca_reset(port->fp_fca_handle, FC_FCA_CORE)) != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(9, ret), "Failed to force adapter dump"); } else { FP_TRACE(FP_NHEAD1(9, 0), "Forced adapter dump successfully"); } } else if (port->fp_options & FP_RESET_CORE_ON_OFFLINE_TIMEOUT) { if ((ret = port->fp_fca_tran->fca_reset(port->fp_fca_handle, FC_FCA_RESET_CORE)) != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(9, ret), "Failed to force adapter dump and reset"); } else { FP_TRACE(FP_NHEAD1(9, 0), "Forced adapter dump and reset successfully"); } } fctl_fillout_map(port, &changelist, &listlen, 1, 0, 0); (void) fp_ulp_statec_cb(port, FC_STATE_OFFLINE, changelist, listlen, listlen, KM_SLEEP); mutex_enter(&port->fp_mutex); port->fp_offline_tid = NULL; mutex_exit(&port->fp_mutex); } /* * Perform general purpose ELS request initialization */ static void fp_els_init(fp_cmd_t *cmd, uint32_t s_id, uint32_t d_id, void (*comp) (), job_request_t *job) { fc_packet_t *pkt; pkt = &cmd->cmd_pkt; cmd->cmd_job = job; pkt->pkt_cmd_fhdr.r_ctl = R_CTL_ELS_REQ; pkt->pkt_cmd_fhdr.d_id = d_id; pkt->pkt_cmd_fhdr.s_id = s_id; pkt->pkt_cmd_fhdr.type = FC_TYPE_EXTENDED_LS; pkt->pkt_cmd_fhdr.f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; pkt->pkt_cmd_fhdr.seq_id = 0; pkt->pkt_cmd_fhdr.df_ctl = 0; pkt->pkt_cmd_fhdr.seq_cnt = 0; pkt->pkt_cmd_fhdr.ox_id = 0xffff; pkt->pkt_cmd_fhdr.rx_id = 0xffff; pkt->pkt_cmd_fhdr.ro = 0; pkt->pkt_cmd_fhdr.rsvd = 0; pkt->pkt_comp = comp; pkt->pkt_timeout = FP_ELS_TIMEOUT; } /* * Initialize PLOGI/FLOGI ELS request */ static void fp_xlogi_init(fc_local_port_t *port, fp_cmd_t *cmd, uint32_t s_id, uint32_t d_id, void (*intr) (), job_request_t *job, uchar_t ls_code) { ls_code_t payload; fp_els_init(cmd, s_id, d_id, intr, job); cmd->cmd_transport = port->fp_fca_tran->fca_els_send; payload.ls_code = ls_code; payload.mbz = 0; FC_SET_CMD(port, cmd->cmd_pkt.pkt_cmd_acc, (uint8_t *)&port->fp_service_params, (uint8_t *)cmd->cmd_pkt.pkt_cmd, sizeof (port->fp_service_params), DDI_DEV_AUTOINCR); FC_SET_CMD(port, cmd->cmd_pkt.pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)cmd->cmd_pkt.pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Initialize LOGO ELS request */ static void fp_logo_init(fc_remote_port_t *pd, fp_cmd_t *cmd, job_request_t *job) { fc_local_port_t *port; fc_packet_t *pkt; la_els_logo_t payload; port = pd->pd_port; pkt = &cmd->cmd_pkt; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(MUTEX_HELD(&pd->pd_mutex)); fp_els_init(cmd, port->fp_port_id.port_id, pd->pd_port_id.port_id, fp_logo_intr, job); cmd->cmd_transport = port->fp_fca_tran->fca_els_send; pkt->pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; pkt->pkt_tran_type = FC_PKT_EXCHANGE; payload.ls_code.ls_code = LA_ELS_LOGO; payload.ls_code.mbz = 0; payload.nport_ww_name = port->fp_service_params.nport_ww_name; payload.nport_id = port->fp_port_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Initialize RNID ELS request */ static void fp_rnid_init(fp_cmd_t *cmd, uint16_t flag, job_request_t *job) { fc_local_port_t *port; fc_packet_t *pkt; la_els_rnid_t payload; fc_remote_port_t *pd; pkt = &cmd->cmd_pkt; pd = pkt->pkt_pd; port = pd->pd_port; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(MUTEX_HELD(&pd->pd_mutex)); fp_els_init(cmd, port->fp_port_id.port_id, pd->pd_port_id.port_id, fp_rnid_intr, job); cmd->cmd_transport = port->fp_fca_tran->fca_els_send; pkt->pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; pkt->pkt_tran_type = FC_PKT_EXCHANGE; payload.ls_code.ls_code = LA_ELS_RNID; payload.ls_code.mbz = 0; payload.data_format = flag; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Initialize RLS ELS request */ static void fp_rls_init(fp_cmd_t *cmd, job_request_t *job) { fc_local_port_t *port; fc_packet_t *pkt; la_els_rls_t payload; fc_remote_port_t *pd; pkt = &cmd->cmd_pkt; pd = pkt->pkt_pd; port = pd->pd_port; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(MUTEX_HELD(&pd->pd_mutex)); fp_els_init(cmd, port->fp_port_id.port_id, pd->pd_port_id.port_id, fp_rls_intr, job); cmd->cmd_transport = port->fp_fca_tran->fca_els_send; pkt->pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; pkt->pkt_tran_type = FC_PKT_EXCHANGE; payload.ls_code.ls_code = LA_ELS_RLS; payload.ls_code.mbz = 0; payload.rls_portid = port->fp_port_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Initialize an ADISC ELS request */ static void fp_adisc_init(fp_cmd_t *cmd, job_request_t *job) { fc_local_port_t *port; fc_packet_t *pkt; la_els_adisc_t payload; fc_remote_port_t *pd; pkt = &cmd->cmd_pkt; pd = pkt->pkt_pd; port = pd->pd_port; ASSERT(MUTEX_HELD(&pd->pd_mutex)); ASSERT(MUTEX_HELD(&pd->pd_port->fp_mutex)); fp_els_init(cmd, port->fp_port_id.port_id, pd->pd_port_id.port_id, fp_adisc_intr, job); cmd->cmd_transport = port->fp_fca_tran->fca_els_send; pkt->pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; pkt->pkt_tran_type = FC_PKT_EXCHANGE; payload.ls_code.ls_code = LA_ELS_ADISC; payload.ls_code.mbz = 0; payload.nport_id = port->fp_port_id; payload.port_wwn = port->fp_service_params.nport_ww_name; payload.node_wwn = port->fp_service_params.node_ww_name; payload.hard_addr = port->fp_hard_addr; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Send up a state change notification to ULPs. * Spawns a call to fctl_ulp_statec_cb in a taskq thread. */ static int fp_ulp_statec_cb(fc_local_port_t *port, uint32_t state, fc_portmap_t *changelist, uint32_t listlen, uint32_t alloc_len, int sleep) { fc_port_clist_t *clist; fc_remote_port_t *pd; int count; ASSERT(!MUTEX_HELD(&port->fp_mutex)); clist = kmem_zalloc(sizeof (*clist), sleep); if (clist == NULL) { kmem_free(changelist, alloc_len * sizeof (*changelist)); return (FC_NOMEM); } clist->clist_state = state; mutex_enter(&port->fp_mutex); clist->clist_flags = port->fp_topology; mutex_exit(&port->fp_mutex); clist->clist_port = (opaque_t)port; clist->clist_len = listlen; clist->clist_size = alloc_len; clist->clist_map = changelist; /* * Bump the reference count of each fc_remote_port_t in this changelist. * This is necessary since these devices will be sitting in a taskq * and referenced later. When the state change notification is * complete, the reference counts will be decremented. */ for (count = 0; count < clist->clist_len; count++) { pd = clist->clist_map[count].map_pd; if (pd != NULL) { mutex_enter(&pd->pd_mutex); ASSERT((pd->pd_ref_count >= 0) || (pd->pd_aux_flags & PD_GIVEN_TO_ULPS)); pd->pd_ref_count++; if (clist->clist_map[count].map_state != PORT_DEVICE_INVALID) { pd->pd_aux_flags |= PD_GIVEN_TO_ULPS; } mutex_exit(&pd->pd_mutex); } } #ifdef DEBUG /* * Sanity check for presence of OLD devices in the hash lists */ if (clist->clist_size) { ASSERT(clist->clist_map != NULL); for (count = 0; count < clist->clist_len; count++) { if (clist->clist_map[count].map_state == PORT_DEVICE_INVALID) { la_wwn_t pwwn; fc_portid_t d_id; pd = clist->clist_map[count].map_pd; ASSERT(pd != NULL); mutex_enter(&pd->pd_mutex); pwwn = pd->pd_port_name; d_id = pd->pd_port_id; mutex_exit(&pd->pd_mutex); pd = fctl_get_remote_port_by_pwwn(port, &pwwn); ASSERT(pd != clist->clist_map[count].map_pd); pd = fctl_get_remote_port_by_did(port, d_id.port_id); ASSERT(pd != clist->clist_map[count].map_pd); } } } #endif mutex_enter(&port->fp_mutex); if (state == FC_STATE_ONLINE) { if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } } mutex_exit(&port->fp_mutex); (void) taskq_dispatch(port->fp_taskq, fctl_ulp_statec_cb, clist, KM_SLEEP); FP_TRACE(FP_NHEAD1(4, 0), "fp_ulp_statec fired; Port=%p," "state=%x, len=%d", port, state, listlen); return (FC_SUCCESS); } /* * Send up a FC_STATE_DEVICE_CHANGE state notification to ULPs */ static int fp_ulp_devc_cb(fc_local_port_t *port, fc_portmap_t *changelist, uint32_t listlen, uint32_t alloc_len, int sleep, int sync) { int ret; fc_port_clist_t *clist; ASSERT(!MUTEX_HELD(&port->fp_mutex)); clist = kmem_zalloc(sizeof (*clist), sleep); if (clist == NULL) { kmem_free(changelist, alloc_len * sizeof (*changelist)); return (FC_NOMEM); } clist->clist_state = FC_STATE_DEVICE_CHANGE; mutex_enter(&port->fp_mutex); clist->clist_flags = port->fp_topology; mutex_exit(&port->fp_mutex); clist->clist_port = (opaque_t)port; clist->clist_len = listlen; clist->clist_size = alloc_len; clist->clist_map = changelist; /* Send sysevents for target state changes */ if (clist->clist_size) { int count; fc_remote_port_t *pd; ASSERT(clist->clist_map != NULL); for (count = 0; count < clist->clist_len; count++) { pd = clist->clist_map[count].map_pd; /* * Bump reference counts on all fc_remote_port_t * structs in this list. We don't know when the task * will fire, and we don't need these fc_remote_port_t * structs going away behind our back. */ if (pd) { mutex_enter(&pd->pd_mutex); ASSERT((pd->pd_ref_count >= 0) || (pd->pd_aux_flags & PD_GIVEN_TO_ULPS)); pd->pd_ref_count++; mutex_exit(&pd->pd_mutex); } if (clist->clist_map[count].map_state == PORT_DEVICE_VALID) { if (clist->clist_map[count].map_type == PORT_DEVICE_NEW) { /* Update our state change counter */ mutex_enter(&port->fp_mutex); port->fp_last_change++; mutex_exit(&port->fp_mutex); /* Additions */ fp_log_target_event(port, ESC_SUNFC_TARGET_ADD, clist->clist_map[count].map_pwwn, clist->clist_map[count].map_did. port_id); } } else if ((clist->clist_map[count].map_type == PORT_DEVICE_OLD) && (clist->clist_map[count].map_state == PORT_DEVICE_INVALID)) { /* Update our state change counter */ mutex_enter(&port->fp_mutex); port->fp_last_change++; mutex_exit(&port->fp_mutex); /* * For removals, we don't decrement * pd_ref_count until after the ULP's * state change callback function has * completed. */ /* Removals */ fp_log_target_event(port, ESC_SUNFC_TARGET_REMOVE, clist->clist_map[count].map_pwwn, clist->clist_map[count].map_did.port_id); } if (clist->clist_map[count].map_state != PORT_DEVICE_INVALID) { /* * Indicate that the ULPs are now aware of * this device. */ mutex_enter(&pd->pd_mutex); pd->pd_aux_flags |= PD_GIVEN_TO_ULPS; mutex_exit(&pd->pd_mutex); } #ifdef DEBUG /* * Sanity check for OLD devices in the hash lists */ if (pd && clist->clist_map[count].map_state == PORT_DEVICE_INVALID) { la_wwn_t pwwn; fc_portid_t d_id; mutex_enter(&pd->pd_mutex); pwwn = pd->pd_port_name; d_id = pd->pd_port_id; mutex_exit(&pd->pd_mutex); /* * This overwrites the 'pd' local variable. * Beware of this if 'pd' ever gets * referenced below this block. */ pd = fctl_get_remote_port_by_pwwn(port, &pwwn); ASSERT(pd != clist->clist_map[count].map_pd); pd = fctl_get_remote_port_by_did(port, d_id.port_id); ASSERT(pd != clist->clist_map[count].map_pd); } #endif } } if (sync) { clist->clist_wait = 1; mutex_init(&clist->clist_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&clist->clist_cv, NULL, CV_DRIVER, NULL); } ret = taskq_dispatch(port->fp_taskq, fctl_ulp_statec_cb, clist, sleep); if (sync && ret != TASKQID_INVALID) { mutex_enter(&clist->clist_mutex); while (clist->clist_wait) { cv_wait(&clist->clist_cv, &clist->clist_mutex); } mutex_exit(&clist->clist_mutex); mutex_destroy(&clist->clist_mutex); cv_destroy(&clist->clist_cv); kmem_free(clist, sizeof (*clist)); } if (!ret) { FP_TRACE(FP_NHEAD1(4, 0), "fp_ulp_devc dispatch failed; " "port=%p", port); kmem_free(clist->clist_map, sizeof (*(clist->clist_map)) * clist->clist_size); kmem_free(clist, sizeof (*clist)); } else { FP_TRACE(FP_NHEAD1(4, 0), "fp_ulp_devc fired; port=%p, len=%d", port, listlen); } return (FC_SUCCESS); } /* * Perform PLOGI to the group of devices for ULPs */ static void fp_plogi_group(fc_local_port_t *port, job_request_t *job) { int offline; int count; int rval; uint32_t listlen; uint32_t done; uint32_t d_id; fc_remote_node_t *node; fc_remote_port_t *pd; fc_remote_port_t *tmp_pd; fc_packet_t *ulp_pkt; la_els_logi_t *els_data; ls_code_t ls_code; FP_TRACE(FP_NHEAD1(1, 0), "fp_plogi_group begin; port=%p, job=%p", port, job); done = 0; listlen = job->job_ulp_listlen; job->job_counter = job->job_ulp_listlen; mutex_enter(&port->fp_mutex); offline = (port->fp_statec_busy || FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) ? 1 : 0; mutex_exit(&port->fp_mutex); for (count = 0; count < listlen; count++) { ASSERT(job->job_ulp_pkts[count]->pkt_rsplen >= sizeof (la_els_logi_t)); ulp_pkt = job->job_ulp_pkts[count]; pd = ulp_pkt->pkt_pd; d_id = ulp_pkt->pkt_cmd_fhdr.d_id; if (offline) { done++; ulp_pkt->pkt_state = FC_PKT_PORT_OFFLINE; ulp_pkt->pkt_reason = FC_REASON_OFFLINE; ulp_pkt->pkt_pd = NULL; ulp_pkt->pkt_comp(ulp_pkt); job->job_ulp_pkts[count] = NULL; fp_jobdone(job); continue; } if (pd == NULL) { pd = fctl_get_remote_port_by_did(port, d_id); if (pd == NULL) { /* reset later */ ulp_pkt->pkt_state = FC_PKT_FAILURE; continue; } mutex_enter(&pd->pd_mutex); if (pd->pd_flags == PD_ELS_IN_PROGRESS) { mutex_exit(&pd->pd_mutex); ulp_pkt->pkt_state = FC_PKT_ELS_IN_PROGRESS; done++; ulp_pkt->pkt_comp(ulp_pkt); job->job_ulp_pkts[count] = NULL; fp_jobdone(job); } else { ulp_pkt->pkt_state = FC_PKT_FAILURE; mutex_exit(&pd->pd_mutex); } continue; } switch (ulp_pkt->pkt_state) { case FC_PKT_ELS_IN_PROGRESS: ulp_pkt->pkt_reason = FC_REASON_OFFLINE; /* FALLTHRU */ case FC_PKT_LOCAL_RJT: done++; ulp_pkt->pkt_comp(ulp_pkt); job->job_ulp_pkts[count] = NULL; fp_jobdone(job); continue; default: break; } /* * Validate the pd corresponding to the d_id passed * by the ULPs */ tmp_pd = fctl_get_remote_port_by_did(port, d_id); if ((tmp_pd == NULL) || (pd != tmp_pd)) { done++; ulp_pkt->pkt_state = FC_PKT_FAILURE; ulp_pkt->pkt_reason = FC_REASON_NO_CONNECTION; ulp_pkt->pkt_pd = NULL; ulp_pkt->pkt_comp(ulp_pkt); job->job_ulp_pkts[count] = NULL; fp_jobdone(job); continue; } FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group contd; " "port=%p, pd=%p", port, pd); mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { done++; els_data = (la_els_logi_t *)ulp_pkt->pkt_resp; ls_code.ls_code = LA_ELS_ACC; ls_code.mbz = 0; FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&ls_code, (uint8_t *)&els_data->ls_code, sizeof (ls_code_t), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&pd->pd_csp, (uint8_t *)&els_data->common_service, sizeof (pd->pd_csp), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&pd->pd_port_name, (uint8_t *)&els_data->nport_ww_name, sizeof (pd->pd_port_name), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&pd->pd_clsp1, (uint8_t *)&els_data->class_1, sizeof (pd->pd_clsp1), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&pd->pd_clsp2, (uint8_t *)&els_data->class_2, sizeof (pd->pd_clsp2), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&pd->pd_clsp3, (uint8_t *)&els_data->class_3, sizeof (pd->pd_clsp3), DDI_DEV_AUTOINCR); node = pd->pd_remote_nodep; pd->pd_login_count++; pd->pd_flags = PD_IDLE; ulp_pkt->pkt_pd = pd; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&node->fd_node_name, (uint8_t *)(&els_data->node_ww_name), sizeof (node->fd_node_name), DDI_DEV_AUTOINCR); FC_SET_CMD(pd->pd_port, ulp_pkt->pkt_resp_acc, (uint8_t *)&node->fd_vv, (uint8_t *)(&els_data->vendor_version), sizeof (node->fd_vv), DDI_DEV_AUTOINCR); mutex_exit(&node->fd_mutex); ulp_pkt->pkt_state = FC_PKT_SUCCESS; } else { ulp_pkt->pkt_state = FC_PKT_FAILURE; /* reset later */ mutex_exit(&pd->pd_mutex); } if (ulp_pkt->pkt_state != FC_PKT_FAILURE) { ulp_pkt->pkt_comp(ulp_pkt); job->job_ulp_pkts[count] = NULL; fp_jobdone(job); } } if (done == listlen) { fp_jobwait(job); fctl_jobdone(job); return; } job->job_counter = listlen - done; for (count = 0; count < listlen; count++) { int cmd_flags; if ((ulp_pkt = job->job_ulp_pkts[count]) == NULL) { continue; } ASSERT(ulp_pkt->pkt_state == FC_PKT_FAILURE); cmd_flags = FP_CMD_PLOGI_RETAIN; d_id = ulp_pkt->pkt_cmd_fhdr.d_id; ASSERT(d_id != 0); pd = fctl_get_remote_port_by_did(port, d_id); /* * We need to properly adjust the port device * reference counter before we assign the pd * to the ULP packets port device pointer. */ if (pd != NULL && ulp_pkt->pkt_pd == NULL) { mutex_enter(&pd->pd_mutex); pd->pd_ref_count++; mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group: DID = 0x%x using new pd %p \ old pd NULL\n", d_id, pd); } else if (pd != NULL && ulp_pkt->pkt_pd != NULL && ulp_pkt->pkt_pd != pd) { mutex_enter(&pd->pd_mutex); pd->pd_ref_count++; mutex_exit(&pd->pd_mutex); mutex_enter(&ulp_pkt->pkt_pd->pd_mutex); ulp_pkt->pkt_pd->pd_ref_count--; mutex_exit(&ulp_pkt->pkt_pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group: DID = 0x%x pkt_pd %p != pd %p\n", d_id, ulp_pkt->pkt_pd, pd); } else if (pd == NULL && ulp_pkt->pkt_pd != NULL) { mutex_enter(&ulp_pkt->pkt_pd->pd_mutex); ulp_pkt->pkt_pd->pd_ref_count--; mutex_exit(&ulp_pkt->pkt_pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group: DID = 0x%x pd is NULL and \ pkt_pd = %p\n", d_id, ulp_pkt->pkt_pd); } ulp_pkt->pkt_pd = pd; if (pd != NULL) { mutex_enter(&pd->pd_mutex); d_id = pd->pd_port_id.port_id; pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); } else { d_id = ulp_pkt->pkt_cmd_fhdr.d_id; #ifdef DEBUG pd = fctl_get_remote_port_by_did(port, d_id); ASSERT(pd == NULL); #endif /* * In the Fabric topology, use NS to create * port device, and if that fails still try * with PLOGI - which will make yet another * attempt to create after successful PLOGI */ mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); pd = fp_create_remote_port_by_ns(port, d_id, KM_SLEEP); if (pd) { cmd_flags |= FP_CMD_DELDEV_ON_ERROR; mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group;" " NS created PD port=%p, job=%p," " pd=%p", port, job, pd); } } else { mutex_exit(&port->fp_mutex); } if ((ulp_pkt->pkt_pd == NULL) && (pd != NULL)) { FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group;" "ulp_pkt's pd is NULL, get a pd %p", pd); mutex_enter(&pd->pd_mutex); pd->pd_ref_count++; mutex_exit(&pd->pd_mutex); } ulp_pkt->pkt_pd = pd; } rval = fp_port_login(port, d_id, job, cmd_flags, KM_SLEEP, pd, ulp_pkt); if (rval == FC_SUCCESS) { continue; } if (rval == FC_STATEC_BUSY) { ulp_pkt->pkt_state = FC_PKT_PORT_OFFLINE; ulp_pkt->pkt_reason = FC_REASON_OFFLINE; } else { ulp_pkt->pkt_state = FC_PKT_FAILURE; } if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); } if (cmd_flags & FP_CMD_DELDEV_ON_ERROR) { ASSERT(pd != NULL); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_group: NS created," " PD removed; port=%p, job=%p", port, job); mutex_enter(&pd->pd_mutex); pd->pd_ref_count--; node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); ASSERT(node != NULL); if (fctl_destroy_remote_port(port, pd) == 0) { fctl_destroy_remote_node(node); } ulp_pkt->pkt_pd = NULL; } ulp_pkt->pkt_comp(ulp_pkt); fp_jobdone(job); } fp_jobwait(job); fctl_jobdone(job); FP_TRACE(FP_NHEAD1(1, 0), "fp_plogi_group end: port=%p, job=%p", port, job); } /* * Name server request initialization */ static void fp_ns_init(fc_local_port_t *port, job_request_t *job, int sleep) { int rval; int count; int size; ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); job->job_counter = 1; job->job_result = FC_SUCCESS; rval = fp_port_login(port, 0xFFFFFC, job, FP_CMD_PLOGI_RETAIN, KM_SLEEP, NULL, NULL); if (rval != FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_topology = FC_TOP_NO_NS; mutex_exit(&port->fp_mutex); return; } fp_jobwait(job); if (job->job_result != FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_topology = FC_TOP_NO_NS; mutex_exit(&port->fp_mutex); return; } /* * At this time, we'll do NS registration for objects in the * ns_reg_cmds (see top of this file) array. * * Each time a ULP module registers with the transport, the * appropriate fc4 bit is set fc4 types and registered with * the NS for this support. Also, ULPs and FC admin utilities * may do registration for objects like IP address, symbolic * port/node name, Initial process associator at run time. */ size = sizeof (ns_reg_cmds) / sizeof (ns_reg_cmds[0]); job->job_counter = size; job->job_result = FC_SUCCESS; for (count = 0; count < size; count++) { if (fp_ns_reg(port, NULL, ns_reg_cmds[count], job, 0, sleep) != FC_SUCCESS) { fp_jobdone(job); } } if (size) { fp_jobwait(job); } job->job_result = FC_SUCCESS; (void) fp_ns_get_devcount(port, job, 0, KM_SLEEP); if (port->fp_dev_count < FP_MAX_DEVICES) { (void) fp_ns_get_devcount(port, job, 1, KM_SLEEP); } job->job_counter = 1; if (fp_ns_scr(port, job, FC_SCR_FULL_REGISTRATION, sleep) == FC_SUCCESS) { fp_jobwait(job); } } /* * Name server finish: * Unregister for RSCNs * Unregister all the host port objects in the Name Server * Perform LOGO with the NS; */ static void fp_ns_fini(fc_local_port_t *port, job_request_t *job) { fp_cmd_t *cmd; uchar_t class; uint32_t s_id; fc_packet_t *pkt; la_els_logo_t payload; ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); job->job_counter = 1; if (fp_ns_scr(port, job, FC_SCR_CLEAR_REGISTRATION, KM_SLEEP) != FC_SUCCESS) { fp_jobdone(job); } fp_jobwait(job); job->job_counter = 1; if (fp_ns_reg(port, NULL, NS_DA_ID, job, 0, KM_SLEEP) != FC_SUCCESS) { fp_jobdone(job); } fp_jobwait(job); job->job_counter = 1; cmd = fp_alloc_pkt(port, sizeof (la_els_logo_t), FP_PORT_IDENTIFIER_LEN, KM_SLEEP, NULL); pkt = &cmd->cmd_pkt; mutex_enter(&port->fp_mutex); class = port->fp_ns_login_class; s_id = port->fp_port_id.port_id; payload.nport_id = port->fp_port_id; mutex_exit(&port->fp_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_PLOGI_DONT_CARE; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; if (port->fp_npiv_type == FC_NPIV_PORT) { fp_els_init(cmd, s_id, 0xFFFFFE, fp_logo_intr, job); } else { fp_els_init(cmd, s_id, 0xFFFFFC, fp_logo_intr, job); } cmd->cmd_transport = port->fp_fca_tran->fca_els_send; payload.ls_code.ls_code = LA_ELS_LOGO; payload.ls_code.mbz = 0; payload.nport_ww_name = port->fp_service_params.nport_ww_name; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_iodone(cmd); } fp_jobwait(job); } /* * NS Registration function. * * It should be seriously noted that FC-GS-2 currently doesn't support * an Object Registration by a D_ID other than the owner of the object. * What we are aiming at currently is to at least allow Symbolic Node/Port * Name registration for any N_Port Identifier by the host software. * * Anyway, if the second argument (fc_remote_port_t *) is NULL, this * function treats the request as Host NS Object. */ static int fp_ns_reg(fc_local_port_t *port, fc_remote_port_t *pd, uint16_t cmd_code, job_request_t *job, int polled, int sleep) { int rval; fc_portid_t s_id; fc_packet_t *pkt; fp_cmd_t *cmd; if (pd == NULL) { mutex_enter(&port->fp_mutex); s_id = port->fp_port_id; mutex_exit(&port->fp_mutex); } else { mutex_enter(&pd->pd_mutex); s_id = pd->pd_port_id; mutex_exit(&pd->pd_mutex); } if (polled) { job->job_counter = 1; } switch (cmd_code) { case NS_RPN_ID: case NS_RNN_ID: { ns_rxn_req_t rxn; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_rxn_req_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { rxn.rxn_xname = ((cmd_code == NS_RPN_ID) ? (port->fp_service_params.nport_ww_name) : (port->fp_service_params.node_ww_name)); } else { if (cmd_code == NS_RPN_ID) { mutex_enter(&pd->pd_mutex); rxn.rxn_xname = pd->pd_port_name; mutex_exit(&pd->pd_mutex); } else { fc_remote_node_t *node; mutex_enter(&pd->pd_mutex); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); rxn.rxn_xname = node->fd_node_name; mutex_exit(&node->fd_mutex); } } rxn.rxn_port_id = s_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rxn, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rxn), DDI_DEV_AUTOINCR); break; } case NS_RCS_ID: { ns_rcos_t rcos; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_rcos_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { rcos.rcos_cos = port->fp_cos; } else { mutex_enter(&pd->pd_mutex); rcos.rcos_cos = pd->pd_cos; mutex_exit(&pd->pd_mutex); } rcos.rcos_port_id = s_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rcos, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rcos), DDI_DEV_AUTOINCR); break; } case NS_RFT_ID: { ns_rfc_type_t rfc; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_rfc_type_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { mutex_enter(&port->fp_mutex); bcopy(port->fp_fc4_types, rfc.rfc_types, sizeof (port->fp_fc4_types)); mutex_exit(&port->fp_mutex); } else { mutex_enter(&pd->pd_mutex); bcopy(pd->pd_fc4types, rfc.rfc_types, sizeof (pd->pd_fc4types)); mutex_exit(&pd->pd_mutex); } rfc.rfc_port_id = s_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rfc, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rfc), DDI_DEV_AUTOINCR); break; } case NS_RSPN_ID: { uchar_t name_len; int pl_size; fc_portid_t spn; if (pd == NULL) { mutex_enter(&port->fp_mutex); name_len = port->fp_sym_port_namelen; mutex_exit(&port->fp_mutex); } else { mutex_enter(&pd->pd_mutex); name_len = pd->pd_spn_len; mutex_exit(&pd->pd_mutex); } pl_size = sizeof (fc_portid_t) + name_len + 1; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + pl_size, sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; spn = s_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&spn, (uint8_t *) (pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (spn), DDI_DEV_AUTOINCR); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&name_len, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (fc_portid_t)), 1, DDI_DEV_AUTOINCR); if (pd == NULL) { mutex_enter(&port->fp_mutex); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)port->fp_sym_port_name, (uint8_t *) (pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (spn) + 1), name_len, DDI_DEV_AUTOINCR); mutex_exit(&port->fp_mutex); } else { mutex_enter(&pd->pd_mutex); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)pd->pd_spn, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (spn) + 1), name_len, DDI_DEV_AUTOINCR); mutex_exit(&pd->pd_mutex); } break; } case NS_RPT_ID: { ns_rpt_t rpt; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_rpt_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { rpt.rpt_type = port->fp_port_type; } else { mutex_enter(&pd->pd_mutex); rpt.rpt_type = pd->pd_porttype; mutex_exit(&pd->pd_mutex); } rpt.rpt_port_id = s_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rpt, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rpt), DDI_DEV_AUTOINCR); break; } case NS_RIP_NN: { ns_rip_t rip; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_rip_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { rip.rip_node_name = port->fp_service_params.node_ww_name; bcopy(port->fp_ip_addr, rip.rip_ip_addr, sizeof (port->fp_ip_addr)); } else { fc_remote_node_t *node; /* * The most correct implementation should have the IP * address in the fc_remote_node_t structure; I believe * Node WWN and IP address should have one to one * correlation (but guess what this is changing in * FC-GS-2 latest draft) */ mutex_enter(&pd->pd_mutex); node = pd->pd_remote_nodep; bcopy(pd->pd_ip_addr, rip.rip_ip_addr, sizeof (pd->pd_ip_addr)); mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); rip.rip_node_name = node->fd_node_name; mutex_exit(&node->fd_mutex); } FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rip, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rip), DDI_DEV_AUTOINCR); break; } case NS_RIPA_NN: { ns_ipa_t ipa; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_ipa_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; if (pd == NULL) { ipa.ipa_node_name = port->fp_service_params.node_ww_name; bcopy(port->fp_ipa, ipa.ipa_value, sizeof (port->fp_ipa)); } else { fc_remote_node_t *node; mutex_enter(&pd->pd_mutex); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); ipa.ipa_node_name = node->fd_node_name; bcopy(node->fd_ipa, ipa.ipa_value, sizeof (node->fd_ipa)); mutex_exit(&node->fd_mutex); } FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&ipa, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (ipa), DDI_DEV_AUTOINCR); break; } case NS_RSNN_NN: { uchar_t name_len; int pl_size; la_wwn_t snn; fc_remote_node_t *node = NULL; if (pd == NULL) { mutex_enter(&port->fp_mutex); name_len = port->fp_sym_node_namelen; mutex_exit(&port->fp_mutex); } else { mutex_enter(&pd->pd_mutex); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); name_len = node->fd_snn_len; mutex_exit(&node->fd_mutex); } pl_size = sizeof (la_wwn_t) + name_len + 1; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + pl_size, sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; bcopy(&port->fp_service_params.node_ww_name, &snn, sizeof (la_wwn_t)); if (pd == NULL) { mutex_enter(&port->fp_mutex); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)port->fp_sym_node_name, (uint8_t *) (pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (snn) + 1), name_len, DDI_DEV_AUTOINCR); mutex_exit(&port->fp_mutex); } else { ASSERT(node != NULL); mutex_enter(&node->fd_mutex); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)node->fd_snn, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (snn) + 1), name_len, DDI_DEV_AUTOINCR); mutex_exit(&node->fd_mutex); } FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&snn, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (snn), DDI_DEV_AUTOINCR); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&name_len, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t) + sizeof (snn)), 1, DDI_DEV_AUTOINCR); break; } case NS_DA_ID: { ns_remall_t rall; char tmp[4] = {0}; char *ptr; cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + sizeof (ns_remall_t), sizeof (fc_reg_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, NULL, cmd_code, NULL, 0, 0, job); pkt = &cmd->cmd_pkt; ptr = (char *)(&s_id); tmp[3] = *ptr++; tmp[2] = *ptr++; tmp[1] = *ptr++; tmp[0] = *ptr; #if defined(_BIT_FIELDS_LTOH) bcopy((caddr_t)tmp, (caddr_t)(&rall.rem_port_id), 4); #else rall.rem_port_id = s_id; #endif FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&rall, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (rall), DDI_DEV_AUTOINCR); break; } default: return (FC_FAILURE); } rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { job->job_result = rval; fp_iodone(cmd); } if (polled) { ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); fp_jobwait(job); } else { rval = FC_SUCCESS; } return (rval); } /* * Common interrupt handler */ static int fp_common_intr(fc_packet_t *pkt, int iodone) { int rval = FC_FAILURE; fp_cmd_t *cmd; fc_local_port_t *port; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; /* * Fail fast the upper layer requests if * a state change has occurred amidst. */ mutex_enter(&port->fp_mutex); if (cmd->cmd_ulp_pkt != NULL && port->fp_statec_busy) { mutex_exit(&port->fp_mutex); cmd->cmd_ulp_pkt->pkt_state = FC_PKT_PORT_OFFLINE; cmd->cmd_ulp_pkt->pkt_reason = FC_REASON_OFFLINE; } else if (!(port->fp_soft_state & (FP_SOFT_IN_DETACH | FP_DETACH_INPROGRESS))) { mutex_exit(&port->fp_mutex); switch (pkt->pkt_state) { case FC_PKT_LOCAL_BSY: case FC_PKT_FABRIC_BSY: case FC_PKT_NPORT_BSY: case FC_PKT_TIMEOUT: cmd->cmd_retry_interval = (pkt->pkt_state == FC_PKT_TIMEOUT) ? 0 : fp_retry_delay; rval = fp_retry_cmd(pkt); break; case FC_PKT_FABRIC_RJT: case FC_PKT_NPORT_RJT: case FC_PKT_LOCAL_RJT: case FC_PKT_LS_RJT: case FC_PKT_FS_RJT: case FC_PKT_BA_RJT: rval = fp_handle_reject(pkt); break; default: if (pkt->pkt_resp_resid) { cmd->cmd_retry_interval = 0; rval = fp_retry_cmd(pkt); } break; } } else { mutex_exit(&port->fp_mutex); } if (rval != FC_SUCCESS && iodone) { fp_iodone(cmd); rval = FC_SUCCESS; } return (rval); } /* * Some not so long winding theory on point to point topology: * * In the ACC payload, if the D_ID is ZERO and the common service * parameters indicate N_Port, then the topology is POINT TO POINT. * * In a point to point topology with an N_Port, during Fabric Login, * the destination N_Port will check with our WWN and decide if it * needs to issue PLOGI or not. That means, FLOGI could potentially * trigger an unsolicited PLOGI from an N_Port. The Unsolicited * PLOGI creates the device handles. * * Assuming that the host port WWN is greater than the other N_Port * WWN, then we become the master (be aware that this isn't the word * used in the FC standards) and initiate the PLOGI. * */ static void fp_flogi_intr(fc_packet_t *pkt) { int state; int f_port; uint32_t s_id; uint32_t d_id; fp_cmd_t *cmd; fc_local_port_t *port; la_wwn_t *swwn; la_wwn_t dwwn; la_wwn_t nwwn; fc_remote_port_t *pd; la_els_logi_t *acc; com_svc_t csp; ls_code_t resp; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; mutex_enter(&port->fp_mutex); port->fp_out_fpcmds--; mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD1(1, 0), "fp_flogi_intr; port=%p, pkt=%p, state=%x", port, pkt, pkt->pkt_state); if (FP_IS_PKT_ERROR(pkt)) { (void) fp_common_intr(pkt, 1); return; } /* * Currently, we don't need to swap bytes here because qlc is faking the * response for us and so endianness is getting taken care of. But we * have to fix this and generalize this at some point */ acc = (la_els_logi_t *)pkt->pkt_resp; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)acc, sizeof (resp), DDI_DEV_AUTOINCR); ASSERT(resp.ls_code == LA_ELS_ACC); if (resp.ls_code != LA_ELS_ACC) { (void) fp_common_intr(pkt, 1); return; } FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&csp, (uint8_t *)&acc->common_service, sizeof (csp), DDI_DEV_AUTOINCR); f_port = FP_IS_F_PORT(csp.cmn_features) ? 1 : 0; ASSERT(!MUTEX_HELD(&port->fp_mutex)); mutex_enter(&port->fp_mutex); state = FC_PORT_STATE_MASK(port->fp_state); mutex_exit(&port->fp_mutex); if (f_port == 0) { if (state != FC_STATE_LOOP) { swwn = &port->fp_service_params.nport_ww_name; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&dwwn, (uint8_t *)&acc->nport_ww_name, sizeof (la_wwn_t), DDI_DEV_AUTOINCR); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&nwwn, (uint8_t *)&acc->node_ww_name, sizeof (la_wwn_t), DDI_DEV_AUTOINCR); mutex_enter(&port->fp_mutex); port->fp_topology = FC_TOP_PT_PT; port->fp_total_devices = 1; if (fctl_wwn_cmp(swwn, &dwwn) >= 0) { port->fp_ptpt_master = 1; /* * Let us choose 'X' as S_ID and 'Y' * as D_ID and that'll work; hopefully * If not, it will get changed. */ s_id = port->fp_instance + FP_DEFAULT_SID; d_id = port->fp_instance + FP_DEFAULT_DID; port->fp_port_id.port_id = s_id; mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD1(1, 0), "fp_flogi_intr: fp %x" "pd %x", port->fp_port_id.port_id, d_id); pd = fctl_create_remote_port(port, &nwwn, &dwwn, d_id, PD_PLOGI_INITIATOR, KM_NOSLEEP); if (pd == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "couldn't create device" " d_id=%X", d_id); fp_iodone(cmd); return; } cmd->cmd_pkt.pkt_tran_flags = pkt->pkt_tran_flags; cmd->cmd_pkt.pkt_tran_type = pkt->pkt_tran_type; cmd->cmd_flags = FP_CMD_PLOGI_RETAIN; cmd->cmd_retry_count = fp_retry_count; fp_xlogi_init(port, cmd, s_id, d_id, fp_plogi_intr, cmd->cmd_job, LA_ELS_PLOGI); (&cmd->cmd_pkt)->pkt_pd = pd; /* * We've just created this fc_remote_port_t, and * we're about to use it to send a PLOGI, so * bump the reference count right now. When * the packet is freed, the reference count will * be decremented. The ULP may also start using * it, so mark it as given away as well. */ pd->pd_ref_count++; pd->pd_aux_flags |= PD_GIVEN_TO_ULPS; if (fp_sendcmd(port, cmd, port->fp_fca_handle) == FC_SUCCESS) { return; } } else { /* * The device handles will be created when the * unsolicited PLOGI is completed successfully */ port->fp_ptpt_master = 0; mutex_exit(&port->fp_mutex); } } pkt->pkt_state = FC_PKT_FAILURE; } else { if (f_port) { mutex_enter(&port->fp_mutex); if (state == FC_STATE_LOOP) { port->fp_topology = FC_TOP_PUBLIC_LOOP; } else { port->fp_topology = FC_TOP_FABRIC; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&port->fp_fabric_name, (uint8_t *)&acc->node_ww_name, sizeof (la_wwn_t), DDI_DEV_AUTOINCR); } port->fp_port_id.port_id = pkt->pkt_resp_fhdr.d_id; mutex_exit(&port->fp_mutex); } else { pkt->pkt_state = FC_PKT_FAILURE; } } fp_iodone(cmd); } /* * Handle solicited PLOGI response */ static void fp_plogi_intr(fc_packet_t *pkt) { int nl_port; int bailout; uint32_t d_id; fp_cmd_t *cmd; la_els_logi_t *acc; fc_local_port_t *port; fc_remote_port_t *pd; la_wwn_t nwwn; la_wwn_t pwwn; ls_code_t resp; nl_port = 0; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; d_id = pkt->pkt_cmd_fhdr.d_id; #ifndef __lock_lint ASSERT(cmd->cmd_job && cmd->cmd_job->job_counter); #endif FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_intr: port=%p, job=%p, d_id=%x," " jcount=%d pkt=%p, state=%x", port, cmd->cmd_job, d_id, cmd->cmd_job->job_counter, pkt, pkt->pkt_state); /* * Bail out early on ULP initiated requests if the * state change has occurred */ mutex_enter(&port->fp_mutex); port->fp_out_fpcmds--; bailout = ((port->fp_statec_busy || FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) && cmd->cmd_ulp_pkt) ? 1 : 0; mutex_exit(&port->fp_mutex); if (FP_IS_PKT_ERROR(pkt) || bailout) { int skip_msg = 0; int giveup = 0; if (cmd->cmd_ulp_pkt) { cmd->cmd_ulp_pkt->pkt_state = pkt->pkt_state; cmd->cmd_ulp_pkt->pkt_reason = pkt->pkt_reason; cmd->cmd_ulp_pkt->pkt_action = pkt->pkt_action; cmd->cmd_ulp_pkt->pkt_expln = pkt->pkt_expln; } /* * If an unsolicited cross login already created * a device speed up the discovery by not retrying * the command mindlessly. */ if (pkt->pkt_pd == NULL && fctl_get_remote_port_by_did(port, d_id) != NULL) { fp_iodone(cmd); return; } if (pkt->pkt_pd != NULL) { giveup = (pkt->pkt_pd->pd_recepient == PD_PLOGI_RECEPIENT) ? 1 : 0; if (giveup) { /* * This pd is marked as plogi * recipient, stop retrying */ FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_intr: stop retry as" " a cross login was accepted" " from d_id=%x, port=%p.", d_id, port); fp_iodone(cmd); return; } } if (fp_common_intr(pkt, 0) == FC_SUCCESS) { return; } if ((pd = fctl_get_remote_port_by_did(port, d_id)) != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { skip_msg++; } mutex_exit(&pd->pd_mutex); } mutex_enter(&port->fp_mutex); if (!bailout && !(skip_msg && port->fp_statec_busy) && port->fp_statec_busy <= 1 && pkt->pkt_reason != FC_REASON_FCAL_OPN_FAIL) { mutex_exit(&port->fp_mutex); /* * In case of Login Collisions, JNI HBAs returns the * FC pkt back to the Initiator with the state set to * FC_PKT_LS_RJT and reason to FC_REASON_LOGICAL_ERROR. * QLC HBAs handles such cases in the FW and doesnot * return the LS_RJT with Logical error when * login collision happens. */ if ((pkt->pkt_state != FC_PKT_LS_RJT) || (pkt->pkt_reason != FC_REASON_LOGICAL_ERROR)) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, pkt, "PLOGI to %x failed", d_id); } FP_TRACE(FP_NHEAD2(9, 0), "PLOGI to %x failed. state=%x reason=%x.", d_id, pkt->pkt_state, pkt->pkt_reason); } else { mutex_exit(&port->fp_mutex); } fp_iodone(cmd); return; } acc = (la_els_logi_t *)pkt->pkt_resp; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)acc, sizeof (resp), DDI_DEV_AUTOINCR); ASSERT(resp.ls_code == LA_ELS_ACC); if (resp.ls_code != LA_ELS_ACC) { (void) fp_common_intr(pkt, 1); return; } if (d_id == FS_NAME_SERVER || d_id == FS_FABRIC_CONTROLLER) { mutex_enter(&port->fp_mutex); port->fp_ns_login_class = FC_TRAN_CLASS(pkt->pkt_tran_flags); mutex_exit(&port->fp_mutex); fp_iodone(cmd); return; } ASSERT(acc == (la_els_logi_t *)pkt->pkt_resp); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&pwwn, (uint8_t *)&acc->nport_ww_name, sizeof (la_wwn_t), DDI_DEV_AUTOINCR); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&nwwn, (uint8_t *)&acc->node_ww_name, sizeof (la_wwn_t), DDI_DEV_AUTOINCR); ASSERT(fctl_is_wwn_zero(&pwwn) == FC_FAILURE); ASSERT(fctl_is_wwn_zero(&nwwn) == FC_FAILURE); if ((pd = pkt->pkt_pd) == NULL) { pd = fctl_get_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { FP_TRACE(FP_NHEAD2(1, 0), "fp_plogi_intr: fp %x pd %x", port->fp_port_id.port_id, d_id); pd = fctl_create_remote_port(port, &nwwn, &pwwn, d_id, PD_PLOGI_INITIATOR, KM_NOSLEEP); if (pd == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "couldn't create port device handles" " d_id=%x", d_id); fp_iodone(cmd); return; } } else { fc_remote_port_t *tmp_pd; tmp_pd = fctl_get_remote_port_by_did(port, d_id); if (tmp_pd != NULL) { fp_iodone(cmd); return; } mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if ((pd->pd_state == PORT_DEVICE_LOGGED_IN) || (pd->pd_aux_flags & PD_LOGGED_OUT)) { cmd->cmd_flags |= FP_CMD_PLOGI_RETAIN; } if (pd->pd_type == PORT_DEVICE_OLD) { if (pd->pd_port_id.port_id != d_id) { fctl_delist_did_table(port, pd); pd->pd_type = PORT_DEVICE_CHANGED; pd->pd_port_id.port_id = d_id; } else { pd->pd_type = PORT_DEVICE_NOCHANGE; } } if (pd->pd_aux_flags & PD_IN_DID_QUEUE) { char ww_name[17]; fc_wwn_to_str(&pd->pd_port_name, ww_name); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, 0), "Possible Duplicate name or address" " identifiers in the PLOGI response" " D_ID=%x, PWWN=%s: Please check the" " configuration", d_id, ww_name); fp_iodone(cmd); return; } fctl_enlist_did_table(port, pd); pd->pd_aux_flags &= ~PD_LOGGED_OUT; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); } } else { fc_remote_port_t *tmp_pd, *new_wwn_pd; tmp_pd = fctl_get_remote_port_by_did(port, d_id); new_wwn_pd = fctl_get_remote_port_by_pwwn(port, &pwwn); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if (fctl_wwn_cmp(&pd->pd_port_name, &pwwn) == 0) { FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_intr: d_id=%x," " pd_state=%x pd_type=%x", d_id, pd->pd_state, pd->pd_type); if ((pd->pd_state == PORT_DEVICE_LOGGED_IN && pd->pd_type == PORT_DEVICE_OLD) || (pd->pd_aux_flags & PD_LOGGED_OUT)) { pd->pd_type = PORT_DEVICE_NOCHANGE; } else if (pd->pd_state != PORT_DEVICE_LOGGED_IN) { pd->pd_type = PORT_DEVICE_NEW; } } else { char old_name[17]; char new_name[17]; fc_wwn_to_str(&pd->pd_port_name, old_name); fc_wwn_to_str(&pwwn, new_name); FP_TRACE(FP_NHEAD1(9, 0), "fp_plogi_intr: PWWN of a device with D_ID=%x " "changed. New PWWN = %s, OLD PWWN = %s ; tmp_pd:%p " "pd:%p new_wwn_pd:%p, cmd_ulp_pkt:%p, bailout:0x%x", d_id, new_name, old_name, tmp_pd, pd, new_wwn_pd, cmd->cmd_ulp_pkt, bailout); FP_TRACE(FP_NHEAD2(9, 0), "PWWN of a device with D_ID=%x changed." " New PWWN = %s, OLD PWWN = %s", d_id, new_name, old_name); if (cmd->cmd_ulp_pkt && !bailout) { fc_remote_node_t *rnodep; fc_portmap_t *changelist; fc_portmap_t *listptr; int len = 1; /* # entries in changelist */ fctl_delist_pwwn_table(port, pd); /* * Lets now check if there already is a pd with * this new WWN in the table. If so, we'll mark * it as invalid */ if (new_wwn_pd) { /* * There is another pd with in the pwwn * table with the same WWN that we got * in the PLOGI payload. We have to get * it out of the pwwn table, update the * pd's state (fp_fillout_old_map does * this for us) and add it to the * changelist that goes up to ULPs. * * len is length of changelist and so * increment it. */ len++; if (tmp_pd != pd) { /* * Odd case where pwwn and did * tables are out of sync but * we will handle that too. See * more comments below. * * One more device that ULPs * should know about and so len * gets incremented again. */ len++; } listptr = changelist = kmem_zalloc(len * sizeof (*changelist), KM_SLEEP); mutex_enter(&new_wwn_pd->pd_mutex); rnodep = new_wwn_pd->pd_remote_nodep; mutex_exit(&new_wwn_pd->pd_mutex); /* * Hold the fd_mutex since * fctl_copy_portmap_held expects it. * Preserve lock hierarchy by grabbing * fd_mutex before pd_mutex */ if (rnodep) { mutex_enter(&rnodep->fd_mutex); } mutex_enter(&new_wwn_pd->pd_mutex); fp_fillout_old_map_held(listptr++, new_wwn_pd, 0); mutex_exit(&new_wwn_pd->pd_mutex); if (rnodep) { mutex_exit(&rnodep->fd_mutex); } /* * Safety check : * Lets ensure that the pwwn and did * tables are in sync. Ideally, we * should not find that these two pd's * are different. */ if (tmp_pd != pd) { mutex_enter(&tmp_pd->pd_mutex); rnodep = tmp_pd->pd_remote_nodep; mutex_exit(&tmp_pd->pd_mutex); /* As above grab fd_mutex */ if (rnodep) { mutex_enter(&rnodep-> fd_mutex); } mutex_enter(&tmp_pd->pd_mutex); fp_fillout_old_map_held( listptr++, tmp_pd, 0); mutex_exit(&tmp_pd->pd_mutex); if (rnodep) { mutex_exit(&rnodep-> fd_mutex); } /* * Now add "pd" (not tmp_pd) * to fp_did_table to sync it up * with fp_pwwn_table * * pd->pd_mutex is already held * at this point */ fctl_enlist_did_table(port, pd); } } else { listptr = changelist = kmem_zalloc( sizeof (*changelist), KM_SLEEP); } ASSERT(changelist != NULL); fp_fillout_changed_map(listptr, pd, &d_id, &pwwn); fctl_enlist_pwwn_table(port, pd); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); fp_iodone(cmd); (void) fp_ulp_devc_cb(port, changelist, len, len, KM_NOSLEEP, 0); return; } } if (pd->pd_porttype.port_type == FC_NS_PORT_NL) { nl_port = 1; } if (pd->pd_aux_flags & PD_DISABLE_RELOGIN) { pd->pd_aux_flags &= ~PD_LOGGED_OUT; } mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (tmp_pd == NULL) { mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if (pd->pd_aux_flags & PD_IN_DID_QUEUE) { char ww_name[17]; fc_wwn_to_str(&pd->pd_port_name, ww_name); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, 0), "Possible Duplicate name or address" " identifiers in the PLOGI response" " D_ID=%x, PWWN=%s: Please check the" " configuration", d_id, ww_name); fp_iodone(cmd); return; } fctl_enlist_did_table(port, pd); pd->pd_aux_flags &= ~PD_LOGGED_OUT; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); } } fp_register_login(&pkt->pkt_resp_acc, pd, acc, FC_TRAN_CLASS(pkt->pkt_tran_flags)); if (cmd->cmd_ulp_pkt) { cmd->cmd_ulp_pkt->pkt_state = pkt->pkt_state; cmd->cmd_ulp_pkt->pkt_action = pkt->pkt_action; cmd->cmd_ulp_pkt->pkt_expln = pkt->pkt_expln; if (cmd->cmd_ulp_pkt->pkt_pd == NULL) { if (pd != NULL) { FP_TRACE(FP_NHEAD1(9, 0), "fp_plogi_intr;" "ulp_pkt's pd is NULL, get a pd %p", pd); mutex_enter(&pd->pd_mutex); pd->pd_ref_count++; mutex_exit(&pd->pd_mutex); } cmd->cmd_ulp_pkt->pkt_pd = pd; } bcopy((caddr_t)&pkt->pkt_resp_fhdr, (caddr_t)&cmd->cmd_ulp_pkt->pkt_resp_fhdr, sizeof (fc_frame_hdr_t)); bcopy((caddr_t)pkt->pkt_resp, (caddr_t)cmd->cmd_ulp_pkt->pkt_resp, sizeof (la_els_logi_t)); } mutex_enter(&port->fp_mutex); if (port->fp_topology == FC_TOP_PRIVATE_LOOP || nl_port) { mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_retry_count = fp_retry_count; /* * If the fc_remote_port_t pointer is not set in the given * fc_packet_t, then this fc_remote_port_t must have just * been created. Save the pointer and also increment the * fc_remote_port_t reference count. */ if (pkt->pkt_pd == NULL) { pkt->pkt_pd = pd; pd->pd_ref_count++; /* It's in use! */ } fp_adisc_init(cmd, cmd->cmd_job); pkt->pkt_cmdlen = sizeof (la_els_adisc_t); pkt->pkt_rsplen = sizeof (la_els_adisc_t); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (fp_sendcmd(port, cmd, port->fp_fca_handle) == FC_SUCCESS) { return; } } else { mutex_exit(&port->fp_mutex); } if ((cmd->cmd_flags & FP_CMD_PLOGI_RETAIN) == 0) { mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_retry_count = fp_retry_count; fp_logo_init(pd, cmd, cmd->cmd_job); pkt->pkt_cmdlen = sizeof (la_els_logo_t); pkt->pkt_rsplen = FP_PORT_IDENTIFIER_LEN; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (fp_sendcmd(port, cmd, port->fp_fca_handle) == FC_SUCCESS) { return; } } fp_iodone(cmd); } /* * Handle solicited ADISC response */ static void fp_adisc_intr(fc_packet_t *pkt) { int rval; int bailout; fp_cmd_t *cmd, *logi_cmd; fc_local_port_t *port; fc_remote_port_t *pd; la_els_adisc_t *acc; ls_code_t resp; fc_hardaddr_t ha; fc_portmap_t *changelist; int initiator, adiscfail = 0; pd = pkt->pkt_pd; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; #ifndef __lock_lint ASSERT(cmd->cmd_job && cmd->cmd_job->job_counter); #endif ASSERT(pd != NULL && port != NULL && cmd != NULL); mutex_enter(&port->fp_mutex); port->fp_out_fpcmds--; bailout = ((port->fp_statec_busy || FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) && cmd->cmd_ulp_pkt) ? 1 : 0; mutex_exit(&port->fp_mutex); if (bailout) { fp_iodone(cmd); return; } if (pkt->pkt_state == FC_PKT_SUCCESS && pkt->pkt_resp_resid == 0) { acc = (la_els_adisc_t *)pkt->pkt_resp; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)acc, sizeof (resp), DDI_DEV_AUTOINCR); if (resp.ls_code == LA_ELS_ACC) { int is_private; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&ha, (uint8_t *)&acc->hard_addr, sizeof (ha), DDI_DEV_AUTOINCR); mutex_enter(&port->fp_mutex); is_private = (port->fp_topology == FC_TOP_PRIVATE_LOOP) ? 1 : 0; mutex_enter(&pd->pd_mutex); if ((pd->pd_aux_flags & PD_IN_DID_QUEUE) == 0) { fctl_enlist_did_table(port, pd); } mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if (pd->pd_type != PORT_DEVICE_NEW) { if (is_private && (pd->pd_hard_addr.hard_addr != ha.hard_addr)) { pd->pd_type = PORT_DEVICE_CHANGED; } else { pd->pd_type = PORT_DEVICE_NOCHANGE; } } if (is_private && (ha.hard_addr && pd->pd_port_id.port_id != ha.hard_addr)) { char ww_name[17]; fc_wwn_to_str(&pd->pd_port_name, ww_name); fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "NL_Port Identifier %x doesn't match" " with Hard Address %x, Will use Port" " WWN %s", pd->pd_port_id.port_id, ha.hard_addr, ww_name); pd->pd_hard_addr.hard_addr = 0; } else { pd->pd_hard_addr.hard_addr = ha.hard_addr; } mutex_exit(&pd->pd_mutex); } else { if (fp_common_intr(pkt, 0) == FC_SUCCESS) { return; } } } else { if (fp_common_intr(pkt, 0) == FC_SUCCESS) { return; } mutex_enter(&port->fp_mutex); if (port->fp_statec_busy <= 1) { mutex_exit(&port->fp_mutex); if (pkt->pkt_state == FC_PKT_LS_RJT && pkt->pkt_reason == FC_REASON_CMD_UNABLE) { uchar_t class; int cmd_flag; uint32_t src_id; class = fp_get_nextclass(port, FC_TRAN_CLASS_INVALID); if (class == FC_TRAN_CLASS_INVALID) { fp_iodone(cmd); return; } FP_TRACE(FP_NHEAD1(1, 0), "ADISC re-login; " "fp_state=0x%x, pkt_state=0x%x, " "reason=0x%x, class=0x%x", port->fp_state, pkt->pkt_state, pkt->pkt_reason, class); cmd_flag = FP_CMD_PLOGI_RETAIN; logi_cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), sizeof (la_els_logi_t), KM_SLEEP, pd); if (logi_cmd == NULL) { fp_iodone(cmd); return; } logi_cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; logi_cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; logi_cmd->cmd_flags = cmd_flag; logi_cmd->cmd_retry_count = fp_retry_count; logi_cmd->cmd_ulp_pkt = NULL; mutex_enter(&port->fp_mutex); src_id = port->fp_port_id.port_id; mutex_exit(&port->fp_mutex); fp_xlogi_init(port, logi_cmd, src_id, pkt->pkt_cmd_fhdr.d_id, fp_plogi_intr, cmd->cmd_job, LA_ELS_PLOGI); if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); } if (fp_sendcmd(port, logi_cmd, port->fp_fca_handle) == FC_SUCCESS) { fp_free_pkt(cmd); return; } else { fp_free_pkt(logi_cmd); } } else { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, pkt, "ADISC to %x failed, cmd_flags=%x", pkt->pkt_cmd_fhdr.d_id, cmd->cmd_flags); cmd->cmd_flags &= ~FP_CMD_PLOGI_RETAIN; adiscfail = 1; } } else { mutex_exit(&port->fp_mutex); } } if (cmd->cmd_ulp_pkt) { cmd->cmd_ulp_pkt->pkt_state = pkt->pkt_state; cmd->cmd_ulp_pkt->pkt_action = pkt->pkt_action; cmd->cmd_ulp_pkt->pkt_expln = pkt->pkt_expln; if (cmd->cmd_ulp_pkt->pkt_pd == NULL) { cmd->cmd_ulp_pkt->pkt_pd = pd; FP_TRACE(FP_NHEAD1(9, 0), "fp_adisc__intr;" "ulp_pkt's pd is NULL, get a pd %p", pd); } bcopy((caddr_t)&pkt->pkt_resp_fhdr, (caddr_t)&cmd->cmd_ulp_pkt->pkt_resp_fhdr, sizeof (fc_frame_hdr_t)); bcopy((caddr_t)pkt->pkt_resp, (caddr_t)cmd->cmd_ulp_pkt->pkt_resp, sizeof (la_els_adisc_t)); } if ((cmd->cmd_flags & FP_CMD_PLOGI_RETAIN) == 0) { FP_TRACE(FP_NHEAD1(9, 0), "fp_adisc_intr: Perform LOGO.cmd_flags=%x, " "fp_retry_count=%x, ulp_pkt=%p", cmd->cmd_flags, fp_retry_count, cmd->cmd_ulp_pkt); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_retry_count = fp_retry_count; fp_logo_init(pd, cmd, cmd->cmd_job); pkt->pkt_cmdlen = sizeof (la_els_logo_t); pkt->pkt_rsplen = FP_PORT_IDENTIFIER_LEN; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (adiscfail) { mutex_enter(&pd->pd_mutex); initiator = ((pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0); pd->pd_state = PORT_DEVICE_VALID; pd->pd_aux_flags |= PD_LOGGED_OUT; if (pd->pd_aux_flags & PD_DISABLE_RELOGIN) { pd->pd_type = PORT_DEVICE_NEW; } else { pd->pd_type = PORT_DEVICE_NOCHANGE; } mutex_exit(&pd->pd_mutex); changelist = kmem_zalloc(sizeof (*changelist), KM_SLEEP); if (initiator) { fp_unregister_login(pd); fctl_copy_portmap(changelist, pd); } else { fp_fillout_old_map(changelist, pd, 0); } FP_TRACE(FP_NHEAD1(9, 0), "fp_adisc_intr: Dev change notification " "to ULP port=%p, pd=%p, map_type=%x map_state=%x " "map_flags=%x initiator=%d", port, pd, changelist->map_type, changelist->map_state, changelist->map_flags, initiator); (void) fp_ulp_devc_cb(port, changelist, 1, 1, KM_SLEEP, 0); } if (rval == FC_SUCCESS) { return; } } fp_iodone(cmd); } /* * Handle solicited LOGO response */ static void fp_logo_intr(fc_packet_t *pkt) { ls_code_t resp; fc_local_port_t *port = ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port; mutex_enter(&((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_mutex); ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_out_fpcmds--; mutex_exit(&((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_mutex); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)pkt->pkt_resp, sizeof (resp), DDI_DEV_AUTOINCR); if (FP_IS_PKT_ERROR(pkt)) { (void) fp_common_intr(pkt, 1); return; } ASSERT(resp.ls_code == LA_ELS_ACC); if (resp.ls_code != LA_ELS_ACC) { (void) fp_common_intr(pkt, 1); return; } if (pkt->pkt_pd != NULL) { fp_unregister_login(pkt->pkt_pd); } fp_iodone(pkt->pkt_ulp_private); } /* * Handle solicited RNID response */ static void fp_rnid_intr(fc_packet_t *pkt) { ls_code_t resp; job_request_t *job; fp_cmd_t *cmd; la_els_rnid_acc_t *acc; fc_local_port_t *port = ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)pkt->pkt_resp, sizeof (resp), DDI_DEV_AUTOINCR); cmd = pkt->pkt_ulp_private; mutex_enter(&cmd->cmd_port->fp_mutex); cmd->cmd_port->fp_out_fpcmds--; mutex_exit(&cmd->cmd_port->fp_mutex); job = cmd->cmd_job; ASSERT(job->job_private != NULL); /* If failure or LS_RJT then retry the packet, if needed */ if (pkt->pkt_state != FC_PKT_SUCCESS || resp.ls_code != LA_ELS_ACC) { (void) fp_common_intr(pkt, 1); return; } /* Save node_id memory allocated in ioctl code */ acc = (la_els_rnid_acc_t *)pkt->pkt_resp; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)job->job_private, (uint8_t *)acc, sizeof (la_els_rnid_acc_t), DDI_DEV_AUTOINCR); /* wakeup the ioctl thread and free the pkt */ fp_iodone(cmd); } /* * Handle solicited RLS response */ static void fp_rls_intr(fc_packet_t *pkt) { ls_code_t resp; job_request_t *job; fp_cmd_t *cmd; la_els_rls_acc_t *acc; fc_local_port_t *port = ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp, (uint8_t *)pkt->pkt_resp, sizeof (resp), DDI_DEV_AUTOINCR); cmd = pkt->pkt_ulp_private; mutex_enter(&cmd->cmd_port->fp_mutex); cmd->cmd_port->fp_out_fpcmds--; mutex_exit(&cmd->cmd_port->fp_mutex); job = cmd->cmd_job; ASSERT(job->job_private != NULL); /* If failure or LS_RJT then retry the packet, if needed */ if (FP_IS_PKT_ERROR(pkt) || resp.ls_code != LA_ELS_ACC) { (void) fp_common_intr(pkt, 1); return; } /* Save link error status block in memory allocated in ioctl code */ acc = (la_els_rls_acc_t *)pkt->pkt_resp; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)job->job_private, (uint8_t *)&acc->rls_link_params, sizeof (fc_rls_acc_t), DDI_DEV_AUTOINCR); /* wakeup the ioctl thread and free the pkt */ fp_iodone(cmd); } /* * A solicited command completion interrupt (mostly for commands * that require almost no post processing such as SCR ELS) */ static void fp_intr(fc_packet_t *pkt) { mutex_enter(&((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_mutex); ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_out_fpcmds--; mutex_exit(&((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port->fp_mutex); if (FP_IS_PKT_ERROR(pkt)) { (void) fp_common_intr(pkt, 1); return; } fp_iodone(pkt->pkt_ulp_private); } /* * Handle the underlying port's state change */ static void fp_statec_cb(opaque_t port_handle, uint32_t state) { fc_local_port_t *port = port_handle; job_request_t *job; /* * If it is not possible to process the callbacks * just drop the callback on the floor; Don't bother * to do something that isn't safe at this time */ mutex_enter(&port->fp_mutex); if ((port->fp_soft_state & (FP_SOFT_IN_DETACH | FP_SOFT_SUSPEND | FP_SOFT_POWER_DOWN)) || (FC_PORT_STATE_MASK(port->fp_state) == FC_PORT_STATE_MASK(state))) { mutex_exit(&port->fp_mutex); return; } if (port->fp_statec_busy == 0) { port->fp_soft_state |= FP_SOFT_IN_STATEC_CB; #ifdef DEBUG } else { ASSERT(port->fp_soft_state & FP_SOFT_IN_STATEC_CB); #endif } port->fp_statec_busy++; /* * For now, force the trusted method of device authentication (by * PLOGI) when LIPs do not involve OFFLINE to ONLINE transition. */ if (FC_PORT_STATE_MASK(state) == FC_STATE_LIP || FC_PORT_STATE_MASK(state) == FC_STATE_LIP_LBIT_SET) { state = FC_PORT_SPEED_MASK(port->fp_state) | FC_STATE_LOOP; fp_port_offline(port, 0); } mutex_exit(&port->fp_mutex); switch (FC_PORT_STATE_MASK(state)) { case FC_STATE_OFFLINE: job = fctl_alloc_job(JOB_PORT_OFFLINE, JOB_TYPE_FCTL_ASYNC, NULL, NULL, KM_NOSLEEP); if (job == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, " fp_statec_cb() couldn't submit a job " " to the thread: failing.."); mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); return; } mutex_enter(&port->fp_mutex); /* * Zero out this field so that we do not retain * the fabric name as its no longer valid */ bzero(&port->fp_fabric_name, sizeof (la_wwn_t)); port->fp_state = state; mutex_exit(&port->fp_mutex); fctl_enque_job(port, job); break; case FC_STATE_ONLINE: case FC_STATE_LOOP: mutex_enter(&port->fp_mutex); port->fp_state = state; if (port->fp_offline_tid) { timeout_id_t tid; tid = port->fp_offline_tid; port->fp_offline_tid = NULL; mutex_exit(&port->fp_mutex); (void) untimeout(tid); } else { mutex_exit(&port->fp_mutex); } job = fctl_alloc_job(JOB_PORT_ONLINE, JOB_TYPE_FCTL_ASYNC, NULL, NULL, KM_NOSLEEP); if (job == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp_statec_cb() couldn't submit a job " "to the thread: failing.."); mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); return; } fctl_enque_job(port, job); break; case FC_STATE_RESET_REQUESTED: mutex_enter(&port->fp_mutex); port->fp_state = FC_STATE_OFFLINE; port->fp_soft_state |= FP_SOFT_IN_FCA_RESET; mutex_exit(&port->fp_mutex); /* FALLTHROUGH */ case FC_STATE_RESET: job = fctl_alloc_job(JOB_ULP_NOTIFY, JOB_TYPE_FCTL_ASYNC, NULL, NULL, KM_NOSLEEP); if (job == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp_statec_cb() couldn't submit a job" " to the thread: failing.."); mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); return; } /* squeeze into some field in the job structure */ job->job_ulp_listlen = FC_PORT_STATE_MASK(state); fctl_enque_job(port, job); break; case FC_STATE_TARGET_PORT_RESET: (void) fp_ulp_notify(port, state, KM_NOSLEEP); /* FALLTHROUGH */ case FC_STATE_NAMESERVICE: /* FALLTHROUGH */ default: mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } mutex_exit(&port->fp_mutex); break; } } /* * Register with the Name Server for RSCNs */ static int fp_ns_scr(fc_local_port_t *port, job_request_t *job, uchar_t scr_func, int sleep) { uint32_t s_id; uchar_t class; fc_scr_req_t payload; fp_cmd_t *cmd; fc_packet_t *pkt; mutex_enter(&port->fp_mutex); s_id = port->fp_port_id.port_id; class = port->fp_ns_login_class; mutex_exit(&port->fp_mutex); cmd = fp_alloc_pkt(port, sizeof (fc_scr_req_t), sizeof (fc_scr_resp_t), sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = NULL; pkt = &cmd->cmd_pkt; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; fp_els_init(cmd, s_id, 0xFFFFFD, fp_intr, job); payload.ls_code.ls_code = LA_ELS_SCR; payload.ls_code.mbz = 0; payload.scr_rsvd = 0; payload.scr_func = scr_func; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); job->job_counter = 1; if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_iodone(cmd); } return (FC_SUCCESS); } /* * There are basically two methods to determine the total number of * devices out in the NS database; Reading the details of the two * methods described below, it shouldn't be hard to identify which * of the two methods is better. * * Method 1. * Iteratively issue GANs until all ports identifiers are walked * * Method 2. * Issue GID_PT (get port Identifiers) with Maximum residual * field in the request CT HEADER set to accommodate only the * CT HEADER in the response frame. And if FC-GS2 has been * carefully read, the NS here has a chance to FS_ACC the * request and indicate the residual size in the FS_ACC. * * Method 2 is wonderful, although it's not mandatory for the NS * to update the Maximum/Residual Field as can be seen in 4.3.1.6 * (note with particular care the use of the auxiliary verb 'may') * */ static int fp_ns_get_devcount(fc_local_port_t *port, job_request_t *job, int create, int sleep) { int flags; int rval; uint32_t src_id; fctl_ns_req_t *ns_cmd; ASSERT(!MUTEX_HELD(&port->fp_mutex)); mutex_enter(&port->fp_mutex); src_id = port->fp_port_id.port_id; mutex_exit(&port->fp_mutex); if (!create && (port->fp_options & FP_NS_SMART_COUNT)) { ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pt_t), sizeof (ns_resp_gid_pt_t), 0, (FCTL_NS_GET_DEV_COUNT | FCTL_NS_NO_DATA_BUF), sleep); if (ns_cmd == NULL) { return (FC_NOMEM); } ns_cmd->ns_cmd_code = NS_GID_PT; ((ns_req_gid_pt_t *)(ns_cmd->ns_cmd_buf))->port_type.port_type = FC_NS_PORT_NX; /* All port types */ ((ns_req_gid_pt_t *)(ns_cmd->ns_cmd_buf))->port_type.rsvd = 0; } else { uint32_t ns_flags; ns_flags = FCTL_NS_GET_DEV_COUNT | FCTL_NS_NO_DATA_BUF; if (create) { ns_flags |= FCTL_NS_CREATE_DEVICE; } ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gan_t), sizeof (ns_resp_gan_t), sizeof (int), ns_flags, sleep); if (ns_cmd == NULL) { return (FC_NOMEM); } ns_cmd->ns_gan_index = 0; ns_cmd->ns_gan_sid = FCTL_GAN_START_ID; ns_cmd->ns_cmd_code = NS_GA_NXT; ns_cmd->ns_gan_max = 0xFFFF; ((ns_req_gan_t *)(ns_cmd->ns_cmd_buf))->pid.port_id = src_id; ((ns_req_gan_t *)(ns_cmd->ns_cmd_buf))->pid.priv_lilp_posit = 0; } flags = job->job_flags; job->job_flags &= ~JOB_TYPE_FP_ASYNC; job->job_counter = 1; rval = fp_ns_query(port, ns_cmd, job, 1, sleep); job->job_flags = flags; if (!create && (port->fp_options & FP_NS_SMART_COUNT)) { uint16_t max_resid; /* * Revert to scanning the NS if NS_GID_PT isn't * helping us figure out total number of devices. */ if (job->job_result != FC_SUCCESS || ns_cmd->ns_resp_hdr.ct_cmdrsp != FS_ACC_IU) { mutex_enter(&port->fp_mutex); port->fp_options &= ~FP_NS_SMART_COUNT; mutex_exit(&port->fp_mutex); fctl_free_ns_cmd(ns_cmd); return (fp_ns_get_devcount(port, job, create, sleep)); } mutex_enter(&port->fp_mutex); port->fp_total_devices = 1; max_resid = ns_cmd->ns_resp_hdr.ct_aiusize; if (max_resid) { /* * Since port identifier is 4 bytes and max_resid * is also in WORDS, max_resid simply indicates * the total number of port identifiers not * transferred */ port->fp_total_devices += max_resid; } mutex_exit(&port->fp_mutex); } mutex_enter(&port->fp_mutex); port->fp_total_devices = *((int *)ns_cmd->ns_data_buf); mutex_exit(&port->fp_mutex); fctl_free_ns_cmd(ns_cmd); return (rval); } /* * One heck of a function to serve userland. */ static int fp_fciocmd(fc_local_port_t *port, intptr_t data, int mode, fcio_t *fcio) { int rval = 0; int jcode; uint32_t ret; uchar_t open_flag; fcio_t *kfcio; job_request_t *job; boolean_t use32 = B_FALSE; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: use32 = B_TRUE; break; case DDI_MODEL_NONE: default: break; } #endif mutex_enter(&port->fp_mutex); if (port->fp_soft_state & (FP_SOFT_IN_STATEC_CB | FP_SOFT_IN_UNSOL_CB)) { fcio->fcio_errno = FC_STATEC_BUSY; mutex_exit(&port->fp_mutex); rval = EAGAIN; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } return (rval); } open_flag = port->fp_flag; mutex_exit(&port->fp_mutex); if (fp_check_perms(open_flag, fcio->fcio_cmd) != FC_SUCCESS) { fcio->fcio_errno = FC_FAILURE; rval = EACCES; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } return (rval); } /* * If an exclusive open was demanded during open, don't let * either innocuous or devil threads to share the file * descriptor and fire down exclusive access commands */ mutex_enter(&port->fp_mutex); if (port->fp_flag & FP_EXCL) { if (port->fp_flag & FP_EXCL_BUSY) { mutex_exit(&port->fp_mutex); fcio->fcio_errno = FC_FAILURE; return (EBUSY); } port->fp_flag |= FP_EXCL_BUSY; } mutex_exit(&port->fp_mutex); fcio->fcio_errno = FC_SUCCESS; switch (fcio->fcio_cmd) { case FCIO_GET_HOST_PARAMS: { fc_port_dev_t *val; fc_port_dev32_t *val32; int index; int lilp_device_count; fc_lilpmap_t *lilp_map; uchar_t *alpa_list; if (use32 == B_TRUE) { if (fcio->fcio_olen != sizeof (*val32) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } else { if (fcio->fcio_olen != sizeof (*val) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } val = kmem_zalloc(sizeof (*val), KM_SLEEP); mutex_enter(&port->fp_mutex); val->dev_did = port->fp_port_id; val->dev_hard_addr = port->fp_hard_addr; val->dev_pwwn = port->fp_service_params.nport_ww_name; val->dev_nwwn = port->fp_service_params.node_ww_name; val->dev_state = port->fp_state; lilp_map = &port->fp_lilp_map; alpa_list = &lilp_map->lilp_alpalist[0]; lilp_device_count = lilp_map->lilp_length; for (index = 0; index < lilp_device_count; index++) { uint32_t d_id; d_id = alpa_list[index]; if (d_id == port->fp_port_id.port_id) { break; } } val->dev_did.priv_lilp_posit = (uint8_t)(index & 0xff); bcopy(port->fp_fc4_types, val->dev_type, sizeof (port->fp_fc4_types)); mutex_exit(&port->fp_mutex); if (use32 == B_TRUE) { val32 = kmem_zalloc(sizeof (*val32), KM_SLEEP); val32->dev_did = val->dev_did; val32->dev_hard_addr = val->dev_hard_addr; val32->dev_pwwn = val->dev_pwwn; val32->dev_nwwn = val->dev_nwwn; val32->dev_state = val->dev_state; val32->dev_did.priv_lilp_posit = val->dev_did.priv_lilp_posit; bcopy(val->dev_type, val32->dev_type, sizeof (port->fp_fc4_types)); if (fp_copyout((void *)val32, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val32, sizeof (*val32)); } else { if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } /* need to free "val" here */ kmem_free(val, sizeof (*val)); break; } case FCIO_GET_OTHER_ADAPTER_PORTS: { uint32_t index; char *tmpPath; fc_local_port_t *tmpPort; if (fcio->fcio_olen < MAXPATHLEN || fcio->fcio_ilen != sizeof (uint32_t)) { rval = EINVAL; break; } if (ddi_copyin(fcio->fcio_ibuf, &index, sizeof (index), mode)) { rval = EFAULT; break; } tmpPort = fctl_get_adapter_port_by_index(port, index); if (tmpPort == NULL) { FP_TRACE(FP_NHEAD1(9, 0), "User supplied index out of range"); fcio->fcio_errno = FC_BADPORT; rval = EFAULT; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } tmpPath = kmem_zalloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(tmpPort->fp_port_dip, tmpPath); if (fp_copyout((void *)tmpPath, (void *)fcio->fcio_obuf, MAXPATHLEN, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(tmpPath, MAXPATHLEN); break; } case FCIO_NPIV_GET_ADAPTER_ATTRIBUTES: case FCIO_GET_ADAPTER_ATTRIBUTES: { fc_hba_adapter_attributes_t *val; fc_hba_adapter_attributes32_t *val32; if (use32 == B_TRUE) { if (fcio->fcio_olen < sizeof (*val32) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } else { if (fcio->fcio_olen < sizeof (*val) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } val = kmem_zalloc(sizeof (*val), KM_SLEEP); val->version = FC_HBA_ADAPTER_ATTRIBUTES_VERSION; mutex_enter(&port->fp_mutex); bcopy(port->fp_hba_port_attrs.manufacturer, val->Manufacturer, sizeof (val->Manufacturer)); bcopy(port->fp_hba_port_attrs.serial_number, val->SerialNumber, sizeof (val->SerialNumber)); bcopy(port->fp_hba_port_attrs.model, val->Model, sizeof (val->Model)); bcopy(port->fp_hba_port_attrs.model_description, val->ModelDescription, sizeof (val->ModelDescription)); bcopy(port->fp_sym_node_name, val->NodeSymbolicName, port->fp_sym_node_namelen); bcopy(port->fp_hba_port_attrs.hardware_version, val->HardwareVersion, sizeof (val->HardwareVersion)); bcopy(port->fp_hba_port_attrs.option_rom_version, val->OptionROMVersion, sizeof (val->OptionROMVersion)); bcopy(port->fp_hba_port_attrs.firmware_version, val->FirmwareVersion, sizeof (val->FirmwareVersion)); val->VendorSpecificID = port->fp_hba_port_attrs.vendor_specific_id; bcopy(&port->fp_service_params.node_ww_name.raw_wwn, &val->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); bcopy(port->fp_hba_port_attrs.driver_name, val->DriverName, sizeof (val->DriverName)); bcopy(port->fp_hba_port_attrs.driver_version, val->DriverVersion, sizeof (val->DriverVersion)); mutex_exit(&port->fp_mutex); if (fcio->fcio_cmd == FCIO_GET_ADAPTER_ATTRIBUTES) { val->NumberOfPorts = fctl_count_fru_ports(port, 0); } else { val->NumberOfPorts = fctl_count_fru_ports(port, 1); } if (use32 == B_TRUE) { val32 = kmem_zalloc(sizeof (*val32), KM_SLEEP); val32->version = val->version; bcopy(val->Manufacturer, val32->Manufacturer, sizeof (val->Manufacturer)); bcopy(val->SerialNumber, val32->SerialNumber, sizeof (val->SerialNumber)); bcopy(val->Model, val32->Model, sizeof (val->Model)); bcopy(val->ModelDescription, val32->ModelDescription, sizeof (val->ModelDescription)); bcopy(val->NodeSymbolicName, val32->NodeSymbolicName, sizeof (val->NodeSymbolicName)); bcopy(val->HardwareVersion, val32->HardwareVersion, sizeof (val->HardwareVersion)); bcopy(val->OptionROMVersion, val32->OptionROMVersion, sizeof (val->OptionROMVersion)); bcopy(val->FirmwareVersion, val32->FirmwareVersion, sizeof (val->FirmwareVersion)); val32->VendorSpecificID = val->VendorSpecificID; bcopy(&val->NodeWWN.raw_wwn, &val32->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); bcopy(val->DriverName, val32->DriverName, sizeof (val->DriverName)); bcopy(val->DriverVersion, val32->DriverVersion, sizeof (val->DriverVersion)); val32->NumberOfPorts = val->NumberOfPorts; if (fp_copyout((void *)val32, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val32, sizeof (*val32)); } else { if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } kmem_free(val, sizeof (*val)); break; } case FCIO_GET_NPIV_ATTRIBUTES: { fc_hba_npiv_attributes_t *attrs; attrs = kmem_zalloc(sizeof (*attrs), KM_SLEEP); mutex_enter(&port->fp_mutex); bcopy(&port->fp_service_params.node_ww_name.raw_wwn, &attrs->NodeWWN.raw_wwn, sizeof (attrs->NodeWWN.raw_wwn)); bcopy(&port->fp_service_params.nport_ww_name.raw_wwn, &attrs->PortWWN.raw_wwn, sizeof (attrs->PortWWN.raw_wwn)); mutex_exit(&port->fp_mutex); if (fp_copyout((void *)attrs, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(attrs, sizeof (*attrs)); break; } case FCIO_DELETE_NPIV_PORT: { fc_local_port_t *tmpport; char ww_pname[17]; la_wwn_t vwwn[1]; FP_TRACE(FP_NHEAD1(1, 0), "Delete NPIV Port"); if (ddi_copyin(fcio->fcio_ibuf, &vwwn, sizeof (la_wwn_t), mode)) { rval = EFAULT; break; } fc_wwn_to_str(&vwwn[0], ww_pname); FP_TRACE(FP_NHEAD1(3, 0), "Delete NPIV Port %s", ww_pname); tmpport = fc_delete_npiv_port(port, &vwwn[0]); if (tmpport == NULL) { FP_TRACE(FP_NHEAD1(3, 0), "Delete NPIV Port : no found"); rval = EFAULT; } else { fc_local_port_t *nextport = tmpport->fp_port_next; fc_local_port_t *prevport = tmpport->fp_port_prev; int portlen, portindex, ret; portlen = sizeof (portindex); ret = ddi_prop_op(DDI_DEV_T_ANY, tmpport->fp_port_dip, PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "port", (caddr_t)&portindex, &portlen); if (ret != DDI_SUCCESS) { rval = EFAULT; break; } if (ndi_devi_offline(tmpport->fp_port_dip, NDI_DEVI_REMOVE) != DDI_SUCCESS) { FP_TRACE(FP_NHEAD1(1, 0), "Delete NPIV Port failed"); mutex_enter(&port->fp_mutex); tmpport->fp_npiv_state = 0; mutex_exit(&port->fp_mutex); rval = EFAULT; } else { mutex_enter(&port->fp_mutex); nextport->fp_port_prev = prevport; prevport->fp_port_next = nextport; if (port == port->fp_port_next) { port->fp_port_next = port->fp_port_prev = NULL; } port->fp_npiv_portnum--; FP_TRACE(FP_NHEAD1(3, 0), "Delete NPIV Port %d", portindex); port->fp_npiv_portindex[portindex-1] = 0; mutex_exit(&port->fp_mutex); } } break; } case FCIO_CREATE_NPIV_PORT: { char ww_nname[17], ww_pname[17]; la_npiv_create_entry_t entrybuf; uint32_t vportindex = 0; int npiv_ret = 0; char *portname, *fcaname; portname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(port->fp_port_dip, portname); fcaname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(port->fp_fca_dip, fcaname); FP_TRACE(FP_NHEAD1(1, 0), "Create NPIV port %s %s %s", portname, fcaname, ddi_driver_name(port->fp_fca_dip)); kmem_free(portname, MAXPATHLEN); kmem_free(fcaname, MAXPATHLEN); if (ddi_copyin(fcio->fcio_ibuf, &entrybuf, sizeof (la_npiv_create_entry_t), mode)) { rval = EFAULT; break; } fc_wwn_to_str(&entrybuf.VNodeWWN, ww_nname); fc_wwn_to_str(&entrybuf.VPortWWN, ww_pname); vportindex = entrybuf.vindex; FP_TRACE(FP_NHEAD1(3, 0), "Create NPIV Port %s %s %d", ww_nname, ww_pname, vportindex); if (fc_get_npiv_port(port, &entrybuf.VPortWWN)) { rval = EFAULT; break; } npiv_ret = fctl_fca_create_npivport(port->fp_fca_dip, port->fp_port_dip, ww_nname, ww_pname, &vportindex); if (npiv_ret == NDI_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_npiv_portnum++; mutex_exit(&port->fp_mutex); if (fp_copyout((void *)&vportindex, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { rval = EFAULT; } FP_TRACE(FP_NHEAD1(3, 0), "Create NPIV Port %d %d", npiv_ret, vportindex); break; } case FCIO_GET_NPIV_PORT_LIST: { fc_hba_npiv_port_list_t *list; int count; if ((fcio->fcio_xfer != FCIO_XFER_READ) || (fcio->fcio_olen == 0) || (fcio->fcio_obuf == 0)) { rval = EINVAL; break; } list = kmem_zalloc(fcio->fcio_olen, KM_SLEEP); list->version = FC_HBA_LIST_VERSION; count = (fcio->fcio_olen - (int)sizeof (fc_hba_npiv_port_list_t))/MAXPATHLEN + 1; if (port->fp_npiv_portnum > count) { list->numAdapters = port->fp_npiv_portnum; } else { /* build npiv port list */ count = fc_ulp_get_npiv_port_list(port, (char *)list->hbaPaths); if (count < 0) { rval = ENXIO; FP_TRACE(FP_NHEAD1(1, 0), "Build NPIV Port List error"); kmem_free(list, fcio->fcio_olen); break; } list->numAdapters = count; } if (fp_copyout((void *)list, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { FP_TRACE(FP_NHEAD1(1, 0), "Copy NPIV Port data error"); rval = EFAULT; } } else { FP_TRACE(FP_NHEAD1(1, 0), "Copy NPIV Port List error"); rval = EFAULT; } kmem_free(list, fcio->fcio_olen); break; } case FCIO_GET_ADAPTER_PORT_NPIV_ATTRIBUTES: { fc_hba_port_npiv_attributes_t *val; val = kmem_zalloc(sizeof (*val), KM_SLEEP); val->version = FC_HBA_PORT_NPIV_ATTRIBUTES_VERSION; mutex_enter(&port->fp_mutex); val->npivflag = port->fp_npiv_flag; val->lastChange = port->fp_last_change; bcopy(&port->fp_service_params.nport_ww_name.raw_wwn, &val->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&port->fp_service_params.node_ww_name.raw_wwn, &val->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); mutex_exit(&port->fp_mutex); val->NumberOfNPIVPorts = fc_ulp_get_npiv_port_num(port); if (port->fp_npiv_type != FC_NPIV_PORT) { val->MaxNumberOfNPIVPorts = port->fp_fca_tran->fca_num_npivports; } else { val->MaxNumberOfNPIVPorts = 0; } if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val, sizeof (*val)); break; } case FCIO_GET_ADAPTER_PORT_ATTRIBUTES: { fc_hba_port_attributes_t *val; fc_hba_port_attributes32_t *val32; if (use32 == B_TRUE) { if (fcio->fcio_olen < sizeof (*val32) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } else { if (fcio->fcio_olen < sizeof (*val) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } val = kmem_zalloc(sizeof (*val), KM_SLEEP); val->version = FC_HBA_PORT_ATTRIBUTES_VERSION; mutex_enter(&port->fp_mutex); val->lastChange = port->fp_last_change; val->fp_minor = port->fp_instance; bcopy(&port->fp_service_params.nport_ww_name.raw_wwn, &val->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&port->fp_service_params.node_ww_name.raw_wwn, &val->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); bcopy(&port->fp_fabric_name, &val->FabricName.raw_wwn, sizeof (val->FabricName.raw_wwn)); val->PortFcId = port->fp_port_id.port_id; switch (FC_PORT_STATE_MASK(port->fp_state)) { case FC_STATE_OFFLINE: val->PortState = FC_HBA_PORTSTATE_OFFLINE; break; case FC_STATE_ONLINE: case FC_STATE_LOOP: case FC_STATE_NAMESERVICE: val->PortState = FC_HBA_PORTSTATE_ONLINE; break; default: val->PortState = FC_HBA_PORTSTATE_UNKNOWN; break; } /* Translate from LV to FC-HBA port type codes */ switch (port->fp_port_type.port_type) { case FC_NS_PORT_N: val->PortType = FC_HBA_PORTTYPE_NPORT; break; case FC_NS_PORT_NL: /* Actually means loop for us */ val->PortType = FC_HBA_PORTTYPE_LPORT; break; case FC_NS_PORT_F: val->PortType = FC_HBA_PORTTYPE_FPORT; break; case FC_NS_PORT_FL: val->PortType = FC_HBA_PORTTYPE_FLPORT; break; case FC_NS_PORT_E: val->PortType = FC_HBA_PORTTYPE_EPORT; break; default: val->PortType = FC_HBA_PORTTYPE_OTHER; break; } /* * If fp has decided that the topology is public loop, * we will indicate that using the appropriate * FC HBA API constant. */ switch (port->fp_topology) { case FC_TOP_PUBLIC_LOOP: val->PortType = FC_HBA_PORTTYPE_NLPORT; break; case FC_TOP_PT_PT: val->PortType = FC_HBA_PORTTYPE_PTP; break; case FC_TOP_UNKNOWN: /* * This should cover the case where nothing is connected * to the port. Crystal+ is p'bly an exception here. * For Crystal+, port 0 will come up as private loop * (i.e fp_bind_state will be FC_STATE_LOOP) even when * nothing is connected to it. * Current plan is to let userland handle this. */ if (port->fp_bind_state == FC_STATE_OFFLINE) { val->PortType = FC_HBA_PORTTYPE_UNKNOWN; } break; default: /* * Do Nothing. * Unused: * val->PortType = FC_HBA_PORTTYPE_GPORT; */ break; } val->PortSupportedClassofService = port->fp_hba_port_attrs.supported_cos; val->PortSupportedFc4Types[0] = 0; bcopy(port->fp_fc4_types, val->PortActiveFc4Types, sizeof (val->PortActiveFc4Types)); bcopy(port->fp_sym_port_name, val->PortSymbolicName, port->fp_sym_port_namelen); val->PortSupportedSpeed = port->fp_hba_port_attrs.supported_speed; switch (FC_PORT_SPEED_MASK(port->fp_state)) { case FC_STATE_1GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_1GBIT; break; case FC_STATE_2GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_2GBIT; break; case FC_STATE_4GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_4GBIT; break; case FC_STATE_8GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_8GBIT; break; case FC_STATE_10GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_10GBIT; break; case FC_STATE_16GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_16GBIT; break; case FC_STATE_32GBIT_SPEED: val->PortSpeed = FC_HBA_PORTSPEED_32GBIT; break; default: val->PortSpeed = FC_HBA_PORTSPEED_UNKNOWN; break; } val->PortMaxFrameSize = port->fp_hba_port_attrs.max_frame_size; val->NumberofDiscoveredPorts = port->fp_dev_count; mutex_exit(&port->fp_mutex); if (use32 == B_TRUE) { val32 = kmem_zalloc(sizeof (*val32), KM_SLEEP); val32->version = val->version; val32->lastChange = val->lastChange; val32->fp_minor = val->fp_minor; bcopy(&val->PortWWN.raw_wwn, &val32->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&val->NodeWWN.raw_wwn, &val32->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); val32->PortFcId = val->PortFcId; val32->PortState = val->PortState; val32->PortType = val->PortType; val32->PortSupportedClassofService = val->PortSupportedClassofService; bcopy(val->PortActiveFc4Types, val32->PortActiveFc4Types, sizeof (val->PortActiveFc4Types)); bcopy(val->PortSymbolicName, val32->PortSymbolicName, sizeof (val->PortSymbolicName)); bcopy(&val->FabricName, &val32->FabricName, sizeof (val->FabricName.raw_wwn)); val32->PortSupportedSpeed = val->PortSupportedSpeed; val32->PortSpeed = val->PortSpeed; val32->PortMaxFrameSize = val->PortMaxFrameSize; val32->NumberofDiscoveredPorts = val->NumberofDiscoveredPorts; if (fp_copyout((void *)val32, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val32, sizeof (*val32)); } else { if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } kmem_free(val, sizeof (*val)); break; } case FCIO_GET_DISCOVERED_PORT_ATTRIBUTES: { fc_hba_port_attributes_t *val; fc_hba_port_attributes32_t *val32; uint32_t index = 0; fc_remote_port_t *tmp_pd; if (use32 == B_TRUE) { if (fcio->fcio_olen < sizeof (*val32) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } else { if (fcio->fcio_olen < sizeof (*val) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } if (ddi_copyin(fcio->fcio_ibuf, &index, sizeof (index), mode)) { rval = EFAULT; break; } if (index >= port->fp_dev_count) { FP_TRACE(FP_NHEAD1(9, 0), "User supplied index out of range"); fcio->fcio_errno = FC_OUTOFBOUNDS; rval = EINVAL; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } val = kmem_zalloc(sizeof (*val), KM_SLEEP); val->version = FC_HBA_PORT_ATTRIBUTES_VERSION; mutex_enter(&port->fp_mutex); tmp_pd = fctl_lookup_pd_by_index(port, index); if (tmp_pd == NULL) { fcio->fcio_errno = FC_BADPORT; rval = EINVAL; } else { val->lastChange = port->fp_last_change; val->fp_minor = port->fp_instance; mutex_enter(&tmp_pd->pd_mutex); bcopy(&tmp_pd->pd_port_name.raw_wwn, &val->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&tmp_pd->pd_remote_nodep->fd_node_name.raw_wwn, &val->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); val->PortFcId = tmp_pd->pd_port_id.port_id; bcopy(tmp_pd->pd_spn, val->PortSymbolicName, tmp_pd->pd_spn_len); val->PortSupportedClassofService = tmp_pd->pd_cos; /* * we will assume the sizeof these pd_fc4types and * portActiveFc4Types will remain the same. we could * add in a check for it, but we decided it was unneeded */ bcopy((caddr_t)tmp_pd->pd_fc4types, val->PortActiveFc4Types, sizeof (tmp_pd->pd_fc4types)); val->PortState = fp_map_remote_port_state(tmp_pd->pd_state); mutex_exit(&tmp_pd->pd_mutex); val->PortType = FC_HBA_PORTTYPE_UNKNOWN; val->PortSupportedFc4Types[0] = 0; val->PortSupportedSpeed = FC_HBA_PORTSPEED_UNKNOWN; val->PortSpeed = FC_HBA_PORTSPEED_UNKNOWN; val->PortMaxFrameSize = 0; val->NumberofDiscoveredPorts = 0; if (use32 == B_TRUE) { val32 = kmem_zalloc(sizeof (*val32), KM_SLEEP); val32->version = val->version; val32->lastChange = val->lastChange; val32->fp_minor = val->fp_minor; bcopy(&val->PortWWN.raw_wwn, &val32->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&val->NodeWWN.raw_wwn, &val32->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); val32->PortFcId = val->PortFcId; bcopy(val->PortSymbolicName, val32->PortSymbolicName, sizeof (val->PortSymbolicName)); val32->PortSupportedClassofService = val->PortSupportedClassofService; bcopy(val->PortActiveFc4Types, val32->PortActiveFc4Types, sizeof (tmp_pd->pd_fc4types)); val32->PortType = val->PortType; val32->PortState = val->PortState; val32->PortSupportedFc4Types[0] = val->PortSupportedFc4Types[0]; val32->PortSupportedSpeed = val->PortSupportedSpeed; val32->PortSpeed = val->PortSpeed; val32->PortMaxFrameSize = val->PortMaxFrameSize; val32->NumberofDiscoveredPorts = val->NumberofDiscoveredPorts; if (fp_copyout((void *)val32, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val32, sizeof (*val32)); } else { if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } } mutex_exit(&port->fp_mutex); kmem_free(val, sizeof (*val)); break; } case FCIO_GET_PORT_ATTRIBUTES: { fc_hba_port_attributes_t *val; fc_hba_port_attributes32_t *val32; la_wwn_t wwn; fc_remote_port_t *tmp_pd; if (use32 == B_TRUE) { if (fcio->fcio_olen < sizeof (*val32) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } else { if (fcio->fcio_olen < sizeof (*val) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } } if (ddi_copyin(fcio->fcio_ibuf, &wwn, sizeof (wwn), mode)) { rval = EFAULT; break; } val = kmem_zalloc(sizeof (*val), KM_SLEEP); val->version = FC_HBA_PORT_ATTRIBUTES_VERSION; mutex_enter(&port->fp_mutex); tmp_pd = fctl_lookup_pd_by_wwn(port, wwn); val->lastChange = port->fp_last_change; val->fp_minor = port->fp_instance; mutex_exit(&port->fp_mutex); if (tmp_pd == NULL) { fcio->fcio_errno = FC_BADWWN; rval = EINVAL; } else { mutex_enter(&tmp_pd->pd_mutex); bcopy(&tmp_pd->pd_port_name.raw_wwn, &val->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&tmp_pd->pd_remote_nodep->fd_node_name.raw_wwn, &val->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); val->PortFcId = tmp_pd->pd_port_id.port_id; bcopy(tmp_pd->pd_spn, val->PortSymbolicName, tmp_pd->pd_spn_len); val->PortSupportedClassofService = tmp_pd->pd_cos; val->PortType = FC_HBA_PORTTYPE_UNKNOWN; val->PortState = fp_map_remote_port_state(tmp_pd->pd_state); val->PortSupportedFc4Types[0] = 0; /* * we will assume the sizeof these pd_fc4types and * portActiveFc4Types will remain the same. we could * add in a check for it, but we decided it was unneeded */ bcopy((caddr_t)tmp_pd->pd_fc4types, val->PortActiveFc4Types, sizeof (tmp_pd->pd_fc4types)); val->PortSupportedSpeed = FC_HBA_PORTSPEED_UNKNOWN; val->PortSpeed = FC_HBA_PORTSPEED_UNKNOWN; val->PortMaxFrameSize = 0; val->NumberofDiscoveredPorts = 0; mutex_exit(&tmp_pd->pd_mutex); if (use32 == B_TRUE) { val32 = kmem_zalloc(sizeof (*val32), KM_SLEEP); val32->version = val->version; val32->lastChange = val->lastChange; val32->fp_minor = val->fp_minor; bcopy(&val->PortWWN.raw_wwn, &val32->PortWWN.raw_wwn, sizeof (val->PortWWN.raw_wwn)); bcopy(&val->NodeWWN.raw_wwn, &val32->NodeWWN.raw_wwn, sizeof (val->NodeWWN.raw_wwn)); val32->PortFcId = val->PortFcId; bcopy(val->PortSymbolicName, val32->PortSymbolicName, sizeof (val->PortSymbolicName)); val32->PortSupportedClassofService = val->PortSupportedClassofService; val32->PortType = val->PortType; val32->PortState = val->PortState; val32->PortSupportedFc4Types[0] = val->PortSupportedFc4Types[0]; bcopy(val->PortActiveFc4Types, val32->PortActiveFc4Types, sizeof (tmp_pd->pd_fc4types)); val32->PortSupportedSpeed = val->PortSupportedSpeed; val32->PortSpeed = val->PortSpeed; val32->PortMaxFrameSize = val->PortMaxFrameSize; val32->NumberofDiscoveredPorts = val->NumberofDiscoveredPorts; if (fp_copyout((void *)val32, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(val32, sizeof (*val32)); } else { if (fp_copyout((void *)val, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } } kmem_free(val, sizeof (*val)); break; } case FCIO_GET_NUM_DEVS: { int num_devices; if (fcio->fcio_olen != sizeof (num_devices) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } mutex_enter(&port->fp_mutex); switch (port->fp_topology) { case FC_TOP_PRIVATE_LOOP: case FC_TOP_PT_PT: num_devices = port->fp_total_devices; fcio->fcio_errno = FC_SUCCESS; break; case FC_TOP_PUBLIC_LOOP: case FC_TOP_FABRIC: mutex_exit(&port->fp_mutex); job = fctl_alloc_job(JOB_NS_CMD, 0, NULL, NULL, KM_SLEEP); ASSERT(job != NULL); /* * In FC-GS-2 the Name Server doesn't send out * RSCNs for any Name Server Database updates * When it is finally fixed there is no need * to probe as below and should be removed. */ (void) fp_ns_get_devcount(port, job, 0, KM_SLEEP); fctl_dealloc_job(job); mutex_enter(&port->fp_mutex); num_devices = port->fp_total_devices; fcio->fcio_errno = FC_SUCCESS; break; case FC_TOP_NO_NS: /* FALLTHROUGH */ case FC_TOP_UNKNOWN: /* FALLTHROUGH */ default: num_devices = 0; fcio->fcio_errno = FC_SUCCESS; break; } mutex_exit(&port->fp_mutex); if (fp_copyout((void *)&num_devices, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } break; } case FCIO_GET_DEV_LIST: { int num_devices; int new_count; int map_size; if (fcio->fcio_xfer != FCIO_XFER_READ || fcio->fcio_alen != sizeof (new_count)) { rval = EINVAL; break; } num_devices = fcio->fcio_olen / sizeof (fc_port_dev_t); mutex_enter(&port->fp_mutex); if (num_devices < port->fp_total_devices) { fcio->fcio_errno = FC_TOOMANY; new_count = port->fp_total_devices; mutex_exit(&port->fp_mutex); if (fp_copyout((void *)&new_count, (void *)fcio->fcio_abuf, sizeof (new_count), mode)) { rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; break; } rval = EINVAL; break; } if (port->fp_total_devices <= 0) { fcio->fcio_errno = FC_NO_MAP; new_count = port->fp_total_devices; mutex_exit(&port->fp_mutex); if (fp_copyout((void *)&new_count, (void *)fcio->fcio_abuf, sizeof (new_count), mode)) { rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; break; } rval = EINVAL; break; } switch (port->fp_topology) { case FC_TOP_PRIVATE_LOOP: if (fp_fillout_loopmap(port, fcio, mode) != FC_SUCCESS) { rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; case FC_TOP_PT_PT: if (fp_fillout_p2pmap(port, fcio, mode) != FC_SUCCESS) { rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; case FC_TOP_PUBLIC_LOOP: case FC_TOP_FABRIC: { fctl_ns_req_t *ns_cmd; map_size = sizeof (fc_port_dev_t) * port->fp_total_devices; mutex_exit(&port->fp_mutex); ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gan_t), sizeof (ns_resp_gan_t), map_size, (FCTL_NS_FILL_NS_MAP | FCTL_NS_BUF_IS_USERLAND), KM_SLEEP); ASSERT(ns_cmd != NULL); ns_cmd->ns_gan_index = 0; ns_cmd->ns_gan_sid = FCTL_GAN_START_ID; ns_cmd->ns_cmd_code = NS_GA_NXT; ns_cmd->ns_gan_max = map_size / sizeof (fc_port_dev_t); job = fctl_alloc_job(JOB_PORT_GETMAP, 0, NULL, NULL, KM_SLEEP); ASSERT(job != NULL); ret = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (ret != FC_SUCCESS || job->job_result != FC_SUCCESS) { fctl_free_ns_cmd(ns_cmd); fcio->fcio_errno = job->job_result; new_count = 0; if (fp_copyout((void *)&new_count, (void *)fcio->fcio_abuf, sizeof (new_count), mode)) { fctl_dealloc_job(job); mutex_enter(&port->fp_mutex); rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { fctl_dealloc_job(job); mutex_enter(&port->fp_mutex); rval = EFAULT; break; } rval = EIO; mutex_enter(&port->fp_mutex); break; } fctl_dealloc_job(job); new_count = ns_cmd->ns_gan_index; if (fp_copyout((void *)&new_count, (void *)fcio->fcio_abuf, sizeof (new_count), mode)) { rval = EFAULT; fctl_free_ns_cmd(ns_cmd); mutex_enter(&port->fp_mutex); break; } if (fp_copyout((void *)ns_cmd->ns_data_buf, (void *)fcio->fcio_obuf, sizeof (fc_port_dev_t) * ns_cmd->ns_gan_index, mode)) { rval = EFAULT; fctl_free_ns_cmd(ns_cmd); mutex_enter(&port->fp_mutex); break; } fctl_free_ns_cmd(ns_cmd); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } mutex_enter(&port->fp_mutex); break; } case FC_TOP_NO_NS: /* FALLTHROUGH */ case FC_TOP_UNKNOWN: /* FALLTHROUGH */ default: fcio->fcio_errno = FC_NO_MAP; num_devices = port->fp_total_devices; if (fp_copyout((void *)&new_count, (void *)fcio->fcio_abuf, sizeof (new_count), mode)) { rval = EFAULT; break; } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; break; } rval = EINVAL; break; } mutex_exit(&port->fp_mutex); break; } case FCIO_GET_SYM_PNAME: { rval = ENOTSUP; break; } case FCIO_GET_SYM_NNAME: { rval = ENOTSUP; break; } case FCIO_SET_SYM_PNAME: { rval = ENOTSUP; break; } case FCIO_SET_SYM_NNAME: { rval = ENOTSUP; break; } case FCIO_GET_LOGI_PARAMS: { la_wwn_t pwwn; la_wwn_t *my_pwwn; la_els_logi_t *params; la_els_logi32_t *params32; fc_remote_node_t *node; fc_remote_port_t *pd; if (fcio->fcio_ilen != sizeof (la_wwn_t) || (fcio->fcio_xfer & FCIO_XFER_READ) == 0 || (fcio->fcio_xfer & FCIO_XFER_WRITE) == 0) { rval = EINVAL; break; } if (use32 == B_TRUE) { if (fcio->fcio_olen != sizeof (la_els_logi32_t)) { rval = EINVAL; break; } } else { if (fcio->fcio_olen != sizeof (la_els_logi_t)) { rval = EINVAL; break; } } if (ddi_copyin(fcio->fcio_ibuf, &pwwn, sizeof (pwwn), mode)) { rval = EFAULT; break; } pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { mutex_enter(&port->fp_mutex); my_pwwn = &port->fp_service_params.nport_ww_name; mutex_exit(&port->fp_mutex); if (fctl_wwn_cmp(&pwwn, my_pwwn) != 0) { rval = ENXIO; break; } params = kmem_zalloc(sizeof (*params), KM_SLEEP); mutex_enter(&port->fp_mutex); *params = port->fp_service_params; mutex_exit(&port->fp_mutex); } else { params = kmem_zalloc(sizeof (*params), KM_SLEEP); mutex_enter(&pd->pd_mutex); params->ls_code.mbz = params->ls_code.ls_code = 0; params->common_service = pd->pd_csp; params->nport_ww_name = pd->pd_port_name; params->class_1 = pd->pd_clsp1; params->class_2 = pd->pd_clsp2; params->class_3 = pd->pd_clsp3; node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); bzero(params->reserved, sizeof (params->reserved)); mutex_enter(&node->fd_mutex); bcopy(node->fd_vv, params->vendor_version, sizeof (node->fd_vv)); params->node_ww_name = node->fd_node_name; mutex_exit(&node->fd_mutex); fctl_release_remote_port(pd); } if (use32 == B_TRUE) { params32 = kmem_zalloc(sizeof (*params32), KM_SLEEP); params32->ls_code.mbz = params->ls_code.mbz; params32->common_service = params->common_service; params32->nport_ww_name = params->nport_ww_name; params32->class_1 = params->class_1; params32->class_2 = params->class_2; params32->class_3 = params->class_3; bzero(params32->reserved, sizeof (params32->reserved)); bcopy(params->vendor_version, params32->vendor_version, sizeof (node->fd_vv)); params32->node_ww_name = params->node_ww_name; if (ddi_copyout((void *)params32, (void *)fcio->fcio_obuf, sizeof (*params32), mode)) { rval = EFAULT; } kmem_free(params32, sizeof (*params32)); } else { if (ddi_copyout((void *)params, (void *)fcio->fcio_obuf, sizeof (*params), mode)) { rval = EFAULT; } } kmem_free(params, sizeof (*params)); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } case FCIO_DEV_LOGOUT: case FCIO_DEV_LOGIN: if (fcio->fcio_ilen != sizeof (la_wwn_t) || fcio->fcio_xfer != FCIO_XFER_WRITE) { rval = EINVAL; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } if (fcio->fcio_cmd == FCIO_DEV_LOGIN) { jcode = JOB_FCIO_LOGIN; } else { jcode = JOB_FCIO_LOGOUT; } kfcio = kmem_zalloc(sizeof (*kfcio), KM_SLEEP); bcopy(fcio, kfcio, sizeof (*fcio)); if (kfcio->fcio_ilen) { kfcio->fcio_ibuf = kmem_zalloc(kfcio->fcio_ilen, KM_SLEEP); if (ddi_copyin((void *)fcio->fcio_ibuf, (void *)kfcio->fcio_ibuf, kfcio->fcio_ilen, mode)) { rval = EFAULT; kmem_free(kfcio->fcio_ibuf, kfcio->fcio_ilen); kmem_free(kfcio, sizeof (*kfcio)); fcio->fcio_errno = job->job_result; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } } job = fctl_alloc_job(jcode, 0, NULL, NULL, KM_SLEEP); job->job_private = kfcio; fctl_enque_job(port, job); fctl_jobwait(job); rval = job->job_result; fcio->fcio_errno = kfcio->fcio_errno; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } kmem_free(kfcio->fcio_ibuf, kfcio->fcio_ilen); kmem_free(kfcio, sizeof (*kfcio)); fctl_dealloc_job(job); break; case FCIO_GET_STATE: { la_wwn_t pwwn; uint32_t state; fc_remote_port_t *pd; fctl_ns_req_t *ns_cmd; if (fcio->fcio_ilen != sizeof (la_wwn_t) || fcio->fcio_olen != sizeof (state) || (fcio->fcio_xfer & FCIO_XFER_WRITE) == 0 || (fcio->fcio_xfer & FCIO_XFER_READ) == 0) { rval = EINVAL; break; } if (ddi_copyin(fcio->fcio_ibuf, &pwwn, sizeof (pwwn), mode)) { rval = EFAULT; break; } fcio->fcio_errno = 0; pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); job = fctl_alloc_job(JOB_PLOGI_ONE, 0, NULL, NULL, KM_SLEEP); job->job_counter = 1; job->job_result = FC_SUCCESS; ns_cmd = fctl_alloc_ns_cmd( sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), FCTL_NS_BUF_IS_USERLAND, KM_SLEEP); ASSERT(ns_cmd != NULL); ns_cmd->ns_cmd_code = NS_GID_PN; ((ns_req_gid_pn_t *) (ns_cmd->ns_cmd_buf))->pwwn = pwwn; ret = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (ret != FC_SUCCESS || job->job_result != FC_SUCCESS) { if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; } else { fcio->fcio_errno = job->job_result; } rval = EIO; } else { state = PORT_DEVICE_INVALID; } fctl_free_ns_cmd(ns_cmd); fctl_dealloc_job(job); } else { mutex_exit(&port->fp_mutex); fcio->fcio_errno = FC_BADWWN; rval = ENXIO; } } else { mutex_enter(&pd->pd_mutex); state = pd->pd_state; mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); } if (!rval) { if (ddi_copyout((void *)&state, (void *)fcio->fcio_obuf, sizeof (state), mode)) { rval = EFAULT; } } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } case FCIO_DEV_REMOVE: { la_wwn_t pwwn; fc_portmap_t *changelist; fc_remote_port_t *pd; if (fcio->fcio_ilen != sizeof (la_wwn_t) || fcio->fcio_xfer != FCIO_XFER_WRITE) { rval = EINVAL; break; } if (ddi_copyin(fcio->fcio_ibuf, &pwwn, sizeof (pwwn), mode)) { rval = EFAULT; break; } pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { rval = ENXIO; fcio->fcio_errno = FC_BADWWN; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } mutex_enter(&pd->pd_mutex); if (pd->pd_ref_count > 1) { mutex_exit(&pd->pd_mutex); rval = EBUSY; fcio->fcio_errno = FC_FAILURE; fctl_release_remote_port(pd); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } mutex_exit(&pd->pd_mutex); changelist = kmem_zalloc(sizeof (*changelist), KM_SLEEP); fctl_copy_portmap(changelist, pd); changelist->map_type = PORT_DEVICE_USER_LOGOUT; (void) fp_ulp_devc_cb(port, changelist, 1, 1, KM_SLEEP, 1); fctl_release_remote_port(pd); break; } case FCIO_GET_FCODE_REV: { caddr_t fcode_rev; fc_fca_pm_t pm; if (fcio->fcio_olen < FC_FCODE_REV_SIZE || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } bzero((caddr_t)&pm, sizeof (pm)); fcode_rev = kmem_zalloc(fcio->fcio_olen, KM_SLEEP); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_FCODE_REV; pm.pm_data_len = fcio->fcio_olen; pm.pm_data_buf = fcode_rev; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { if (ddi_copyout((void *)fcode_rev, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { /* * check if buffer was not large enough to obtain * FCODE version. */ if (pm.pm_data_len > fcio->fcio_olen) { rval = ENOMEM; } else { rval = EIO; } fcio->fcio_errno = ret; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } kmem_free(fcode_rev, fcio->fcio_olen); break; } case FCIO_GET_FW_REV: { caddr_t fw_rev; fc_fca_pm_t pm; if (fcio->fcio_olen < FC_FW_REV_SIZE || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } bzero((caddr_t)&pm, sizeof (pm)); fw_rev = kmem_zalloc(fcio->fcio_olen, KM_SLEEP); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_FW_REV; pm.pm_data_len = fcio->fcio_olen; pm.pm_data_buf = fw_rev; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { if (ddi_copyout((void *)fw_rev, (void *)fcio->fcio_obuf, fcio->fcio_olen, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } rval = EIO; } kmem_free(fw_rev, fcio->fcio_olen); break; } case FCIO_GET_DUMP_SIZE: { uint32_t dump_size; fc_fca_pm_t pm; if (fcio->fcio_olen != sizeof (dump_size) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_DUMP_SIZE; pm.pm_data_len = sizeof (dump_size); pm.pm_data_buf = (caddr_t)&dump_size; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { if (ddi_copyout((void *)&dump_size, (void *)fcio->fcio_obuf, sizeof (dump_size), mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; } case FCIO_DOWNLOAD_FW: { caddr_t firmware; fc_fca_pm_t pm; if (fcio->fcio_ilen <= 0 || fcio->fcio_xfer != FCIO_XFER_WRITE) { rval = EINVAL; break; } firmware = kmem_zalloc(fcio->fcio_ilen, KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, firmware, fcio->fcio_ilen, mode)) { rval = EFAULT; kmem_free(firmware, fcio->fcio_ilen); break; } bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_WRITE; pm.pm_cmd_code = FC_PORT_DOWNLOAD_FW; pm.pm_data_len = fcio->fcio_ilen; pm.pm_data_buf = firmware; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); kmem_free(firmware, fcio->fcio_ilen); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; } case FCIO_DOWNLOAD_FCODE: { caddr_t fcode; fc_fca_pm_t pm; if (fcio->fcio_ilen <= 0 || fcio->fcio_xfer != FCIO_XFER_WRITE) { rval = EINVAL; break; } fcode = kmem_zalloc(fcio->fcio_ilen, KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, fcode, fcio->fcio_ilen, mode)) { rval = EFAULT; kmem_free(fcode, fcio->fcio_ilen); break; } bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_WRITE; pm.pm_cmd_code = FC_PORT_DOWNLOAD_FCODE; pm.pm_data_len = fcio->fcio_ilen; pm.pm_data_buf = fcode; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); kmem_free(fcode, fcio->fcio_ilen); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; } case FCIO_FORCE_DUMP: ret = port->fp_fca_tran->fca_reset( port->fp_fca_handle, FC_FCA_CORE); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; case FCIO_GET_DUMP: { caddr_t dump; uint32_t dump_size; fc_fca_pm_t pm; if (fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_DUMP_SIZE; pm.pm_data_len = sizeof (dump_size); pm.pm_data_buf = (caddr_t)&dump_size; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } if (fcio->fcio_olen != dump_size) { fcio->fcio_errno = FC_NOMEM; rval = EINVAL; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } dump = kmem_zalloc(dump_size, KM_SLEEP); bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_DUMP; pm.pm_data_len = dump_size; pm.pm_data_buf = dump; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { if (ddi_copyout((void *)dump, (void *)fcio->fcio_obuf, dump_size, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } kmem_free(dump, dump_size); break; } case FCIO_GET_TOPOLOGY: { uint32_t user_topology; if (fcio->fcio_xfer != FCIO_XFER_READ || fcio->fcio_olen != sizeof (user_topology)) { rval = EINVAL; break; } mutex_enter(&port->fp_mutex); if (FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) { user_topology = FC_TOP_UNKNOWN; } else { user_topology = port->fp_topology; } mutex_exit(&port->fp_mutex); if (ddi_copyout((void *)&user_topology, (void *)fcio->fcio_obuf, sizeof (user_topology), mode)) { rval = EFAULT; } break; } case FCIO_RESET_LINK: { la_wwn_t pwwn; /* * Look at the output buffer field; if this field has zero * bytes then attempt to reset the local link/loop. If the * fcio_ibuf field points to a WWN, see if it's an NL_Port, * and if yes, determine the LFA and reset the remote LIP * by LINIT ELS. */ if (fcio->fcio_xfer != FCIO_XFER_WRITE || fcio->fcio_ilen != sizeof (pwwn)) { rval = EINVAL; break; } if (ddi_copyin(fcio->fcio_ibuf, &pwwn, sizeof (pwwn), mode)) { rval = EFAULT; break; } mutex_enter(&port->fp_mutex); if (port->fp_soft_state & FP_SOFT_IN_LINK_RESET) { mutex_exit(&port->fp_mutex); break; } port->fp_soft_state |= FP_SOFT_IN_LINK_RESET; mutex_exit(&port->fp_mutex); job = fctl_alloc_job(JOB_LINK_RESET, 0, NULL, NULL, KM_SLEEP); if (job == NULL) { rval = ENOMEM; break; } job->job_counter = 1; job->job_private = (void *)&pwwn; fctl_enque_job(port, job); fctl_jobwait(job); mutex_enter(&port->fp_mutex); port->fp_soft_state &= ~FP_SOFT_IN_LINK_RESET; mutex_exit(&port->fp_mutex); if (job->job_result != FC_SUCCESS) { fcio->fcio_errno = job->job_result; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } fctl_dealloc_job(job); break; } case FCIO_RESET_HARD: ret = port->fp_fca_tran->fca_reset( port->fp_fca_handle, FC_FCA_RESET); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; case FCIO_RESET_HARD_CORE: ret = port->fp_fca_tran->fca_reset( port->fp_fca_handle, FC_FCA_RESET_CORE); if (ret != FC_SUCCESS) { rval = EIO; fcio->fcio_errno = ret; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } break; case FCIO_DIAG: { fc_fca_pm_t pm; bzero((caddr_t)&pm, sizeof (fc_fca_pm_t)); /* Validate user buffer from ioctl call. */ if (((fcio->fcio_ilen > 0) && (fcio->fcio_ibuf == NULL)) || ((fcio->fcio_ilen <= 0) && (fcio->fcio_ibuf != NULL)) || ((fcio->fcio_alen > 0) && (fcio->fcio_abuf == NULL)) || ((fcio->fcio_alen <= 0) && (fcio->fcio_abuf != NULL)) || ((fcio->fcio_olen > 0) && (fcio->fcio_obuf == NULL)) || ((fcio->fcio_olen <= 0) && (fcio->fcio_obuf != NULL))) { rval = EFAULT; break; } if ((pm.pm_cmd_len = fcio->fcio_ilen) > 0) { pm.pm_cmd_buf = kmem_zalloc(fcio->fcio_ilen, KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, pm.pm_cmd_buf, fcio->fcio_ilen, mode)) { rval = EFAULT; goto fp_fcio_diag_cleanup; } } if ((pm.pm_data_len = fcio->fcio_alen) > 0) { pm.pm_data_buf = kmem_zalloc(fcio->fcio_alen, KM_SLEEP); if (ddi_copyin(fcio->fcio_abuf, pm.pm_data_buf, fcio->fcio_alen, mode)) { rval = EFAULT; goto fp_fcio_diag_cleanup; } } if ((pm.pm_stat_len = fcio->fcio_olen) > 0) { pm.pm_stat_buf = kmem_zalloc(fcio->fcio_olen, KM_SLEEP); } pm.pm_cmd_code = FC_PORT_DIAG; pm.pm_cmd_flags = fcio->fcio_cmd_flags; ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret != FC_SUCCESS) { if (ret == FC_INVALID_REQUEST) { rval = ENOTTY; } else { rval = EIO; } fcio->fcio_errno = ret; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } goto fp_fcio_diag_cleanup; } /* * pm_stat_len will contain the number of status bytes * an FCA driver requires to return the complete status * of the requested diag operation. If the user buffer * is not large enough to hold the entire status, We * copy only the portion of data the fits in the buffer and * return a ENOMEM to the user application. */ if (pm.pm_stat_len > fcio->fcio_olen) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp:FCIO_DIAG:status buffer too small\n"); rval = ENOMEM; if (ddi_copyout(pm.pm_stat_buf, fcio->fcio_obuf, fcio->fcio_olen, mode)) { rval = EFAULT; goto fp_fcio_diag_cleanup; } } else { /* * Copy only data pm_stat_len bytes of data */ if (ddi_copyout(pm.pm_stat_buf, fcio->fcio_obuf, pm.pm_stat_len, mode)) { rval = EFAULT; goto fp_fcio_diag_cleanup; } } if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } fp_fcio_diag_cleanup: if (pm.pm_cmd_buf != NULL) { kmem_free(pm.pm_cmd_buf, fcio->fcio_ilen); } if (pm.pm_data_buf != NULL) { kmem_free(pm.pm_data_buf, fcio->fcio_alen); } if (pm.pm_stat_buf != NULL) { kmem_free(pm.pm_stat_buf, fcio->fcio_olen); } break; } case FCIO_GET_NODE_ID: { /* validate parameters */ if (fcio->fcio_xfer != FCIO_XFER_READ || fcio->fcio_olen < sizeof (fc_rnid_t)) { rval = EINVAL; break; } rval = fp_get_rnid(port, data, mode, fcio); /* ioctl handling is over */ break; } case FCIO_SEND_NODE_ID: { la_wwn_t pwwn; /* validate parameters */ if (fcio->fcio_ilen != sizeof (la_wwn_t) || fcio->fcio_xfer != FCIO_XFER_READ) { rval = EINVAL; break; } if (ddi_copyin(fcio->fcio_ibuf, &pwwn, sizeof (la_wwn_t), mode)) { rval = EFAULT; break; } rval = fp_send_rnid(port, data, mode, fcio, &pwwn); /* ioctl handling is over */ break; } case FCIO_SET_NODE_ID: { if (fcio->fcio_ilen != sizeof (fc_rnid_t) || (fcio->fcio_xfer != FCIO_XFER_WRITE)) { rval = EINVAL; break; } rval = fp_set_rnid(port, data, mode, fcio); break; } case FCIO_LINK_STATUS: { fc_portid_t rls_req; fc_rls_acc_t *rls_acc; fc_fca_pm_t pm; uint32_t dest, src_id; fp_cmd_t *cmd; fc_remote_port_t *pd; uchar_t pd_flags; /* validate parameters */ if (fcio->fcio_ilen != sizeof (fc_portid_t) || fcio->fcio_olen != sizeof (fc_rls_acc_t) || fcio->fcio_xfer != FCIO_XFER_RW) { rval = EINVAL; break; } if ((fcio->fcio_cmd_flags != FCIO_CFLAGS_RLS_DEST_FPORT) && (fcio->fcio_cmd_flags != FCIO_CFLAGS_RLS_DEST_NPORT)) { rval = EINVAL; break; } if (ddi_copyin((void *)fcio->fcio_ibuf, (void *)&rls_req, sizeof (fc_portid_t), mode)) { rval = EFAULT; break; } /* Determine the destination of the RLS frame */ if (fcio->fcio_cmd_flags == FCIO_CFLAGS_RLS_DEST_FPORT) { dest = FS_FABRIC_F_PORT; } else { dest = rls_req.port_id; } mutex_enter(&port->fp_mutex); src_id = port->fp_port_id.port_id; mutex_exit(&port->fp_mutex); /* If dest is zero OR same as FCA ID, then use port_manage() */ if (dest == 0 || dest == src_id) { /* Allocate memory for link error status block */ rls_acc = kmem_zalloc(sizeof (*rls_acc), KM_SLEEP); ASSERT(rls_acc != NULL); /* Prepare the port management structure */ bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_RLS; pm.pm_data_len = sizeof (*rls_acc); pm.pm_data_buf = (caddr_t)rls_acc; /* Get the adapter's link error status block */ ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { /* xfer link status block to userland */ if (ddi_copyout((void *)rls_acc, (void *)fcio->fcio_obuf, sizeof (*rls_acc), mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { rval = EIO; fcio->fcio_errno = ret; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } kmem_free(rls_acc, sizeof (*rls_acc)); /* ioctl handling is over */ break; } /* * Send RLS to the destination port. * Having RLS frame destination is as FPORT is not yet * supported and will be implemented in future, if needed. * Following call to get "pd" will fail if dest is FPORT */ pd = fctl_hold_remote_port_by_did(port, dest); if (pd == NULL) { fcio->fcio_errno = FC_BADOBJECT; rval = ENXIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } mutex_enter(&pd->pd_mutex); if (pd->pd_state != PORT_DEVICE_LOGGED_IN) { mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); fcio->fcio_errno = FC_LOGINREQ; rval = EINVAL; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } ASSERT(pd->pd_login_count >= 1); mutex_exit(&pd->pd_mutex); /* * Allocate job structure and set job_code as DUMMY, * because we will not go through the job thread. * Instead fp_sendcmd() is called directly here. */ job = fctl_alloc_job(JOB_DUMMY, JOB_TYPE_FP_ASYNC, NULL, NULL, KM_SLEEP); ASSERT(job != NULL); job->job_counter = 1; cmd = fp_alloc_pkt(port, sizeof (la_els_rls_t), sizeof (la_els_rls_acc_t), KM_SLEEP, pd); if (cmd == NULL) { fcio->fcio_errno = FC_NOMEM; rval = ENOMEM; fctl_release_remote_port(pd); fctl_dealloc_job(job); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } /* Allocate memory for link error status block */ rls_acc = kmem_zalloc(sizeof (*rls_acc), KM_SLEEP); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; fp_rls_init(cmd, job); job->job_private = (void *)rls_acc; pd_flags = pd->pd_flags; pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (fp_sendcmd(port, cmd, port->fp_fca_handle) == FC_SUCCESS) { fctl_jobwait(job); fcio->fcio_errno = job->job_result; if (job->job_result == FC_SUCCESS) { ASSERT(pd != NULL); /* * link error status block is now available. * Copy it to userland */ ASSERT(job->job_private == (void *)rls_acc); if (ddi_copyout((void *)rls_acc, (void *)fcio->fcio_obuf, sizeof (*rls_acc), mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { rval = EIO; } } else { rval = EIO; fp_free_pkt(cmd); } if (rval) { mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if (pd->pd_flags == PD_ELS_IN_PROGRESS) { pd->pd_flags = pd_flags; } mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); } fctl_release_remote_port(pd); fctl_dealloc_job(job); kmem_free(rls_acc, sizeof (*rls_acc)); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } case FCIO_NS: { fc_ns_cmd_t *ns_req; fc_ns_cmd32_t *ns_req32; fctl_ns_req_t *ns_cmd; if (use32 == B_TRUE) { if (fcio->fcio_ilen != sizeof (*ns_req32)) { rval = EINVAL; break; } ns_req = kmem_zalloc(sizeof (*ns_req), KM_SLEEP); ns_req32 = kmem_zalloc(sizeof (*ns_req32), KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, ns_req32, sizeof (*ns_req32), mode)) { rval = EFAULT; kmem_free(ns_req, sizeof (*ns_req)); kmem_free(ns_req32, sizeof (*ns_req32)); break; } ns_req->ns_flags = ns_req32->ns_flags; ns_req->ns_cmd = ns_req32->ns_cmd; ns_req->ns_req_len = ns_req32->ns_req_len; ns_req->ns_req_payload = ns_req32->ns_req_payload; ns_req->ns_resp_len = ns_req32->ns_resp_len; ns_req->ns_resp_payload = ns_req32->ns_resp_payload; ns_req->ns_fctl_private = ns_req32->ns_fctl_private; ns_req->ns_resp_hdr = ns_req32->ns_resp_hdr; kmem_free(ns_req32, sizeof (*ns_req32)); } else { if (fcio->fcio_ilen != sizeof (*ns_req)) { rval = EINVAL; break; } ns_req = kmem_zalloc(sizeof (*ns_req), KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, ns_req, sizeof (fc_ns_cmd_t), mode)) { rval = EFAULT; kmem_free(ns_req, sizeof (*ns_req)); break; } } if (ns_req->ns_req_len <= 0) { rval = EINVAL; kmem_free(ns_req, sizeof (*ns_req)); break; } job = fctl_alloc_job(JOB_NS_CMD, 0, NULL, NULL, KM_SLEEP); ASSERT(job != NULL); ns_cmd = fctl_alloc_ns_cmd(ns_req->ns_req_len, ns_req->ns_resp_len, ns_req->ns_resp_len, FCTL_NS_FILL_NS_MAP, KM_SLEEP); ASSERT(ns_cmd != NULL); ns_cmd->ns_cmd_code = ns_req->ns_cmd; if (ns_cmd->ns_cmd_code == NS_GA_NXT) { ns_cmd->ns_gan_max = 1; ns_cmd->ns_gan_index = 0; ns_cmd->ns_gan_sid = FCTL_GAN_START_ID; } if (ddi_copyin(ns_req->ns_req_payload, ns_cmd->ns_cmd_buf, ns_req->ns_req_len, mode)) { rval = EFAULT; fctl_free_ns_cmd(ns_cmd); fctl_dealloc_job(job); kmem_free(ns_req, sizeof (*ns_req)); break; } job->job_private = (void *)ns_cmd; fctl_enque_job(port, job); fctl_jobwait(job); rval = job->job_result; if (rval == FC_SUCCESS) { if (ns_req->ns_resp_len) { if (ddi_copyout(ns_cmd->ns_data_buf, ns_req->ns_resp_payload, ns_cmd->ns_data_len, mode)) { rval = EFAULT; fctl_free_ns_cmd(ns_cmd); fctl_dealloc_job(job); kmem_free(ns_req, sizeof (*ns_req)); break; } } } else { rval = EIO; } ns_req->ns_resp_hdr = ns_cmd->ns_resp_hdr; fctl_free_ns_cmd(ns_cmd); fctl_dealloc_job(job); kmem_free(ns_req, sizeof (*ns_req)); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } break; } default: rval = ENOTTY; break; } /* * If set, reset the EXCL busy bit to * receive other exclusive access commands */ mutex_enter(&port->fp_mutex); if (port->fp_flag & FP_EXCL_BUSY) { port->fp_flag &= ~FP_EXCL_BUSY; } mutex_exit(&port->fp_mutex); return (rval); } /* * This function assumes that the response length * is same regardless of data model (LP32 or LP64) * which is true for all the ioctls currently * supported. */ static int fp_copyout(void *from, void *to, size_t len, int mode) { return (ddi_copyout(from, to, len, mode)); } /* * This function does the set rnid */ static int fp_set_rnid(fc_local_port_t *port, intptr_t data, int mode, fcio_t *fcio) { int rval = 0; fc_rnid_t *rnid; fc_fca_pm_t pm; /* Allocate memory for node id block */ rnid = kmem_zalloc(sizeof (fc_rnid_t), KM_SLEEP); if (ddi_copyin(fcio->fcio_ibuf, rnid, sizeof (fc_rnid_t), mode)) { FP_TRACE(FP_NHEAD1(3, 0), "fp_set_rnid: failed = %d", EFAULT); kmem_free(rnid, sizeof (fc_rnid_t)); return (EFAULT); } /* Prepare the port management structure */ bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_WRITE; pm.pm_cmd_code = FC_PORT_SET_NODE_ID; pm.pm_data_len = sizeof (*rnid); pm.pm_data_buf = (caddr_t)rnid; /* Get the adapter's node data */ rval = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (rval != FC_SUCCESS) { fcio->fcio_errno = rval; rval = EIO; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { mutex_enter(&port->fp_mutex); /* copy to the port structure */ bcopy(rnid, &port->fp_rnid_params, sizeof (port->fp_rnid_params)); mutex_exit(&port->fp_mutex); } kmem_free(rnid, sizeof (fc_rnid_t)); if (rval != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(3, 0), "fp_set_rnid: failed = %d", rval); } return (rval); } /* * This function does the local pwwn get rnid */ static int fp_get_rnid(fc_local_port_t *port, intptr_t data, int mode, fcio_t *fcio) { fc_rnid_t *rnid; fc_fca_pm_t pm; int rval = 0; uint32_t ret; /* Allocate memory for rnid data block */ rnid = kmem_zalloc(sizeof (fc_rnid_t), KM_SLEEP); mutex_enter(&port->fp_mutex); if (port->fp_rnid_init == 1) { bcopy(&port->fp_rnid_params, rnid, sizeof (fc_rnid_t)); mutex_exit(&port->fp_mutex); /* xfer node info to userland */ if (ddi_copyout((void *)rnid, (void *)fcio->fcio_obuf, sizeof (*rnid), mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } kmem_free(rnid, sizeof (fc_rnid_t)); if (rval != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(3, 0), "fp_get_rnid: failed = %d", rval); } return (rval); } mutex_exit(&port->fp_mutex); /* Prepare the port management structure */ bzero((caddr_t)&pm, sizeof (pm)); pm.pm_cmd_flags = FC_FCA_PM_READ; pm.pm_cmd_code = FC_PORT_GET_NODE_ID; pm.pm_data_len = sizeof (fc_rnid_t); pm.pm_data_buf = (caddr_t)rnid; /* Get the adapter's node data */ ret = port->fp_fca_tran->fca_port_manage( port->fp_fca_handle, &pm); if (ret == FC_SUCCESS) { /* initialize in the port_info */ mutex_enter(&port->fp_mutex); port->fp_rnid_init = 1; bcopy(rnid, &port->fp_rnid_params, sizeof (*rnid)); mutex_exit(&port->fp_mutex); /* xfer node info to userland */ if (ddi_copyout((void *)rnid, (void *)fcio->fcio_obuf, sizeof (*rnid), mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { rval = EIO; fcio->fcio_errno = ret; if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } kmem_free(rnid, sizeof (fc_rnid_t)); if (rval != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(3, 0), "fp_get_rnid: failed = %d", rval); } return (rval); } static int fp_send_rnid(fc_local_port_t *port, intptr_t data, int mode, fcio_t *fcio, la_wwn_t *pwwn) { int rval = 0; fc_remote_port_t *pd; fp_cmd_t *cmd; job_request_t *job; la_els_rnid_acc_t *rnid_acc; pd = fctl_get_remote_port_by_pwwn(port, pwwn); if (pd == NULL) { /* * We can safely assume that the destination port * is logged in. Either the user land will explicitly * login before issuing RNID ioctl or the device would * have been configured, meaning already logged in. */ FP_TRACE(FP_NHEAD1(3, 0), "fp_send_rnid: failed = %d", ENXIO); return (ENXIO); } /* * Allocate job structure and set job_code as DUMMY, * because we will not go thorugh the job thread. * Instead fp_sendcmd() is called directly here. */ job = fctl_alloc_job(JOB_DUMMY, JOB_TYPE_FP_ASYNC, NULL, NULL, KM_SLEEP); ASSERT(job != NULL); job->job_counter = 1; cmd = fp_alloc_pkt(port, sizeof (la_els_rnid_t), sizeof (la_els_rnid_acc_t), KM_SLEEP, pd); if (cmd == NULL) { fcio->fcio_errno = FC_NOMEM; rval = ENOMEM; fctl_dealloc_job(job); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } FP_TRACE(FP_NHEAD1(3, 0), "fp_send_rnid: failed = %d", rval); return (rval); } /* Allocate memory for node id accept block */ rnid_acc = kmem_zalloc(sizeof (la_els_rnid_acc_t), KM_SLEEP); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; fp_rnid_init(cmd, fcio->fcio_cmd_flags, job); job->job_private = (void *)rnid_acc; pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (fp_sendcmd(port, cmd, port->fp_fca_handle) == FC_SUCCESS) { fctl_jobwait(job); fcio->fcio_errno = job->job_result; if (job->job_result == FC_SUCCESS) { int rnid_cnt; ASSERT(pd != NULL); /* * node id block is now available. * Copy it to userland */ ASSERT(job->job_private == (void *)rnid_acc); /* get the response length */ rnid_cnt = sizeof (ls_code_t) + sizeof (fc_rnid_hdr_t) + rnid_acc->hdr.cmn_len + rnid_acc->hdr.specific_len; if (fcio->fcio_olen < rnid_cnt) { rval = EINVAL; } else if (ddi_copyout((void *)rnid_acc, (void *)fcio->fcio_obuf, rnid_cnt, mode) == 0) { if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } } else { rval = EFAULT; } } else { rval = EIO; } } else { rval = EIO; if (pd) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); } fp_free_pkt(cmd); } fctl_dealloc_job(job); kmem_free(rnid_acc, sizeof (la_els_rnid_acc_t)); if (fp_fcio_copyout(fcio, data, mode)) { rval = EFAULT; } if (rval != FC_SUCCESS) { FP_TRACE(FP_NHEAD1(3, 0), "fp_send_rnid: failed = %d", rval); } return (rval); } /* * Copy out to userland */ static int fp_fcio_copyout(fcio_t *fcio, intptr_t data, int mode) { int rval; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcio32 fcio32; fcio32.fcio_xfer = fcio->fcio_xfer; fcio32.fcio_cmd = fcio->fcio_cmd; fcio32.fcio_flags = fcio->fcio_flags; fcio32.fcio_cmd_flags = fcio->fcio_cmd_flags; fcio32.fcio_ilen = fcio->fcio_ilen; fcio32.fcio_ibuf = (caddr32_t)(uintptr_t)fcio->fcio_ibuf; fcio32.fcio_olen = fcio->fcio_olen; fcio32.fcio_obuf = (caddr32_t)(uintptr_t)fcio->fcio_obuf; fcio32.fcio_alen = fcio->fcio_alen; fcio32.fcio_abuf = (caddr32_t)(uintptr_t)fcio->fcio_abuf; fcio32.fcio_errno = fcio->fcio_errno; rval = ddi_copyout((void *)&fcio32, (void *)data, sizeof (struct fcio32), mode); break; } case DDI_MODEL_NONE: rval = ddi_copyout((void *)fcio, (void *)data, sizeof (fcio_t), mode); break; } #else rval = ddi_copyout((void *)fcio, (void *)data, sizeof (fcio_t), mode); #endif return (rval); } static void fp_p2p_online(fc_local_port_t *port, job_request_t *job) { uint32_t listlen; fc_portmap_t *changelist; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(port->fp_topology == FC_TOP_PT_PT); ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); listlen = 0; changelist = NULL; if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { if (port->fp_statec_busy > 1) { job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; } } mutex_exit(&port->fp_mutex); if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { fctl_fillout_map(port, &changelist, &listlen, 1, 0, 0); (void) fp_ulp_statec_cb(port, FC_STATE_ONLINE, changelist, listlen, listlen, KM_SLEEP); mutex_enter(&port->fp_mutex); } else { ASSERT(changelist == NULL && listlen == 0); mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } } } static int fp_fillout_p2pmap(fc_local_port_t *port, fcio_t *fcio, int mode) { int rval; int count; int index; int num_devices; fc_remote_node_t *node; fc_port_dev_t *devlist; struct pwwn_hash *head; fc_remote_port_t *pd; ASSERT(MUTEX_HELD(&port->fp_mutex)); num_devices = fcio->fcio_olen / sizeof (fc_port_dev_t); devlist = kmem_zalloc(sizeof (fc_port_dev_t) * num_devices, KM_SLEEP); for (count = index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_INVALID) { mutex_exit(&pd->pd_mutex); pd = pd->pd_wwn_hnext; continue; } devlist[count].dev_state = pd->pd_state; devlist[count].dev_hard_addr = pd->pd_hard_addr; devlist[count].dev_did = pd->pd_port_id; devlist[count].dev_did.priv_lilp_posit = (uint8_t)(index & 0xff); bcopy((caddr_t)pd->pd_fc4types, (caddr_t)devlist[count].dev_type, sizeof (pd->pd_fc4types)); bcopy((caddr_t)&pd->pd_port_name, (caddr_t)&devlist[count].dev_pwwn, sizeof (la_wwn_t)); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); if (node) { mutex_enter(&node->fd_mutex); bcopy((caddr_t)&node->fd_node_name, (caddr_t)&devlist[count].dev_nwwn, sizeof (la_wwn_t)); mutex_exit(&node->fd_mutex); } count++; if (count >= num_devices) { goto found; } } } found: if (fp_copyout((void *)&count, (void *)fcio->fcio_abuf, sizeof (count), mode)) { rval = FC_FAILURE; } else if (fp_copyout((void *)devlist, (void *)fcio->fcio_obuf, sizeof (fc_port_dev_t) * num_devices, mode)) { rval = FC_FAILURE; } else { rval = FC_SUCCESS; } kmem_free(devlist, sizeof (fc_port_dev_t) * num_devices); return (rval); } /* * Handle Fabric ONLINE */ static void fp_fabric_online(fc_local_port_t *port, job_request_t *job) { int index; int rval; int dbg_count; int count = 0; char ww_name[17]; uint32_t d_id; uint32_t listlen; fctl_ns_req_t *ns_cmd; struct pwwn_hash *head; fc_remote_port_t *pd; fc_remote_port_t *npd; fc_portmap_t *changelist; ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(FC_IS_TOP_SWITCH(port->fp_topology)); ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), 0, KM_SLEEP); ASSERT(ns_cmd != NULL); ns_cmd->ns_cmd_code = NS_GID_PN; /* * Check if orphans are showing up now */ if (port->fp_orphan_count) { fc_orphan_t *orp; fc_orphan_t *norp = NULL; fc_orphan_t *prev = NULL; for (orp = port->fp_orphan_list; orp; orp = norp) { norp = orp->orp_next; mutex_exit(&port->fp_mutex); orp->orp_nscan++; job->job_counter = 1; job->job_result = FC_SUCCESS; ((ns_req_gid_pn_t *) (ns_cmd->ns_cmd_buf))->pwwn = orp->orp_pwwn; ((ns_resp_gid_pn_t *) ns_cmd->ns_data_buf)->pid.port_id = 0; ((ns_resp_gid_pn_t *) ns_cmd->ns_data_buf)->pid.priv_lilp_posit = 0; rval = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (rval == FC_SUCCESS) { d_id = BE_32(*((uint32_t *)ns_cmd->ns_data_buf)); pd = fp_create_remote_port_by_ns(port, d_id, KM_SLEEP); if (pd != NULL) { fc_wwn_to_str(&orp->orp_pwwn, ww_name); fp_printf(port, CE_WARN, FP_LOG_ONLY, 0, NULL, "N_x Port with D_ID=%x," " PWWN=%s reappeared in fabric", d_id, ww_name); mutex_enter(&port->fp_mutex); if (prev) { prev->orp_next = orp->orp_next; } else { ASSERT(orp == port->fp_orphan_list); port->fp_orphan_list = orp->orp_next; } port->fp_orphan_count--; mutex_exit(&port->fp_mutex); kmem_free(orp, sizeof (*orp)); count++; mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_ELS_MARK; mutex_exit(&pd->pd_mutex); } else { prev = orp; } } else { if (orp->orp_nscan == FC_ORPHAN_SCAN_LIMIT) { fc_wwn_to_str(&orp->orp_pwwn, ww_name); fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, " Port WWN %s removed from orphan" " list after %d scans", ww_name, orp->orp_nscan); mutex_enter(&port->fp_mutex); if (prev) { prev->orp_next = orp->orp_next; } else { ASSERT(orp == port->fp_orphan_list); port->fp_orphan_list = orp->orp_next; } port->fp_orphan_count--; mutex_exit(&port->fp_mutex); kmem_free(orp, sizeof (*orp)); } else { prev = orp; } } mutex_enter(&port->fp_mutex); } } /* * Walk the Port WWN hash table, reestablish LOGIN * if a LOGIN is already performed on a particular * device; Any failure to LOGIN should mark the * port device OLD. */ for (index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; npd = head->pwwn_head; while ((pd = npd) != NULL) { la_wwn_t *pwwn; npd = pd->pd_wwn_hnext; /* * Don't count in the port devices that are new * unless the total number of devices visible * through this port is less than FP_MAX_DEVICES */ mutex_enter(&pd->pd_mutex); if (port->fp_dev_count >= FP_MAX_DEVICES || (port->fp_options & FP_TARGET_MODE)) { if (pd->pd_type == PORT_DEVICE_NEW || pd->pd_flags == PD_ELS_MARK || pd->pd_recepient != PD_PLOGI_INITIATOR) { mutex_exit(&pd->pd_mutex); continue; } } else { if (pd->pd_flags == PD_ELS_MARK || pd->pd_recepient != PD_PLOGI_INITIATOR) { mutex_exit(&pd->pd_mutex); continue; } pd->pd_type = PORT_DEVICE_OLD; } count++; /* * Consult with the name server about D_ID changes */ job->job_counter = 1; job->job_result = FC_SUCCESS; ((ns_req_gid_pn_t *) (ns_cmd->ns_cmd_buf))->pwwn = pd->pd_port_name; ((ns_resp_gid_pn_t *) ns_cmd->ns_data_buf)->pid.port_id = 0; ((ns_resp_gid_pn_t *)ns_cmd->ns_data_buf)-> pid.priv_lilp_posit = 0; pwwn = &pd->pd_port_name; pd->pd_flags = PD_ELS_MARK; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (rval != FC_SUCCESS) { fc_wwn_to_str(pwwn, ww_name); mutex_enter(&pd->pd_mutex); d_id = pd->pd_port_id.port_id; pd->pd_type = PORT_DEVICE_DELETE; mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_fabric_online: PD " "disappeared; d_id=%x, PWWN=%s", d_id, ww_name); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s" " disappeared from fabric", d_id, ww_name); mutex_enter(&port->fp_mutex); continue; } d_id = BE_32(*((uint32_t *)ns_cmd->ns_data_buf)); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); if (d_id != pd->pd_port_id.port_id) { fctl_delist_did_table(port, pd); fc_wwn_to_str(pwwn, ww_name); FP_TRACE(FP_NHEAD2(9, 0), "D_ID of a device with PWWN %s changed." " New D_ID = %x, OLD D_ID = %x", ww_name, d_id, pd->pd_port_id.port_id); pd->pd_port_id.port_id = BE_32(d_id); pd->pd_type = PORT_DEVICE_CHANGED; fctl_enlist_did_table(port, pd); } mutex_exit(&pd->pd_mutex); } } if (ns_cmd) { fctl_free_ns_cmd(ns_cmd); } listlen = 0; changelist = NULL; if (count) { if (port->fp_soft_state & FP_SOFT_IN_FCA_RESET) { port->fp_soft_state &= ~FP_SOFT_IN_FCA_RESET; mutex_exit(&port->fp_mutex); delay(drv_usectohz(FLA_RR_TOV * 1000 * 1000)); mutex_enter(&port->fp_mutex); } dbg_count = 0; job->job_counter = count; for (index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; npd = head->pwwn_head; while ((pd = npd) != NULL) { npd = pd->pd_wwn_hnext; mutex_enter(&pd->pd_mutex); if (pd->pd_flags != PD_ELS_MARK) { mutex_exit(&pd->pd_mutex); continue; } dbg_count++; /* * If it is already marked deletion, nothing * else to do. */ if (pd->pd_type == PORT_DEVICE_DELETE) { pd->pd_type = PORT_DEVICE_OLD; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); fp_jobdone(job); mutex_enter(&port->fp_mutex); continue; } /* * If it is freshly discovered out of * the orphan list, nothing else to do */ if (pd->pd_type == PORT_DEVICE_NEW) { pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); fp_jobdone(job); mutex_enter(&port->fp_mutex); continue; } pd->pd_flags = PD_IDLE; d_id = pd->pd_port_id.port_id; /* * Explicitly mark all devices OLD; successful * PLOGI should reset this to either NO_CHANGE * or CHANGED. */ if (pd->pd_type != PORT_DEVICE_CHANGED) { pd->pd_type = PORT_DEVICE_OLD; } mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_port_login(port, d_id, job, FP_CMD_PLOGI_RETAIN, KM_SLEEP, pd, NULL); if (rval != FC_SUCCESS) { fp_jobdone(job); } mutex_enter(&port->fp_mutex); } } mutex_exit(&port->fp_mutex); ASSERT(dbg_count == count); fp_jobwait(job); mutex_enter(&port->fp_mutex); ASSERT(port->fp_statec_busy > 0); if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { if (port->fp_statec_busy > 1) { job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; } } mutex_exit(&port->fp_mutex); } else { ASSERT(port->fp_statec_busy > 0); if (port->fp_statec_busy > 1) { job->job_flags |= JOB_CANCEL_ULP_NOTIFICATION; } mutex_exit(&port->fp_mutex); } if ((job->job_flags & JOB_CANCEL_ULP_NOTIFICATION) == 0) { fctl_fillout_map(port, &changelist, &listlen, 1, 0, 0); (void) fp_ulp_statec_cb(port, FC_STATE_ONLINE, changelist, listlen, listlen, KM_SLEEP); mutex_enter(&port->fp_mutex); } else { ASSERT(changelist == NULL && listlen == 0); mutex_enter(&port->fp_mutex); if (--port->fp_statec_busy == 0) { port->fp_soft_state &= ~FP_SOFT_IN_STATEC_CB; } } } /* * Fill out device list for userland ioctl in private loop */ static int fp_fillout_loopmap(fc_local_port_t *port, fcio_t *fcio, int mode) { int rval; int count; int index; int num_devices; fc_remote_node_t *node; fc_port_dev_t *devlist; int lilp_device_count; fc_lilpmap_t *lilp_map; uchar_t *alpa_list; ASSERT(MUTEX_HELD(&port->fp_mutex)); num_devices = fcio->fcio_olen / sizeof (fc_port_dev_t); if (port->fp_total_devices > port->fp_dev_count && num_devices >= port->fp_total_devices) { job_request_t *job; mutex_exit(&port->fp_mutex); job = fctl_alloc_job(JOB_PORT_GETMAP, 0, NULL, NULL, KM_SLEEP); job->job_counter = 1; mutex_enter(&port->fp_mutex); fp_get_loopmap(port, job); mutex_exit(&port->fp_mutex); fp_jobwait(job); fctl_dealloc_job(job); } else { mutex_exit(&port->fp_mutex); } devlist = kmem_zalloc(sizeof (*devlist) * num_devices, KM_SLEEP); mutex_enter(&port->fp_mutex); /* * Applications are accustomed to getting the device list in * LILP map order. The HBA firmware usually returns the device * map in the LILP map order and diagnostic applications would * prefer to receive in the device list in that order too */ lilp_map = &port->fp_lilp_map; alpa_list = &lilp_map->lilp_alpalist[0]; /* * the length field corresponds to the offset in the LILP frame * which begins with 1. The thing to note here is that the * lilp_device_count is 1 more than fp->fp_total_devices since * the host adapter's alpa also shows up in the lilp map. We * don't however return details of the host adapter since * fctl_get_remote_port_by_did fails for the host adapter's ALPA * and applications are required to issue the FCIO_GET_HOST_PARAMS * ioctl to obtain details about the host adapter port. */ lilp_device_count = lilp_map->lilp_length; for (count = index = 0; index < lilp_device_count && count < num_devices; index++) { uint32_t d_id; fc_remote_port_t *pd; d_id = alpa_list[index]; mutex_exit(&port->fp_mutex); pd = fctl_get_remote_port_by_did(port, d_id); mutex_enter(&port->fp_mutex); if (pd != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_INVALID) { mutex_exit(&pd->pd_mutex); continue; } devlist[count].dev_state = pd->pd_state; devlist[count].dev_hard_addr = pd->pd_hard_addr; devlist[count].dev_did = pd->pd_port_id; devlist[count].dev_did.priv_lilp_posit = (uint8_t)(index & 0xff); bcopy((caddr_t)pd->pd_fc4types, (caddr_t)devlist[count].dev_type, sizeof (pd->pd_fc4types)); bcopy((caddr_t)&pd->pd_port_name, (caddr_t)&devlist[count].dev_pwwn, sizeof (la_wwn_t)); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); if (node) { mutex_enter(&node->fd_mutex); bcopy((caddr_t)&node->fd_node_name, (caddr_t)&devlist[count].dev_nwwn, sizeof (la_wwn_t)); mutex_exit(&node->fd_mutex); } count++; } } if (fp_copyout((void *)&count, (void *)fcio->fcio_abuf, sizeof (count), mode)) { rval = FC_FAILURE; } if (fp_copyout((void *)devlist, (void *)fcio->fcio_obuf, sizeof (fc_port_dev_t) * num_devices, mode)) { rval = FC_FAILURE; } else { rval = FC_SUCCESS; } kmem_free(devlist, sizeof (*devlist) * num_devices); ASSERT(MUTEX_HELD(&port->fp_mutex)); return (rval); } /* * Completion function for responses to unsolicited commands */ static void fp_unsol_intr(fc_packet_t *pkt) { fp_cmd_t *cmd; fc_local_port_t *port; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; mutex_enter(&port->fp_mutex); port->fp_out_fpcmds--; mutex_exit(&port->fp_mutex); if (pkt->pkt_state != FC_PKT_SUCCESS) { fp_printf(port, CE_WARN, FP_LOG_ONLY, 0, pkt, "couldn't post response to unsolicited request;" " ox_id=%x rx_id=%x", pkt->pkt_cmd_fhdr.ox_id, pkt->pkt_resp_fhdr.rx_id); } if (cmd == port->fp_els_resp_pkt) { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); return; } fp_free_pkt(cmd); } /* * solicited LINIT ELS completion function */ static void fp_linit_intr(fc_packet_t *pkt) { fp_cmd_t *cmd; job_request_t *job; fc_linit_resp_t acc; fc_local_port_t *port = ((fp_cmd_t *)pkt->pkt_ulp_private)->cmd_port; cmd = (fp_cmd_t *)pkt->pkt_ulp_private; mutex_enter(&cmd->cmd_port->fp_mutex); cmd->cmd_port->fp_out_fpcmds--; mutex_exit(&cmd->cmd_port->fp_mutex); if (FP_IS_PKT_ERROR(pkt)) { (void) fp_common_intr(pkt, 1); return; } job = cmd->cmd_job; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&acc, (uint8_t *)pkt->pkt_resp, sizeof (acc), DDI_DEV_AUTOINCR); if (acc.status != FC_LINIT_SUCCESS) { job->job_result = FC_FAILURE; } else { job->job_result = FC_SUCCESS; } fp_iodone(cmd); } /* * Decode the unsolicited request; For FC-4 Device and Link data frames * notify the registered ULP of this FC-4 type right here. For Unsolicited * ELS requests, submit a request to the job_handler thread to work on it. * The intent is to act quickly on the FC-4 unsolicited link and data frames * and save much of the interrupt time processing of unsolicited ELS requests * and hand it off to the job_handler thread. */ static void fp_unsol_cb(opaque_t port_handle, fc_unsol_buf_t *buf, uint32_t type) { uchar_t r_ctl; uchar_t ls_code; uint32_t s_id; uint32_t rscn_count = FC_INVALID_RSCN_COUNT; uint32_t cb_arg; fp_cmd_t *cmd; fc_local_port_t *port; job_request_t *job; fc_remote_port_t *pd; port = port_handle; FP_TRACE(FP_NHEAD1(1, 0), "fp_unsol_cb: s_id=%x," " d_id=%x, type=%x, r_ctl=%x, f_ctl=%x" " seq_id=%x, df_ctl=%x, seq_cnt=%x, ox_id=%x, rx_id=%x" " ro=%x, buffer[0]:%x", buf->ub_frame.s_id, buf->ub_frame.d_id, buf->ub_frame.type, buf->ub_frame.r_ctl, buf->ub_frame.f_ctl, buf->ub_frame.seq_id, buf->ub_frame.df_ctl, buf->ub_frame.seq_cnt, buf->ub_frame.ox_id, buf->ub_frame.rx_id, buf->ub_frame.ro, buf->ub_buffer[0]); if (type & 0x80000000) { /* * Huh ? Nothing much can be done without * a valid buffer. So just exit. */ return; } /* * If the unsolicited interrupts arrive while it isn't * safe to handle unsolicited callbacks; Drop them, yes, * drop them on the floor */ mutex_enter(&port->fp_mutex); port->fp_active_ubs++; if ((port->fp_soft_state & (FP_SOFT_IN_DETACH | FP_SOFT_SUSPEND | FP_SOFT_POWER_DOWN)) || FC_PORT_STATE_MASK(port->fp_state) == FC_STATE_OFFLINE) { FP_TRACE(FP_NHEAD1(3, 0), "fp_unsol_cb: port state is " "not ONLINE. s_id=%x, d_id=%x, type=%x, " "seq_id=%x, ox_id=%x, rx_id=%x" "ro=%x", buf->ub_frame.s_id, buf->ub_frame.d_id, buf->ub_frame.type, buf->ub_frame.seq_id, buf->ub_frame.ox_id, buf->ub_frame.rx_id, buf->ub_frame.ro); ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release(port->fp_fca_handle, 1, &buf->ub_token); return; } r_ctl = buf->ub_frame.r_ctl; s_id = buf->ub_frame.s_id; if (port->fp_active_ubs == 1) { port->fp_soft_state |= FP_SOFT_IN_UNSOL_CB; } if (r_ctl == R_CTL_ELS_REQ && buf->ub_buffer[0] == LA_ELS_LOGO && port->fp_statec_busy) { mutex_exit(&port->fp_mutex); pd = fctl_get_remote_port_by_did(port, s_id); if (pd) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { FP_TRACE(FP_NHEAD1(3, 0), "LOGO for LOGGED IN D_ID %x", buf->ub_frame.s_id); pd->pd_state = PORT_DEVICE_VALID; } mutex_exit(&pd->pd_mutex); } mutex_enter(&port->fp_mutex); ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release(port->fp_fca_handle, 1, &buf->ub_token); FP_TRACE(FP_NHEAD1(3, 0), "fp_unsol_cb() bailing out LOGO for D_ID %x", buf->ub_frame.s_id); return; } if (port->fp_els_resp_pkt_busy == 0) { if (r_ctl == R_CTL_ELS_REQ) { ls_code = buf->ub_buffer[0]; switch (ls_code) { case LA_ELS_PLOGI: case LA_ELS_FLOGI: port->fp_els_resp_pkt_busy = 1; mutex_exit(&port->fp_mutex); fp_i_handle_unsol_els(port, buf); mutex_enter(&port->fp_mutex); ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release( port->fp_fca_handle, 1, &buf->ub_token); return; case LA_ELS_RSCN: if (++(port)->fp_rscn_count == FC_INVALID_RSCN_COUNT) { ++(port)->fp_rscn_count; } rscn_count = port->fp_rscn_count; break; default: break; } } } else if ((r_ctl == R_CTL_ELS_REQ) && (buf->ub_buffer[0] == LA_ELS_RSCN)) { if (++port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { ++port->fp_rscn_count; } rscn_count = port->fp_rscn_count; } mutex_exit(&port->fp_mutex); switch (r_ctl & R_CTL_ROUTING) { case R_CTL_DEVICE_DATA: /* * If the unsolicited buffer is a CT IU, * have the job_handler thread work on it. */ if (buf->ub_frame.type == FC_TYPE_FC_SERVICES) { break; } /* FALLTHROUGH */ case R_CTL_FC4_SVC: { int sendup = 0; /* * If a LOGIN isn't performed before this request * shut the door on this port with a reply that a * LOGIN is required. We make an exception however * for IP broadcast packets and pass them through * to the IP ULP(s) to handle broadcast requests. * This is not a problem for private loop devices * but for fabric topologies we don't log into the * remote ports during port initialization and * the ULPs need to log into requesting ports on * demand. */ pd = fctl_get_remote_port_by_did(port, s_id); if (pd) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { sendup++; } mutex_exit(&pd->pd_mutex); } else if ((pd == NULL) && (buf->ub_frame.type == FC_TYPE_IS8802_SNAP) && (buf->ub_frame.d_id == 0xffffff || buf->ub_frame.d_id == 0x00)) { /* brodacst IP frame - so sendup via job thread */ break; } /* * Send all FC4 services via job thread too */ if ((r_ctl & R_CTL_ROUTING) == R_CTL_FC4_SVC) { break; } if (sendup || !FC_IS_REAL_DEVICE(s_id)) { fctl_ulp_unsol_cb(port, buf, buf->ub_frame.type); return; } if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_NOSLEEP, pd); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_LOGIN_REQUIRED, NULL); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } mutex_enter(&port->fp_mutex); ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release(port->fp_fca_handle, 1, &buf->ub_token); return; } default: break; } /* * Submit a Request to the job_handler thread to work * on the unsolicited request. The potential side effect * of this is that the unsolicited buffer takes a little * longer to get released but we save interrupt time in * the bargain. */ cb_arg = (rscn_count == FC_INVALID_RSCN_COUNT) ? 0 : rscn_count; /* * One way that the rscn_count will get used is described below : * * 1. fp_unsol_cb() gets an RSCN and updates fp_rscn_count. * 2. Before mutex is released, a copy of it is stored in rscn_count. * 3. The count is passed to job thread as JOB_UNSOL_REQUEST (below) * by overloading the job_cb_arg to pass the rscn_count * 4. When one of the routines processing the RSCN picks it up (ex: * fp_validate_rscn_page()), it passes this count in the map * structure (as part of the map_rscn_info structure member) to the * ULPs. * 5. When ULPs make calls back to the transport (example interfaces for * this are fc_ulp_transport(), fc_ulp_login(), fc_issue_els()), they * can now pass back this count as part of the fc_packet's * pkt_ulp_rscn_count member. fcp does this currently. * 6. When transport gets a call to transport a command on the wire, it * will check to see if there is a valid pkt_ulp_rsvd1 field in the * fc_packet. If there is, it will match that info with the current * rscn_count on that instance of the port. If they don't match up * then there was a newer RSCN. The ULP gets back an error code which * informs it about it - FC_DEVICE_BUSY_NEW_RSCN. * 7. At this point the ULP is free to make up its own mind as to how to * handle this. Currently, fcp will reset its retry counters and keep * retrying the operation it was doing in anticipation of getting a * new state change call back for the new RSCN. */ job = fctl_alloc_job(JOB_UNSOL_REQUEST, 0, NULL, (opaque_t)(uintptr_t)cb_arg, KM_NOSLEEP); if (job == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "fp_unsol_cb() " "couldn't submit a job to the thread, failing.."); mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release(port->fp_fca_handle, 1, &buf->ub_token); return; } job->job_private = (void *)buf; fctl_enque_job(port, job); } /* * Handle unsolicited requests */ static void fp_handle_unsol_buf(fc_local_port_t *port, fc_unsol_buf_t *buf, job_request_t *job) { uchar_t r_ctl; uchar_t ls_code; uint32_t s_id; fp_cmd_t *cmd; fc_remote_port_t *pd; fp_unsol_spec_t *ub_spec; r_ctl = buf->ub_frame.r_ctl; s_id = buf->ub_frame.s_id; switch (r_ctl & R_CTL_ROUTING) { case R_CTL_EXTENDED_SVC: if (r_ctl != R_CTL_ELS_REQ) { break; } ls_code = buf->ub_buffer[0]; switch (ls_code) { case LA_ELS_LOGO: case LA_ELS_ADISC: case LA_ELS_PRLO: pd = fctl_get_remote_port_by_did(port, s_id); if (pd == NULL) { if (!FC_IS_REAL_DEVICE(s_id)) { break; } if (!FP_IS_CLASS_1_OR_2(buf->ub_class)) { break; } if ((cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, NULL)) == NULL) { /* * Can this actually fail when * given KM_SLEEP? (Could be used * this way in a number of places.) */ break; } fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } break; } if (ls_code == LA_ELS_LOGO) { fp_handle_unsol_logo(port, buf, pd, job); } else if (ls_code == LA_ELS_ADISC) { fp_handle_unsol_adisc(port, buf, pd, job); } else { fp_handle_unsol_prlo(port, buf, pd, job); } break; case LA_ELS_PLOGI: fp_handle_unsol_plogi(port, buf, job, KM_SLEEP); break; case LA_ELS_FLOGI: fp_handle_unsol_flogi(port, buf, job, KM_SLEEP); break; case LA_ELS_RSCN: fp_handle_unsol_rscn(port, buf, job, KM_SLEEP); break; default: ub_spec = kmem_zalloc(sizeof (*ub_spec), KM_SLEEP); ub_spec->port = port; ub_spec->buf = buf; (void) taskq_dispatch(port->fp_taskq, fp_ulp_unsol_cb, ub_spec, KM_SLEEP); return; } break; case R_CTL_BASIC_SVC: /* * The unsolicited basic link services could be ABTS * and RMC (Or even a NOP). Just BA_RJT them until * such time there arises a need to handle them more * carefully. */ if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_ba_rjt_t), 0, KM_SLEEP, NULL); if (cmd != NULL) { fp_ba_rjt_init(port, cmd, buf, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } break; case R_CTL_DEVICE_DATA: if (buf->ub_frame.type == FC_TYPE_FC_SERVICES) { /* * Mostly this is of type FC_TYPE_FC_SERVICES. * As we don't like any Unsolicited FC services * requests, we would do well to RJT them as * well. */ if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, NULL); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } break; } /* FALLTHROUGH */ case R_CTL_FC4_SVC: ub_spec = kmem_zalloc(sizeof (*ub_spec), KM_SLEEP); ub_spec->port = port; ub_spec->buf = buf; (void) taskq_dispatch(port->fp_taskq, fp_ulp_unsol_cb, ub_spec, KM_SLEEP); return; case R_CTL_LINK_CTL: /* * Turn deaf ear on unsolicited link control frames. * Typical unsolicited link control Frame is an LCR * (to reset End to End credit to the default login * value and abort current sequences for all classes) * An intelligent microcode/firmware should handle * this transparently at its level and not pass all * the way up here. * * Possible responses to LCR are R_RDY, F_RJT, P_RJT * or F_BSY. P_RJT is chosen to be the most appropriate * at this time. */ /* FALLTHROUGH */ default: /* * Just reject everything else as an invalid request. */ if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, NULL); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } break; } mutex_enter(&port->fp_mutex); ASSERT(port->fp_active_ubs > 0); if (--(port->fp_active_ubs) == 0) { port->fp_soft_state &= ~FP_SOFT_IN_UNSOL_CB; } mutex_exit(&port->fp_mutex); port->fp_fca_tran->fca_ub_release(port->fp_fca_handle, 1, &buf->ub_token); } /* * Prepare a BA_RJT and send it over. */ static void fp_ba_rjt_init(fc_local_port_t *port, fp_cmd_t *cmd, fc_unsol_buf_t *buf, job_request_t *job) { fc_packet_t *pkt; la_ba_rjt_t payload; ASSERT(!MUTEX_HELD(&port->fp_mutex)); cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_LS_BA_RJT, FC_TYPE_BASIC_LS); payload.reserved = 0; payload.reason_code = FC_REASON_CMD_UNSUPPORTED; payload.explanation = FC_EXPLN_NONE; payload.vendor = 0; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Prepare an LS_RJT and send it over */ static void fp_els_rjt_init(fc_local_port_t *port, fp_cmd_t *cmd, fc_unsol_buf_t *buf, uchar_t action, uchar_t reason, job_request_t *job) { fc_packet_t *pkt; la_els_rjt_t payload; ASSERT(!MUTEX_HELD(&port->fp_mutex)); cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); payload.ls_code.ls_code = LA_ELS_RJT; payload.ls_code.mbz = 0; payload.action = action; payload.reason = reason; payload.reserved = 0; payload.vu = 0; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Function: fp_prlo_acc_init * * Description: Initializes an Link Service Accept for a PRLO. * * Arguments: *port Local port through which the PRLO was * received. * cmd Command that will carry the accept. * *buf Unsolicited buffer containing the PRLO * request. * job Job request. * sleep Allocation mode. * * Return Value: *cmd Command containing the response. * * Context: Depends on the parameter sleep. */ fp_cmd_t * fp_prlo_acc_init(fc_local_port_t *port, fc_remote_port_t *pd, fc_unsol_buf_t *buf, job_request_t *job, int sleep) { fp_cmd_t *cmd; fc_packet_t *pkt; la_els_prlo_t *req; size_t len; uint16_t flags; req = (la_els_prlo_t *)buf->ub_buffer; len = (size_t)ntohs(req->payload_length); /* * The payload of the accept to a PRLO has to be the exact match of * the payload of the request (at the exception of the code). */ cmd = fp_alloc_pkt(port, (int)len, 0, sleep, pd); if (cmd) { /* * The fp command was successfully allocated. */ cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); /* The code is overwritten for the copy. */ req->ls_code = LA_ELS_ACC; /* Response code is set. */ flags = ntohs(req->flags); flags &= ~SP_RESP_CODE_MASK; flags |= SP_RESP_CODE_REQ_EXECUTED; req->flags = htons(flags); FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)req, (uint8_t *)pkt->pkt_cmd, len, DDI_DEV_AUTOINCR); } return (cmd); } /* * Prepare an ACC response to an ELS request */ static void fp_els_acc_init(fc_local_port_t *port, fp_cmd_t *cmd, fc_unsol_buf_t *buf, job_request_t *job) { fc_packet_t *pkt; ls_code_t payload; cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); payload.ls_code = LA_ELS_ACC; payload.mbz = 0; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Unsolicited PRLO handler * * A Process Logout should be handled by the ULP that established it. However, * some devices send a PRLO to trigger a PLOGI followed by a PRLI. This happens * when a device implicitly logs out an initiator (for whatever reason) and * tries to get that initiator to restablish the connection (PLOGI and PRLI). * The logical thing to do for the device would be to send a LOGO in response * to any FC4 frame sent by the initiator. Some devices choose, however, to send * a PRLO instead. * * From a Fibre Channel standpoint a PRLO calls for a PRLI. There's no reason to * think that the Port Login has been lost. If we follow the Fibre Channel * protocol to the letter a PRLI should be sent after accepting the PRLO. If * the Port Login has also been lost, the remote port will reject the PRLI * indicating that we must PLOGI first. The initiator will then turn around and * send a PLOGI. The way Leadville is layered and the way the ULP interface * is defined doesn't allow this scenario to be followed easily. If FCP were to * handle the PRLO and attempt the PRLI, the reject indicating that a PLOGI is * needed would be received by FCP. FCP would have, then, to tell the transport * (fp) to PLOGI. The problem is, the transport would still think the Port * Login is valid and there is no way for FCP to tell the transport: "PLOGI even * if you think it's not necessary". To work around that difficulty, the PRLO * is treated by the transport as a LOGO. The downside to it is a Port Login * may be disrupted (if a PLOGI wasn't actually needed) and another ULP (that * has nothing to do with the PRLO) may be impacted. However, this is a * scenario very unlikely to happen. As of today the only ULP in Leadville * using PRLI/PRLOs is FCP. For a PRLO to disrupt another ULP (that would be * FCIP), a SCSI target would have to be running FCP and FCIP (which is very * unlikely). */ static void fp_handle_unsol_prlo(fc_local_port_t *port, fc_unsol_buf_t *buf, fc_remote_port_t *pd, job_request_t *job) { int busy; int rval; int retain; fp_cmd_t *cmd; fc_portmap_t *listptr; boolean_t tolerance; la_els_prlo_t *req; req = (la_els_prlo_t *)buf->ub_buffer; if ((ntohs(req->payload_length) != (sizeof (service_parameter_page_t) + sizeof (ls_code_t))) || (req->page_length != sizeof (service_parameter_page_t))) { /* * We are being very restrictive. Only on page per * payload. If it is not the case we reject the ELS although * we should reply indicating we handle only single page * per PRLO. */ goto fp_reject_prlo; } if (ntohs(req->payload_length) > buf->ub_bufsize) { /* * This is in case the payload advertizes a size bigger than * what it really is. */ goto fp_reject_prlo; } mutex_enter(&port->fp_mutex); busy = port->fp_statec_busy; mutex_exit(&port->fp_mutex); mutex_enter(&pd->pd_mutex); tolerance = fctl_tc_increment(&pd->pd_logo_tc); if (!busy) { if (pd->pd_state != PORT_DEVICE_LOGGED_IN || pd->pd_state == PORT_DEVICE_INVALID || pd->pd_flags == PD_ELS_IN_PROGRESS || pd->pd_type == PORT_DEVICE_OLD) { busy++; } } if (busy) { mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(5, 0), "Logout; D_ID=%x," "pd=%p - busy", pd->pd_port_id.port_id, pd); if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { goto fp_reject_prlo; } } else { retain = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; if (tolerance) { fctl_tc_reset(&pd->pd_logo_tc); retain = 0; pd->pd_state = PORT_DEVICE_INVALID; } FP_TRACE(FP_NHEAD1(5, 0), "Accepting LOGO; d_id=%x, pd=%p," " tolerance=%d retain=%d", pd->pd_port_id.port_id, pd, tolerance, retain); pd->pd_aux_flags |= PD_LOGGED_OUT; mutex_exit(&pd->pd_mutex); cmd = fp_prlo_acc_init(port, pd, buf, job, KM_SLEEP); if (cmd == NULL) { return; } rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { fp_free_pkt(cmd); return; } listptr = kmem_zalloc(sizeof (fc_portmap_t), KM_SLEEP); if (retain) { fp_unregister_login(pd); fctl_copy_portmap(listptr, pd); } else { uint32_t d_id; char ww_name[17]; mutex_enter(&pd->pd_mutex); d_id = pd->pd_port_id.port_id; fc_wwn_to_str(&pd->pd_port_name, ww_name); mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s logged out" " %d times in %d us; Giving up", d_id, ww_name, FC_LOGO_TOLERANCE_LIMIT, FC_LOGO_TOLERANCE_TIME_LIMIT); fp_fillout_old_map(listptr, pd, 0); listptr->map_type = PORT_DEVICE_OLD; } (void) fp_ulp_devc_cb(port, listptr, 1, 1, KM_SLEEP, 0); return; } fp_reject_prlo: cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, pd); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } /* * Unsolicited LOGO handler */ static void fp_handle_unsol_logo(fc_local_port_t *port, fc_unsol_buf_t *buf, fc_remote_port_t *pd, job_request_t *job) { int busy; int rval; int retain; fp_cmd_t *cmd; fc_portmap_t *listptr; boolean_t tolerance; mutex_enter(&port->fp_mutex); busy = port->fp_statec_busy; mutex_exit(&port->fp_mutex); mutex_enter(&pd->pd_mutex); tolerance = fctl_tc_increment(&pd->pd_logo_tc); if (!busy) { if (pd->pd_state != PORT_DEVICE_LOGGED_IN || pd->pd_state == PORT_DEVICE_INVALID || pd->pd_flags == PD_ELS_IN_PROGRESS || pd->pd_type == PORT_DEVICE_OLD) { busy++; } } if (busy) { mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(5, 0), "Logout; D_ID=%x," "pd=%p - busy", pd->pd_port_id.port_id, pd); if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, pd); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } } else { retain = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; if (tolerance) { fctl_tc_reset(&pd->pd_logo_tc); retain = 0; pd->pd_state = PORT_DEVICE_INVALID; } FP_TRACE(FP_NHEAD1(5, 0), "Accepting LOGO; d_id=%x, pd=%p," " tolerance=%d retain=%d", pd->pd_port_id.port_id, pd, tolerance, retain); pd->pd_aux_flags |= PD_LOGGED_OUT; mutex_exit(&pd->pd_mutex); cmd = fp_alloc_pkt(port, FP_PORT_IDENTIFIER_LEN, 0, KM_SLEEP, pd); if (cmd == NULL) { return; } fp_els_acc_init(port, cmd, buf, job); rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { fp_free_pkt(cmd); return; } listptr = kmem_zalloc(sizeof (fc_portmap_t), KM_SLEEP); if (retain) { job_request_t *job; fctl_ns_req_t *ns_cmd; /* * when get LOGO, first try to get PID from nameserver * if failed, then we do not need * send PLOGI to that remote port */ job = fctl_alloc_job( JOB_NS_CMD, 0, NULL, (opaque_t)port, KM_SLEEP); if (job != NULL) { ns_cmd = fctl_alloc_ns_cmd( sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), 0, KM_SLEEP); if (ns_cmd != NULL) { int ret; job->job_result = FC_SUCCESS; ns_cmd->ns_cmd_code = NS_GID_PN; ((ns_req_gid_pn_t *) (ns_cmd->ns_cmd_buf))->pwwn = pd->pd_port_name; ret = fp_ns_query( port, ns_cmd, job, 1, KM_SLEEP); if ((ret != FC_SUCCESS) || (job->job_result != FC_SUCCESS)) { fctl_free_ns_cmd(ns_cmd); fctl_dealloc_job(job); FP_TRACE(FP_NHEAD2(9, 0), "NS query failed,", " delete pd"); goto delete_pd; } fctl_free_ns_cmd(ns_cmd); } fctl_dealloc_job(job); } fp_unregister_login(pd); fctl_copy_portmap(listptr, pd); } else { uint32_t d_id; char ww_name[17]; delete_pd: mutex_enter(&pd->pd_mutex); d_id = pd->pd_port_id.port_id; fc_wwn_to_str(&pd->pd_port_name, ww_name); mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s logged out" " %d times in %d us; Giving up", d_id, ww_name, FC_LOGO_TOLERANCE_LIMIT, FC_LOGO_TOLERANCE_TIME_LIMIT); fp_fillout_old_map(listptr, pd, 0); listptr->map_type = PORT_DEVICE_OLD; } (void) fp_ulp_devc_cb(port, listptr, 1, 1, KM_SLEEP, 0); } } /* * Perform general purpose preparation of a response to an unsolicited request */ static void fp_unsol_resp_init(fc_packet_t *pkt, fc_unsol_buf_t *buf, uchar_t r_ctl, uchar_t type) { pkt->pkt_cmd_fhdr.r_ctl = r_ctl; pkt->pkt_cmd_fhdr.d_id = buf->ub_frame.s_id; pkt->pkt_cmd_fhdr.s_id = buf->ub_frame.d_id; pkt->pkt_cmd_fhdr.type = type; pkt->pkt_cmd_fhdr.f_ctl = F_CTL_LAST_SEQ | F_CTL_XCHG_CONTEXT; pkt->pkt_cmd_fhdr.seq_id = buf->ub_frame.seq_id; pkt->pkt_cmd_fhdr.df_ctl = buf->ub_frame.df_ctl; pkt->pkt_cmd_fhdr.seq_cnt = buf->ub_frame.seq_cnt; pkt->pkt_cmd_fhdr.ox_id = buf->ub_frame.ox_id; pkt->pkt_cmd_fhdr.rx_id = buf->ub_frame.rx_id; pkt->pkt_cmd_fhdr.ro = 0; pkt->pkt_cmd_fhdr.rsvd = 0; pkt->pkt_comp = fp_unsol_intr; pkt->pkt_timeout = FP_ELS_TIMEOUT; pkt->pkt_ub_resp_token = (opaque_t)buf; } /* * Immediate handling of unsolicited FLOGI and PLOGI requests. In the * early development days of public loop soc+ firmware, numerous problems * were encountered (the details are undocumented and history now) which * led to the birth of this function. * * If a pre-allocated unsolicited response packet is free, send out an * immediate response, otherwise submit the request to the port thread * to do the deferred processing. */ static void fp_i_handle_unsol_els(fc_local_port_t *port, fc_unsol_buf_t *buf) { int sent; int f_port; int do_acc; fp_cmd_t *cmd; la_els_logi_t *payload; fc_remote_port_t *pd; char dww_name[17]; ASSERT(!MUTEX_HELD(&port->fp_mutex)); cmd = port->fp_els_resp_pkt; mutex_enter(&port->fp_mutex); do_acc = (port->fp_statec_busy == 0) ? 1 : 0; mutex_exit(&port->fp_mutex); switch (buf->ub_buffer[0]) { case LA_ELS_PLOGI: { int small; payload = (la_els_logi_t *)buf->ub_buffer; f_port = FP_IS_F_PORT(payload-> common_service.cmn_features) ? 1 : 0; small = fctl_wwn_cmp(&port->fp_service_params.nport_ww_name, &payload->nport_ww_name); pd = fctl_get_remote_port_by_pwwn(port, &payload->nport_ww_name); if (pd) { mutex_enter(&pd->pd_mutex); sent = (pd->pd_flags == PD_ELS_IN_PROGRESS) ? 1 : 0; /* * Most likely this means a cross login is in * progress or a device about to be yanked out. * Only accept the plogi if my wwn is smaller. */ if (pd->pd_type == PORT_DEVICE_OLD) { sent = 1; } /* * Stop plogi request (if any) * attempt from local side to speedup * the discovery progress. * Mark the pd as PD_PLOGI_RECEPIENT. */ if (f_port == 0 && small < 0) { pd->pd_recepient = PD_PLOGI_RECEPIENT; } fc_wwn_to_str(&pd->pd_port_name, dww_name); mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Unsol PLOGI received. PD still exists in the " "PWWN list. pd=%p PWWN=%s, sent=%x", pd, dww_name, sent); if (f_port == 0 && small < 0) { FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: Mark the pd" " as plogi recipient, pd=%p, PWWN=%s" ", sent=%x", pd, dww_name, sent); } } else { sent = 0; } /* * To avoid Login collisions, accept only if my WWN * is smaller than the requester (A curious side note * would be that this rule may not satisfy the PLOGIs * initiated by the switch from not-so-well known * ports such as 0xFFFC41) */ if ((f_port == 0 && small < 0) || (((small > 0 && do_acc) || FC_MUST_ACCEPT_D_ID(buf->ub_frame.s_id)) && sent == 0)) { if (fp_is_class_supported(port->fp_cos, buf->ub_class) == FC_FAILURE) { if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_CLASS_NOT_SUPP, NULL); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Unsupported class. " "Rejecting PLOGI"); } else { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); return; } } else { cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_logi_t); cmd->cmd_pkt.pkt_rsplen = 0; /* * If fp_port_id is zero and topology is * Point-to-Point, get the local port id from * the d_id in the PLOGI request. * If the outgoing FLOGI hasn't been accepted, * the topology will be unknown here. But it's * still safe to save the d_id to fp_port_id, * just because it will be overwritten later * if the topology is not Point-to-Point. */ mutex_enter(&port->fp_mutex); if ((port->fp_port_id.port_id == 0) && (port->fp_topology == FC_TOP_PT_PT || port->fp_topology == FC_TOP_UNKNOWN)) { port->fp_port_id.port_id = buf->ub_frame.d_id; } mutex_exit(&port->fp_mutex); /* * Sometime later, we should validate * the service parameters instead of * just accepting it. */ fp_login_acc_init(port, cmd, buf, NULL, KM_NOSLEEP); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: Accepting PLOGI," " f_port=%d, small=%d, do_acc=%d," " sent=%d.", f_port, small, do_acc, sent); } } else { if (FP_IS_CLASS_1_OR_2(buf->ub_class) || port->fp_options & FP_SEND_RJT) { cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_LOGICAL_BSY, NULL); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Rejecting PLOGI with Logical Busy." "Possible Login collision."); } else { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); return; } } break; } case LA_ELS_FLOGI: if (fp_is_class_supported(port->fp_cos, buf->ub_class) == FC_FAILURE) { if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_CLASS_NOT_SUPP, NULL); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Unsupported Class. Rejecting FLOGI."); } else { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); return; } } else { mutex_enter(&port->fp_mutex); if (FC_PORT_STATE_MASK(port->fp_state) != FC_STATE_ONLINE || (port->fp_port_id.port_id && buf->ub_frame.s_id == port->fp_port_id.port_id)) { mutex_exit(&port->fp_mutex); if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, NULL); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Invalid Link Ctrl. " "Rejecting FLOGI."); } else { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); return; } } else { mutex_exit(&port->fp_mutex); cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_logi_t); cmd->cmd_pkt.pkt_rsplen = 0; /* * Let's not aggressively validate the N_Port's * service parameters until PLOGI. Suffice it * to give a hint that we are an N_Port and we * are game to some serious stuff here. */ fp_login_acc_init(port, cmd, buf, NULL, KM_NOSLEEP); FP_TRACE(FP_NHEAD1(3, 0), "fp_i_handle_unsol_els: " "Accepting FLOGI."); } } break; default: return; } if ((fp_sendcmd(port, cmd, port->fp_fca_handle)) != FC_SUCCESS) { mutex_enter(&port->fp_mutex); port->fp_els_resp_pkt_busy = 0; mutex_exit(&port->fp_mutex); } } /* * Handle unsolicited PLOGI request */ static void fp_handle_unsol_plogi(fc_local_port_t *port, fc_unsol_buf_t *buf, job_request_t *job, int sleep) { int sent; int small; int f_port; int do_acc; fp_cmd_t *cmd; la_wwn_t *swwn; la_wwn_t *dwwn; la_els_logi_t *payload; fc_remote_port_t *pd; char dww_name[17]; payload = (la_els_logi_t *)buf->ub_buffer; f_port = FP_IS_F_PORT(payload->common_service.cmn_features) ? 1 : 0; mutex_enter(&port->fp_mutex); do_acc = (port->fp_statec_busy == 0) ? 1 : 0; mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: s_id=%x, d_id=%x," "type=%x, f_ctl=%x" " seq_id=%x, ox_id=%x, rx_id=%x" " ro=%x", buf->ub_frame.s_id, buf->ub_frame.d_id, buf->ub_frame.type, buf->ub_frame.f_ctl, buf->ub_frame.seq_id, buf->ub_frame.ox_id, buf->ub_frame.rx_id, buf->ub_frame.ro); swwn = &port->fp_service_params.nport_ww_name; dwwn = &payload->nport_ww_name; small = fctl_wwn_cmp(swwn, dwwn); pd = fctl_get_remote_port_by_pwwn(port, dwwn); if (pd) { mutex_enter(&pd->pd_mutex); sent = (pd->pd_flags == PD_ELS_IN_PROGRESS) ? 1 : 0; /* * Most likely this means a cross login is in * progress or a device about to be yanked out. * Only accept the plogi if my wwn is smaller. */ if (pd->pd_type == PORT_DEVICE_OLD) { sent = 1; } /* * Stop plogi request (if any) * attempt from local side to speedup * the discovery progress. * Mark the pd as PD_PLOGI_RECEPIENT. */ if (f_port == 0 && small < 0) { pd->pd_recepient = PD_PLOGI_RECEPIENT; } fc_wwn_to_str(&pd->pd_port_name, dww_name); mutex_exit(&pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: Unsol PLOGI" " received. PD still exists in the PWWN list. pd=%p " "PWWN=%s, sent=%x", pd, dww_name, sent); if (f_port == 0 && small < 0) { FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: Mark the pd" " as plogi recipient, pd=%p, PWWN=%s" ", sent=%x", pd, dww_name, sent); } } else { sent = 0; } /* * Avoid Login collisions by accepting only if my WWN is smaller. * * A side note: There is no need to start a PLOGI from this end in * this context if login isn't going to be accepted for the * above reason as either a LIP (in private loop), RSCN (in * fabric topology), or an FLOGI (in point to point - Huh ? * check FC-PH) would normally drive the PLOGI from this end. * At this point of time there is no need for an inbound PLOGI * to kick an outbound PLOGI when it is going to be rejected * for the reason of WWN being smaller. However it isn't hard * to do that either (when such a need arises, start a timer * for a duration that extends beyond a normal device discovery * time and check if an outbound PLOGI did go before that, if * none fire one) * * Unfortunately, as it turned out, during booting, it is possible * to miss another initiator in the same loop as port driver * instances are serially attached. While preserving the above * comments for belly laughs, please kick an outbound PLOGI in * a non-switch environment (which is a pt pt between N_Ports or * a private loop) * * While preserving the above comments for amusement, send an * ACC if the PLOGI is going to be rejected for WWN being smaller * when no discovery is in progress at this end. Turn around * and make the port device as the PLOGI initiator, so that * during subsequent link/loop initialization, this end drives * the PLOGI (In fact both ends do in this particular case, but * only one wins) * * Make sure the PLOGIs initiated by the switch from not-so-well-known * ports (such as 0xFFFC41) are accepted too. */ if ((f_port == 0 && small < 0) || (((small > 0 && do_acc) || FC_MUST_ACCEPT_D_ID(buf->ub_frame.s_id)) && sent == 0)) { if (fp_is_class_supported(port->fp_cos, buf->ub_class) == FC_FAILURE) { if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), 0, sleep, pd); if (cmd == NULL) { return; } cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_CLASS_NOT_SUPP, job); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: " "Unsupported class. rejecting PLOGI"); } } else { cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), 0, sleep, pd); if (cmd == NULL) { return; } cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_logi_t); cmd->cmd_pkt.pkt_rsplen = 0; /* * Sometime later, we should validate the service * parameters instead of just accepting it. */ fp_login_acc_init(port, cmd, buf, job, KM_SLEEP); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: " "Accepting PLOGI, f_port=%d, small=%d, " "do_acc=%d, sent=%d.", f_port, small, do_acc, sent); /* * If fp_port_id is zero and topology is * Point-to-Point, get the local port id from * the d_id in the PLOGI request. * If the outgoing FLOGI hasn't been accepted, * the topology will be unknown here. But it's * still safe to save the d_id to fp_port_id, * just because it will be overwritten later * if the topology is not Point-to-Point. */ mutex_enter(&port->fp_mutex); if ((port->fp_port_id.port_id == 0) && (port->fp_topology == FC_TOP_PT_PT || port->fp_topology == FC_TOP_UNKNOWN)) { port->fp_port_id.port_id = buf->ub_frame.d_id; } mutex_exit(&port->fp_mutex); } } else { if (FP_IS_CLASS_1_OR_2(buf->ub_class) || port->fp_options & FP_SEND_RJT) { cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), 0, sleep, pd); if (cmd == NULL) { return; } cmd->cmd_pkt.pkt_cmdlen = sizeof (la_els_rjt_t); cmd->cmd_pkt.pkt_rsplen = 0; /* * Send out Logical busy to indicate * the detection of PLOGI collision */ fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_LOGICAL_BSY, job); fc_wwn_to_str(dwwn, dww_name); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_plogi: " "Rejecting Unsol PLOGI with Logical Busy." "possible PLOGI collision. PWWN=%s, sent=%x", dww_name, sent); } else { return; } } if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } /* * Handle mischievous turning over of our own FLOGI requests back to * us by the SOC+ microcode. In other words, look at the class of such * bone headed requests, if 1 or 2, bluntly P_RJT them, if 3 drop them * on the floor */ static void fp_handle_unsol_flogi(fc_local_port_t *port, fc_unsol_buf_t *buf, job_request_t *job, int sleep) { uint32_t state; uint32_t s_id; fp_cmd_t *cmd; if (fp_is_class_supported(port->fp_cos, buf->ub_class) == FC_FAILURE) { if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, sleep, NULL); if (cmd == NULL) { return; } fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_CLASS_NOT_SUPP, job); } else { return; } } else { FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_flogi:" " s_id=%x, d_id=%x, type=%x, f_ctl=%x" " seq_id=%x, ox_id=%x, rx_id=%x, ro=%x", buf->ub_frame.s_id, buf->ub_frame.d_id, buf->ub_frame.type, buf->ub_frame.f_ctl, buf->ub_frame.seq_id, buf->ub_frame.ox_id, buf->ub_frame.rx_id, buf->ub_frame.ro); mutex_enter(&port->fp_mutex); state = FC_PORT_STATE_MASK(port->fp_state); s_id = port->fp_port_id.port_id; mutex_exit(&port->fp_mutex); if (state != FC_STATE_ONLINE || (s_id && buf->ub_frame.s_id == s_id)) { if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, sleep, NULL); if (cmd == NULL) { return; } fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_flogi: " "Rejecting PLOGI. Invalid Link CTRL"); } else { return; } } else { cmd = fp_alloc_pkt(port, sizeof (la_els_logi_t), 0, sleep, NULL); if (cmd == NULL) { return; } /* * Let's not aggressively validate the N_Port's * service parameters until PLOGI. Suffice it * to give a hint that we are an N_Port and we * are game to some serious stuff here. */ fp_login_acc_init(port, cmd, buf, job, KM_SLEEP); FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_flogi: " "Accepting PLOGI"); } } if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } /* * Perform PLOGI accept */ static void fp_login_acc_init(fc_local_port_t *port, fp_cmd_t *cmd, fc_unsol_buf_t *buf, job_request_t *job, int sleep) { fc_packet_t *pkt; fc_portmap_t *listptr; la_els_logi_t payload; ASSERT(buf != NULL); /* * If we are sending ACC to PLOGI and we haven't already * create port and node device handles, let's create them * here. */ if (buf->ub_buffer[0] == LA_ELS_PLOGI && FC_IS_REAL_DEVICE(buf->ub_frame.s_id)) { int small; int do_acc; fc_remote_port_t *pd; la_els_logi_t *req; req = (la_els_logi_t *)buf->ub_buffer; small = fctl_wwn_cmp(&port->fp_service_params.nport_ww_name, &req->nport_ww_name); mutex_enter(&port->fp_mutex); do_acc = (port->fp_statec_busy == 0) ? 1 : 0; mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD1(3, 0), "fp_plogi_acc_init fp %x, pd %x", port->fp_port_id.port_id, buf->ub_frame.s_id); pd = fctl_create_remote_port(port, &req->node_ww_name, &req->nport_ww_name, buf->ub_frame.s_id, PD_PLOGI_RECEPIENT, sleep); if (pd == NULL) { FP_TRACE(FP_NHEAD1(3, 0), "login_acc_init: " "Couldn't create port device for d_id:0x%x", buf->ub_frame.s_id); fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "couldn't create port device d_id=%x", buf->ub_frame.s_id); } else { /* * usoc currently returns PLOGIs inline and * the maximum buffer size is 60 bytes or so. * So attempt not to look beyond what is in * the unsolicited buffer * * JNI also traverses this path sometimes */ if (buf->ub_bufsize >= sizeof (la_els_logi_t)) { fp_register_login(NULL, pd, req, buf->ub_class); } else { mutex_enter(&pd->pd_mutex); if (pd->pd_login_count == 0) { pd->pd_login_count++; } pd->pd_state = PORT_DEVICE_LOGGED_IN; pd->pd_login_class = buf->ub_class; mutex_exit(&pd->pd_mutex); } listptr = kmem_zalloc(sizeof (fc_portmap_t), sleep); if (listptr != NULL) { fctl_copy_portmap(listptr, pd); (void) fp_ulp_devc_cb(port, listptr, 1, 1, sleep, 0); } if (small > 0 && do_acc) { mutex_enter(&pd->pd_mutex); pd->pd_recepient = PD_PLOGI_INITIATOR; mutex_exit(&pd->pd_mutex); } } } cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); payload = port->fp_service_params; payload.ls_code.ls_code = LA_ELS_ACC; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); FP_TRACE(FP_NHEAD1(3, 0), "login_acc_init: ELS:0x%x d_id:0x%x " "bufsize:0x%x sizeof (la_els_logi):0x%x " "port's wwn:0x%01x%03x%04x%08x requestor's wwn:0x%01x%03x%04x%08x " "statec_busy:0x%x", buf->ub_buffer[0], buf->ub_frame.s_id, buf->ub_bufsize, sizeof (la_els_logi_t), port->fp_service_params.nport_ww_name.w.naa_id, port->fp_service_params.nport_ww_name.w.nport_id, port->fp_service_params.nport_ww_name.w.wwn_hi, port->fp_service_params.nport_ww_name.w.wwn_lo, ((la_els_logi_t *)buf->ub_buffer)->nport_ww_name.w.naa_id, ((la_els_logi_t *)buf->ub_buffer)->nport_ww_name.w.nport_id, ((la_els_logi_t *)buf->ub_buffer)->nport_ww_name.w.wwn_hi, ((la_els_logi_t *)buf->ub_buffer)->nport_ww_name.w.wwn_lo, port->fp_statec_busy); } #define RSCN_EVENT_NAME_LEN 256 /* * Handle RSCNs */ static void fp_handle_unsol_rscn(fc_local_port_t *port, fc_unsol_buf_t *buf, job_request_t *job, int sleep) { uint32_t mask; fp_cmd_t *cmd; uint32_t count; int listindex; int16_t len; fc_rscn_t *payload; fc_portmap_t *listptr; fctl_ns_req_t *ns_cmd; fc_affected_id_t *page; caddr_t nvname; nvlist_t *attr_list = NULL; mutex_enter(&port->fp_mutex); if (!FC_IS_TOP_SWITCH(port->fp_topology)) { if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); return; } mutex_exit(&port->fp_mutex); cmd = fp_alloc_pkt(port, FP_PORT_IDENTIFIER_LEN, 0, sleep, NULL); if (cmd != NULL) { fp_els_acc_init(port, cmd, buf, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } payload = (fc_rscn_t *)buf->ub_buffer; ASSERT(payload->rscn_code == LA_ELS_RSCN); ASSERT(payload->rscn_len == FP_PORT_IDENTIFIER_LEN); len = payload->rscn_payload_len - FP_PORT_IDENTIFIER_LEN; if (len <= 0) { mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); return; } ASSERT((len & 0x3) == 0); /* Must be power of 4 */ count = (len >> 2) << 1; /* number of pages multiplied by 2 */ listptr = kmem_zalloc(sizeof (fc_portmap_t) * count, sleep); page = (fc_affected_id_t *)(buf->ub_buffer + sizeof (fc_rscn_t)); ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gpn_id_t), sizeof (ns_resp_gpn_id_t), sizeof (ns_resp_gpn_id_t), 0, sleep); if (ns_cmd == NULL) { kmem_free(listptr, sizeof (fc_portmap_t) * count); mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); return; } ns_cmd->ns_cmd_code = NS_GPN_ID; FP_TRACE(FP_NHEAD1(3, 0), "fp_handle_unsol_rscn: s_id=%x, d_id=%x," "type=%x, f_ctl=%x seq_id=%x, ox_id=%x, rx_id=%x" " ro=%x", buf->ub_frame.s_id, buf->ub_frame.d_id, buf->ub_frame.type, buf->ub_frame.f_ctl, buf->ub_frame.seq_id, buf->ub_frame.ox_id, buf->ub_frame.rx_id, buf->ub_frame.ro); /* Only proceed if we can allocate nvname and the nvlist */ if ((nvname = kmem_zalloc(RSCN_EVENT_NAME_LEN, KM_NOSLEEP)) != NULL && nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_NOSLEEP) == DDI_SUCCESS) { if (!(attr_list && nvlist_add_uint32(attr_list, "instance", port->fp_instance) == DDI_SUCCESS && nvlist_add_byte_array(attr_list, "port-wwn", port->fp_service_params.nport_ww_name.raw_wwn, sizeof (la_wwn_t)) == DDI_SUCCESS)) { nvlist_free(attr_list); attr_list = NULL; } } for (listindex = 0; len; len -= FP_PORT_IDENTIFIER_LEN, page++) { /* Add affected page to the event payload */ if (attr_list != NULL) { (void) snprintf(nvname, RSCN_EVENT_NAME_LEN, "affected_page_%d", listindex); if (attr_list && nvlist_add_uint32(attr_list, nvname, ntohl(*(uint32_t *)page)) != DDI_SUCCESS) { /* We don't send a partial event, so dump it */ nvlist_free(attr_list); attr_list = NULL; } } /* * Query the NS to get the Port WWN for this * affected D_ID. */ mask = 0; switch (page->aff_format & FC_RSCN_ADDRESS_MASK) { case FC_RSCN_PORT_ADDRESS: fp_validate_rscn_page(port, page, job, ns_cmd, listptr, &listindex, sleep); if (listindex == 0) { /* * We essentially did not process this RSCN. So, * ULPs are not going to be called and so we * decrement the rscn_count */ mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); } break; case FC_RSCN_AREA_ADDRESS: mask = 0xFFFF00; /* FALLTHROUGH */ case FC_RSCN_DOMAIN_ADDRESS: if (!mask) { mask = 0xFF0000; } fp_validate_area_domain(port, page->aff_d_id, mask, job, sleep); break; case FC_RSCN_FABRIC_ADDRESS: /* * We need to discover all the devices on this * port. */ fp_validate_area_domain(port, 0, 0, job, sleep); break; default: break; } } if (attr_list != NULL) { (void) ddi_log_sysevent(port->fp_port_dip, DDI_VENDOR_SUNW, EC_SUNFC, ESC_SUNFC_PORT_RSCN, attr_list, NULL, DDI_SLEEP); nvlist_free(attr_list); } else { FP_TRACE(FP_NHEAD1(9, 0), "RSCN handled, but event not sent to userland"); } if (nvname != NULL) { kmem_free(nvname, RSCN_EVENT_NAME_LEN); } if (ns_cmd) { fctl_free_ns_cmd(ns_cmd); } if (listindex) { #ifdef DEBUG page = (fc_affected_id_t *)(buf->ub_buffer + sizeof (fc_rscn_t)); if (listptr->map_did.port_id != page->aff_d_id) { FP_TRACE(FP_NHEAD1(9, 0), "PORT RSCN: processed=%x, reporting=%x", listptr->map_did.port_id, page->aff_d_id); } #endif (void) fp_ulp_devc_cb(port, listptr, listindex, count, sleep, 0); } else { kmem_free(listptr, sizeof (fc_portmap_t) * count); } } /* * Fill out old map for ULPs with fp_mutex, fd_mutex and pd_mutex held */ static void fp_fillout_old_map_held(fc_portmap_t *map, fc_remote_port_t *pd, uchar_t flag) { int is_switch; int initiator; fc_local_port_t *port; port = pd->pd_port; /* This function has the following bunch of assumptions */ ASSERT(port != NULL); ASSERT(MUTEX_HELD(&port->fp_mutex)); ASSERT(MUTEX_HELD(&pd->pd_remote_nodep->fd_mutex)); ASSERT(MUTEX_HELD(&pd->pd_mutex)); pd->pd_state = PORT_DEVICE_INVALID; pd->pd_type = PORT_DEVICE_OLD; initiator = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; is_switch = FC_IS_TOP_SWITCH(port->fp_topology); fctl_delist_did_table(port, pd); fctl_delist_pwwn_table(port, pd); FP_TRACE(FP_NHEAD1(6, 0), "fp_fillout_old_map_held: port=%p, d_id=%x" " removed the PD=%p from DID and PWWN tables", port, pd->pd_port_id.port_id, pd); if ((!flag) && port && initiator && is_switch) { (void) fctl_add_orphan_held(port, pd); } fctl_copy_portmap_held(map, pd); map->map_pd = pd; } /* * Fill out old map for ULPs */ static void fp_fillout_old_map(fc_portmap_t *map, fc_remote_port_t *pd, uchar_t flag) { int is_switch; int initiator; fc_local_port_t *port; mutex_enter(&pd->pd_mutex); port = pd->pd_port; mutex_exit(&pd->pd_mutex); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); pd->pd_state = PORT_DEVICE_INVALID; pd->pd_type = PORT_DEVICE_OLD; initiator = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; is_switch = FC_IS_TOP_SWITCH(port->fp_topology); fctl_delist_did_table(port, pd); fctl_delist_pwwn_table(port, pd); FP_TRACE(FP_NHEAD1(6, 0), "fp_fillout_old_map: port=%p, d_id=%x" " removed the PD=%p from DID and PWWN tables", port, pd->pd_port_id.port_id, pd); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); ASSERT(port != NULL); if ((!flag) && port && initiator && is_switch) { (void) fctl_add_orphan(port, pd, KM_NOSLEEP); } fctl_copy_portmap(map, pd); map->map_pd = pd; } /* * Fillout Changed Map for ULPs */ static void fp_fillout_changed_map(fc_portmap_t *map, fc_remote_port_t *pd, uint32_t *new_did, la_wwn_t *new_pwwn) { ASSERT(MUTEX_HELD(&pd->pd_mutex)); pd->pd_type = PORT_DEVICE_CHANGED; if (new_did) { pd->pd_port_id.port_id = *new_did; } if (new_pwwn) { pd->pd_port_name = *new_pwwn; } mutex_exit(&pd->pd_mutex); fctl_copy_portmap(map, pd); mutex_enter(&pd->pd_mutex); pd->pd_type = PORT_DEVICE_NOCHANGE; } /* * Fillout New Name Server map */ static void fp_fillout_new_nsmap(fc_local_port_t *port, ddi_acc_handle_t *handle, fc_portmap_t *port_map, ns_resp_gan_t *gan_resp, uint32_t d_id) { ASSERT(!MUTEX_HELD(&port->fp_mutex)); if (handle) { FC_GET_RSP(port, *handle, (uint8_t *)&port_map->map_pwwn, (uint8_t *)&gan_resp->gan_pwwn, sizeof (gan_resp->gan_pwwn), DDI_DEV_AUTOINCR); FC_GET_RSP(port, *handle, (uint8_t *)&port_map->map_nwwn, (uint8_t *)&gan_resp->gan_nwwn, sizeof (gan_resp->gan_nwwn), DDI_DEV_AUTOINCR); FC_GET_RSP(port, *handle, (uint8_t *)port_map->map_fc4_types, (uint8_t *)gan_resp->gan_fc4types, sizeof (gan_resp->gan_fc4types), DDI_DEV_AUTOINCR); } else { bcopy(&gan_resp->gan_pwwn, &port_map->map_pwwn, sizeof (gan_resp->gan_pwwn)); bcopy(&gan_resp->gan_nwwn, &port_map->map_nwwn, sizeof (gan_resp->gan_nwwn)); bcopy(gan_resp->gan_fc4types, port_map->map_fc4_types, sizeof (gan_resp->gan_fc4types)); } port_map->map_did.port_id = d_id; port_map->map_did.priv_lilp_posit = 0; port_map->map_hard_addr.hard_addr = 0; port_map->map_hard_addr.rsvd = 0; port_map->map_state = PORT_DEVICE_INVALID; port_map->map_type = PORT_DEVICE_NEW; port_map->map_flags = 0; port_map->map_pd = NULL; (void) fctl_remove_if_orphan(port, &port_map->map_pwwn); ASSERT(port != NULL); } /* * Perform LINIT ELS */ static int fp_remote_lip(fc_local_port_t *port, la_wwn_t *pwwn, int sleep, job_request_t *job) { int rval; uint32_t d_id; uint32_t s_id; uint32_t lfa; uchar_t class; uint32_t ret; fp_cmd_t *cmd; fc_porttype_t ptype; fc_packet_t *pkt; fc_linit_req_t payload; fc_remote_port_t *pd; rval = 0; ASSERT(job != NULL); ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); pd = fctl_get_remote_port_by_pwwn(port, pwwn); if (pd == NULL) { fctl_ns_req_t *ns_cmd; ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), 0, sleep); if (ns_cmd == NULL) { return (FC_NOMEM); } job->job_result = FC_SUCCESS; ns_cmd->ns_cmd_code = NS_GID_PN; ((ns_req_gid_pn_t *)(ns_cmd->ns_cmd_buf))->pwwn = *pwwn; ret = fp_ns_query(port, ns_cmd, job, 1, sleep); if (ret != FC_SUCCESS || job->job_result != FC_SUCCESS) { fctl_free_ns_cmd(ns_cmd); return (FC_FAILURE); } bcopy(ns_cmd->ns_data_buf, (caddr_t)&d_id, sizeof (d_id)); d_id = BE_32(*((uint32_t *)ns_cmd->ns_data_buf)); fctl_free_ns_cmd(ns_cmd); lfa = d_id & 0xFFFF00; /* * Given this D_ID, get the port type to see if * we can do LINIT on the LFA */ ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gpt_id_t), sizeof (ns_resp_gpt_id_t), sizeof (ns_resp_gpt_id_t), 0, sleep); if (ns_cmd == NULL) { return (FC_NOMEM); } job->job_result = FC_SUCCESS; ns_cmd->ns_cmd_code = NS_GPT_ID; ((ns_req_gpt_id_t *)(ns_cmd->ns_cmd_buf))->pid.port_id = d_id; ((ns_req_gpt_id_t *) (ns_cmd->ns_cmd_buf))->pid.priv_lilp_posit = 0; ret = fp_ns_query(port, ns_cmd, job, 1, sleep); if (ret != FC_SUCCESS || job->job_result != FC_SUCCESS) { fctl_free_ns_cmd(ns_cmd); return (FC_FAILURE); } bcopy(ns_cmd->ns_data_buf, (caddr_t)&ptype, sizeof (ptype)); fctl_free_ns_cmd(ns_cmd); switch (ptype.port_type) { case FC_NS_PORT_NL: case FC_NS_PORT_F_NL: case FC_NS_PORT_FL: break; default: return (FC_FAILURE); } } else { mutex_enter(&pd->pd_mutex); ptype = pd->pd_porttype; switch (pd->pd_porttype.port_type) { case FC_NS_PORT_NL: case FC_NS_PORT_F_NL: case FC_NS_PORT_FL: lfa = pd->pd_port_id.port_id & 0xFFFF00; break; default: mutex_exit(&pd->pd_mutex); return (FC_FAILURE); } mutex_exit(&pd->pd_mutex); } mutex_enter(&port->fp_mutex); s_id = port->fp_port_id.port_id; class = port->fp_ns_login_class; mutex_exit(&port->fp_mutex); cmd = fp_alloc_pkt(port, sizeof (fc_linit_req_t), sizeof (fc_linit_resp_t), sleep, pd); if (cmd == NULL) { return (FC_NOMEM); } cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = NULL; pkt = &cmd->cmd_pkt; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; fp_els_init(cmd, s_id, lfa, fp_linit_intr, job); /* * How does LIP work by the way ? * If the L_Port receives three consecutive identical ordered * sets whose first two characters (fully decoded) are equal to * the values shown in Table 3 of FC-AL-2 then the L_Port shall * recognize a Loop Initialization Primitive sequence. The * character 3 determines the type of lip: * LIP(F7) Normal LIP * LIP(F8) Loop Failure LIP * * The possible combination for the 3rd and 4th bytes are: * F7, F7 Normal Lip - No valid AL_PA * F8, F8 Loop Failure - No valid AL_PA * F7, AL_PS Normal Lip - Valid source AL_PA * F8, AL_PS Loop Failure - Valid source AL_PA * AL_PD AL_PS Loop reset of AL_PD originated by AL_PS * And Normal Lip for all other loop members * 0xFF AL_PS Vendor specific reset of all loop members * * Now, it may not always be that we, at the source, may have an * AL_PS (AL_PA of source) for 4th character slot, so we decide * to do (Normal Lip, No Valid AL_PA), that means, in the LINIT * payload we are going to set: * lip_b3 = 0xF7; Normal LIP * lip_b4 = 0xF7; No valid source AL_PA */ payload.ls_code.ls_code = LA_ELS_LINIT; payload.ls_code.mbz = 0; payload.rsvd = 0; payload.func = 0; /* Let Fabric determine the best way */ payload.lip_b3 = 0xF7; /* Normal LIP */ payload.lip_b4 = 0xF7; /* No valid source AL_PA */ FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); job->job_counter = 1; ret = fp_sendcmd(port, cmd, port->fp_fca_handle); if (ret == FC_SUCCESS) { fp_jobwait(job); rval = job->job_result; } else { rval = FC_FAILURE; fp_free_pkt(cmd); } return (rval); } /* * Fill out the device handles with GAN response */ static void fp_stuff_device_with_gan(ddi_acc_handle_t *handle, fc_remote_port_t *pd, ns_resp_gan_t *gan_resp) { fc_remote_node_t *node; fc_porttype_t type; fc_local_port_t *port; ASSERT(pd != NULL); ASSERT(handle != NULL); port = pd->pd_port; FP_TRACE(FP_NHEAD1(1, 0), "GAN PD stuffing; pd=%p," " port_id=%x, sym_len=%d fc4-type=%x", pd, gan_resp->gan_type_id.rsvd, gan_resp->gan_spnlen, gan_resp->gan_fc4types[0]); mutex_enter(&pd->pd_mutex); FC_GET_RSP(port, *handle, (uint8_t *)&type, (uint8_t *)&gan_resp->gan_type_id, sizeof (type), DDI_DEV_AUTOINCR); pd->pd_porttype.port_type = type.port_type; pd->pd_porttype.rsvd = 0; pd->pd_spn_len = gan_resp->gan_spnlen; if (pd->pd_spn_len) { FC_GET_RSP(port, *handle, (uint8_t *)pd->pd_spn, (uint8_t *)gan_resp->gan_spname, pd->pd_spn_len, DDI_DEV_AUTOINCR); } FC_GET_RSP(port, *handle, (uint8_t *)pd->pd_ip_addr, (uint8_t *)gan_resp->gan_ip, sizeof (pd->pd_ip_addr), DDI_DEV_AUTOINCR); FC_GET_RSP(port, *handle, (uint8_t *)&pd->pd_cos, (uint8_t *)&gan_resp->gan_cos, sizeof (pd->pd_cos), DDI_DEV_AUTOINCR); FC_GET_RSP(port, *handle, (uint8_t *)pd->pd_fc4types, (uint8_t *)gan_resp->gan_fc4types, sizeof (pd->pd_fc4types), DDI_DEV_AUTOINCR); node = pd->pd_remote_nodep; mutex_exit(&pd->pd_mutex); mutex_enter(&node->fd_mutex); FC_GET_RSP(port, *handle, (uint8_t *)node->fd_ipa, (uint8_t *)gan_resp->gan_ipa, sizeof (node->fd_ipa), DDI_DEV_AUTOINCR); node->fd_snn_len = gan_resp->gan_snnlen; if (node->fd_snn_len) { FC_GET_RSP(port, *handle, (uint8_t *)node->fd_snn, (uint8_t *)gan_resp->gan_snname, node->fd_snn_len, DDI_DEV_AUTOINCR); } mutex_exit(&node->fd_mutex); } /* * Handles all NS Queries (also means that this function * doesn't handle NS object registration) */ static int fp_ns_query(fc_local_port_t *port, fctl_ns_req_t *ns_cmd, job_request_t *job, int polled, int sleep) { int rval; fp_cmd_t *cmd; ASSERT(!MUTEX_HELD(&port->fp_mutex)); if (ns_cmd->ns_cmd_code == NS_GA_NXT) { FP_TRACE(FP_NHEAD1(1, 0), "fp_ns_query GA_NXT fp %x pd %x", port->fp_port_id.port_id, ns_cmd->ns_gan_sid); } if (ns_cmd->ns_cmd_size == 0) { return (FC_FAILURE); } cmd = fp_alloc_pkt(port, sizeof (fc_ct_header_t) + ns_cmd->ns_cmd_size, sizeof (fc_ct_header_t) + ns_cmd->ns_resp_size, sleep, NULL); if (cmd == NULL) { return (FC_NOMEM); } fp_ct_init(port, cmd, ns_cmd, ns_cmd->ns_cmd_code, ns_cmd->ns_cmd_buf, ns_cmd->ns_cmd_size, ns_cmd->ns_resp_size, job); if (polled) { job->job_counter = 1; ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); } rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { job->job_result = rval; fp_iodone(cmd); if (polled == 0) { /* * Return FC_SUCCESS to indicate that * fp_iodone is performed already. */ rval = FC_SUCCESS; } } if (polled) { fp_jobwait(job); rval = job->job_result; } return (rval); } /* * Initialize Common Transport request */ static void fp_ct_init(fc_local_port_t *port, fp_cmd_t *cmd, fctl_ns_req_t *ns_cmd, uint16_t cmd_code, caddr_t cmd_buf, uint16_t cmd_len, uint16_t resp_len, job_request_t *job) { uint32_t s_id; uchar_t class; fc_packet_t *pkt; fc_ct_header_t ct; ASSERT(!MUTEX_HELD(&port->fp_mutex)); mutex_enter(&port->fp_mutex); s_id = port->fp_port_id.port_id; class = port->fp_ns_login_class; mutex_exit(&port->fp_mutex); cmd->cmd_job = job; cmd->cmd_private = ns_cmd; pkt = &cmd->cmd_pkt; ct.ct_rev = CT_REV; ct.ct_inid = 0; ct.ct_fcstype = FCSTYPE_DIRECTORY; ct.ct_fcssubtype = FCSSUB_DS_NAME_SERVER; ct.ct_options = 0; ct.ct_reserved1 = 0; ct.ct_cmdrsp = cmd_code; ct.ct_aiusize = resp_len >> 2; ct.ct_reserved2 = 0; ct.ct_reason = 0; ct.ct_expln = 0; ct.ct_vendor = 0; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&ct, (uint8_t *)pkt->pkt_cmd, sizeof (ct), DDI_DEV_AUTOINCR); pkt->pkt_cmd_fhdr.r_ctl = R_CTL_UNSOL_CONTROL; pkt->pkt_cmd_fhdr.d_id = 0xFFFFFC; pkt->pkt_cmd_fhdr.s_id = s_id; pkt->pkt_cmd_fhdr.type = FC_TYPE_FC_SERVICES; pkt->pkt_cmd_fhdr.f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ | F_CTL_END_SEQ; pkt->pkt_cmd_fhdr.seq_id = 0; pkt->pkt_cmd_fhdr.df_ctl = 0; pkt->pkt_cmd_fhdr.seq_cnt = 0; pkt->pkt_cmd_fhdr.ox_id = 0xffff; pkt->pkt_cmd_fhdr.rx_id = 0xffff; pkt->pkt_cmd_fhdr.ro = 0; pkt->pkt_cmd_fhdr.rsvd = 0; pkt->pkt_comp = fp_ns_intr; pkt->pkt_ulp_private = (opaque_t)cmd; pkt->pkt_timeout = FP_NS_TIMEOUT; if (cmd_buf) { FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)cmd_buf, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), cmd_len, DDI_DEV_AUTOINCR); } cmd->cmd_transport = port->fp_fca_tran->fca_transport; cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_PLOGI_DONT_CARE; cmd->cmd_retry_count = fp_retry_count; cmd->cmd_ulp_pkt = NULL; } /* * Name Server request interrupt routine */ static void fp_ns_intr(fc_packet_t *pkt) { fp_cmd_t *cmd; fc_local_port_t *port; fc_ct_header_t resp_hdr; fc_ct_header_t cmd_hdr; fctl_ns_req_t *ns_cmd; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; mutex_enter(&port->fp_mutex); port->fp_out_fpcmds--; mutex_exit(&port->fp_mutex); FC_GET_RSP(port, pkt->pkt_cmd_acc, (uint8_t *)&cmd_hdr, (uint8_t *)pkt->pkt_cmd, sizeof (cmd_hdr), DDI_DEV_AUTOINCR); ns_cmd = (fctl_ns_req_t *) (((fp_cmd_t *)(pkt->pkt_ulp_private))->cmd_private); if (!FP_IS_PKT_ERROR(pkt)) { FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&resp_hdr, (uint8_t *)pkt->pkt_resp, sizeof (resp_hdr), DDI_DEV_AUTOINCR); /* * On x86 architectures, make sure the resp_hdr is big endian. * This macro is a NOP on sparc architectures mainly because * we don't want to end up wasting time since the end result * is going to be the same. */ MAKE_BE_32(&resp_hdr); if (ns_cmd) { /* * Always copy out the response CT_HDR */ bcopy(&resp_hdr, &ns_cmd->ns_resp_hdr, sizeof (resp_hdr)); } if (resp_hdr.ct_cmdrsp == FS_RJT_IU) { pkt->pkt_state = FC_PKT_FS_RJT; pkt->pkt_reason = resp_hdr.ct_reason; pkt->pkt_expln = resp_hdr.ct_expln; } } if (FP_IS_PKT_ERROR(pkt)) { if (ns_cmd) { if (ns_cmd->ns_flags & FCTL_NS_VALIDATE_PD) { ASSERT(ns_cmd->ns_pd != NULL); /* Mark it OLD if not already done */ mutex_enter(&ns_cmd->ns_pd->pd_mutex); ns_cmd->ns_pd->pd_type = PORT_DEVICE_OLD; mutex_exit(&ns_cmd->ns_pd->pd_mutex); } if (ns_cmd->ns_flags & FCTL_NS_ASYNC_REQUEST) { fctl_free_ns_cmd(ns_cmd); ((fp_cmd_t *) (pkt->pkt_ulp_private))->cmd_private = NULL; } } FP_TRACE(FP_NHEAD2(1, 0), "%x NS failure pkt state=%x " "reason=%x, expln=%x, NSCMD=%04X, NSRSP=%04X", port->fp_port_id.port_id, pkt->pkt_state, pkt->pkt_reason, pkt->pkt_expln, cmd_hdr.ct_cmdrsp, resp_hdr.ct_cmdrsp); (void) fp_common_intr(pkt, 1); return; } if (resp_hdr.ct_cmdrsp != FS_ACC_IU) { uint32_t d_id; fc_local_port_t *port; fp_cmd_t *cmd; d_id = pkt->pkt_cmd_fhdr.d_id; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; FP_TRACE(FP_NHEAD2(9, 0), "Bogus NS response received for D_ID=%x", d_id); } if (cmd_hdr.ct_cmdrsp == NS_GA_NXT) { fp_gan_handler(pkt, ns_cmd); return; } if (cmd_hdr.ct_cmdrsp >= NS_GPN_ID && cmd_hdr.ct_cmdrsp <= NS_GID_PT) { if (ns_cmd) { if ((ns_cmd->ns_flags & FCTL_NS_NO_DATA_BUF) == 0) { fp_ns_query_handler(pkt, ns_cmd); return; } } } fp_iodone(pkt->pkt_ulp_private); } /* * Process NS_GAN response */ static void fp_gan_handler(fc_packet_t *pkt, fctl_ns_req_t *ns_cmd) { int my_did; fc_portid_t d_id; fp_cmd_t *cmd; fc_local_port_t *port; fc_remote_port_t *pd; ns_req_gan_t gan_req; ns_resp_gan_t *gan_resp; ASSERT(ns_cmd != NULL); cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; gan_resp = (ns_resp_gan_t *)(pkt->pkt_resp + sizeof (fc_ct_header_t)); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&d_id, (uint8_t *)&gan_resp->gan_type_id, sizeof (d_id), DDI_DEV_AUTOINCR); *(uint32_t *)&d_id = BE_32(*(uint32_t *)&d_id); /* * In this case the priv_lilp_posit field in reality * is actually represents the relative position on a private loop. * So zero it while dealing with Port Identifiers. */ d_id.priv_lilp_posit = 0; pd = fctl_get_remote_port_by_did(port, d_id.port_id); if (ns_cmd->ns_gan_sid == d_id.port_id) { /* * We've come a full circle; time to get out. */ fp_iodone(cmd); return; } if (ns_cmd->ns_gan_sid == FCTL_GAN_START_ID) { ns_cmd->ns_gan_sid = d_id.port_id; } mutex_enter(&port->fp_mutex); my_did = (d_id.port_id == port->fp_port_id.port_id) ? 1 : 0; mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD1(1, 0), "GAN response; port=%p, fp %x pd %x", port, port->fp_port_id.port_id, d_id.port_id); if (my_did == 0) { la_wwn_t pwwn; la_wwn_t nwwn; FP_TRACE(FP_NHEAD1(1, 0), "GAN response details; " "port=%p, d_id=%x, type_id=%x, " "pwwn=%x %x %x %x %x %x %x %x, " "nwwn=%x %x %x %x %x %x %x %x", port, d_id.port_id, gan_resp->gan_type_id, gan_resp->gan_pwwn.raw_wwn[0], gan_resp->gan_pwwn.raw_wwn[1], gan_resp->gan_pwwn.raw_wwn[2], gan_resp->gan_pwwn.raw_wwn[3], gan_resp->gan_pwwn.raw_wwn[4], gan_resp->gan_pwwn.raw_wwn[5], gan_resp->gan_pwwn.raw_wwn[6], gan_resp->gan_pwwn.raw_wwn[7], gan_resp->gan_nwwn.raw_wwn[0], gan_resp->gan_nwwn.raw_wwn[1], gan_resp->gan_nwwn.raw_wwn[2], gan_resp->gan_nwwn.raw_wwn[3], gan_resp->gan_nwwn.raw_wwn[4], gan_resp->gan_nwwn.raw_wwn[5], gan_resp->gan_nwwn.raw_wwn[6], gan_resp->gan_nwwn.raw_wwn[7]); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&nwwn, (uint8_t *)&gan_resp->gan_nwwn, sizeof (nwwn), DDI_DEV_AUTOINCR); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)&pwwn, (uint8_t *)&gan_resp->gan_pwwn, sizeof (pwwn), DDI_DEV_AUTOINCR); if (ns_cmd->ns_flags & FCTL_NS_CREATE_DEVICE && pd == NULL) { FP_TRACE(FP_NHEAD1(1, 0), "fp %x gan_hander create" "pd %x", port->fp_port_id.port_id, d_id.port_id); pd = fctl_create_remote_port(port, &nwwn, &pwwn, d_id.port_id, PD_PLOGI_INITIATOR, KM_NOSLEEP); } if (pd != NULL) { fp_stuff_device_with_gan(&pkt->pkt_resp_acc, pd, gan_resp); } if (ns_cmd->ns_flags & FCTL_NS_GET_DEV_COUNT) { *((int *)ns_cmd->ns_data_buf) += 1; } if (ns_cmd->ns_flags & FCTL_NS_FILL_NS_MAP) { ASSERT((ns_cmd->ns_flags & FCTL_NS_NO_DATA_BUF) == 0); if (ns_cmd->ns_flags & FCTL_NS_BUF_IS_USERLAND) { fc_port_dev_t *userbuf; userbuf = ((fc_port_dev_t *) ns_cmd->ns_data_buf) + ns_cmd->ns_gan_index++; userbuf->dev_did = d_id; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)userbuf->dev_type, (uint8_t *)gan_resp->gan_fc4types, sizeof (userbuf->dev_type), DDI_DEV_AUTOINCR); userbuf->dev_nwwn = nwwn; userbuf->dev_pwwn = pwwn; if (pd != NULL) { mutex_enter(&pd->pd_mutex); userbuf->dev_state = pd->pd_state; userbuf->dev_hard_addr = pd->pd_hard_addr; mutex_exit(&pd->pd_mutex); } else { userbuf->dev_state = PORT_DEVICE_INVALID; } } else if (ns_cmd->ns_flags & FCTL_NS_BUF_IS_FC_PORTMAP) { fc_portmap_t *map; map = ((fc_portmap_t *) ns_cmd->ns_data_buf) + ns_cmd->ns_gan_index++; /* * First fill it like any new map * and update the port device info * below. */ fp_fillout_new_nsmap(port, &pkt->pkt_resp_acc, map, gan_resp, d_id.port_id); if (pd != NULL) { fctl_copy_portmap(map, pd); } else { map->map_state = PORT_DEVICE_INVALID; map->map_type = PORT_DEVICE_NOCHANGE; } } else { caddr_t dst_ptr; dst_ptr = ns_cmd->ns_data_buf + (NS_GAN_RESP_LEN) * ns_cmd->ns_gan_index++; FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)dst_ptr, (uint8_t *)gan_resp, NS_GAN_RESP_LEN, DDI_DEV_AUTOINCR); } } else { ns_cmd->ns_gan_index++; } if (ns_cmd->ns_gan_index >= ns_cmd->ns_gan_max) { fp_iodone(cmd); return; } } gan_req.pid = d_id; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&gan_req, (uint8_t *)(pkt->pkt_cmd + sizeof (fc_ct_header_t)), sizeof (gan_req), DDI_DEV_AUTOINCR); if (cmd->cmd_transport(port->fp_fca_handle, pkt) != FC_SUCCESS) { pkt->pkt_state = FC_PKT_TRAN_ERROR; fp_iodone(cmd); } else { mutex_enter(&port->fp_mutex); port->fp_out_fpcmds++; mutex_exit(&port->fp_mutex); } } /* * Handle NS Query interrupt */ static void fp_ns_query_handler(fc_packet_t *pkt, fctl_ns_req_t *ns_cmd) { fp_cmd_t *cmd; fc_local_port_t *port; caddr_t src_ptr; uint32_t xfer_len; cmd = pkt->pkt_ulp_private; port = cmd->cmd_port; xfer_len = ns_cmd->ns_resp_size; FP_TRACE(FP_NHEAD1(1, 0), "NS Query response, cmd_code=%x, xfer_len=%x", ns_cmd->ns_cmd_code, xfer_len); if (ns_cmd->ns_cmd_code == NS_GPN_ID) { src_ptr = (caddr_t)pkt->pkt_resp + sizeof (fc_ct_header_t); FP_TRACE(FP_NHEAD1(6, 0), "GPN_ID results; %x %x %x %x %x", src_ptr[0], src_ptr[1], src_ptr[2], src_ptr[3], src_ptr[4]); } if (xfer_len <= ns_cmd->ns_data_len) { src_ptr = (caddr_t)pkt->pkt_resp + sizeof (fc_ct_header_t); FC_GET_RSP(port, pkt->pkt_resp_acc, (uint8_t *)ns_cmd->ns_data_buf, (uint8_t *)src_ptr, xfer_len, DDI_DEV_AUTOINCR); } if (ns_cmd->ns_flags & FCTL_NS_VALIDATE_PD) { ASSERT(ns_cmd->ns_pd != NULL); mutex_enter(&ns_cmd->ns_pd->pd_mutex); if (ns_cmd->ns_pd->pd_type == PORT_DEVICE_OLD) { ns_cmd->ns_pd->pd_type = PORT_DEVICE_NOCHANGE; } mutex_exit(&ns_cmd->ns_pd->pd_mutex); } if (ns_cmd->ns_flags & FCTL_NS_ASYNC_REQUEST) { fctl_free_ns_cmd(ns_cmd); ((fp_cmd_t *)(pkt->pkt_ulp_private))->cmd_private = NULL; } fp_iodone(cmd); } /* * Handle unsolicited ADISC ELS request */ static void fp_handle_unsol_adisc(fc_local_port_t *port, fc_unsol_buf_t *buf, fc_remote_port_t *pd, job_request_t *job) { int rval; fp_cmd_t *cmd; FP_TRACE(FP_NHEAD1(5, 0), "ADISC; port=%p, D_ID=%x state=%x, pd=%p", port, pd->pd_port_id.port_id, pd->pd_state, pd); mutex_enter(&pd->pd_mutex); if (pd->pd_state != PORT_DEVICE_LOGGED_IN) { mutex_exit(&pd->pd_mutex); if (FP_IS_CLASS_1_OR_2(buf->ub_class)) { cmd = fp_alloc_pkt(port, sizeof (la_els_rjt_t), 0, KM_SLEEP, pd); if (cmd != NULL) { fp_els_rjt_init(port, cmd, buf, FC_ACTION_NON_RETRYABLE, FC_REASON_INVALID_LINK_CTRL, job); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { fp_free_pkt(cmd); } } } } else { mutex_exit(&pd->pd_mutex); /* * Yes, yes, we don't have a hard address. But we * we should still respond. Huh ? Visit 21.19.2 * of FC-PH-2 which essentially says that if an * NL_Port doesn't have a hard address, or if a port * does not have FC-AL capability, it shall report * zeroes in this field. */ cmd = fp_alloc_pkt(port, sizeof (la_els_adisc_t), 0, KM_SLEEP, pd); if (cmd == NULL) { return; } fp_adisc_acc_init(port, cmd, buf, job); rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { fp_free_pkt(cmd); } } } /* * Initialize ADISC response. */ static void fp_adisc_acc_init(fc_local_port_t *port, fp_cmd_t *cmd, fc_unsol_buf_t *buf, job_request_t *job) { fc_packet_t *pkt; la_els_adisc_t payload; cmd->cmd_pkt.pkt_tran_flags = buf->ub_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_OUTBOUND; cmd->cmd_flags = FP_CMD_CFLAG_UNDEFINED; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; cmd->cmd_transport = port->fp_fca_tran->fca_els_send; cmd->cmd_job = job; pkt = &cmd->cmd_pkt; fp_unsol_resp_init(pkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); payload.ls_code.ls_code = LA_ELS_ACC; payload.ls_code.mbz = 0; mutex_enter(&port->fp_mutex); payload.nport_id = port->fp_port_id; payload.hard_addr = port->fp_hard_addr; mutex_exit(&port->fp_mutex); payload.port_wwn = port->fp_service_params.nport_ww_name; payload.node_wwn = port->fp_service_params.node_ww_name; FC_SET_CMD(port, pkt->pkt_cmd_acc, (uint8_t *)&payload, (uint8_t *)pkt->pkt_cmd, sizeof (payload), DDI_DEV_AUTOINCR); } /* * Hold and Install the requested ULP drivers */ static void fp_load_ulp_modules(dev_info_t *dip, fc_local_port_t *port) { int len; int count; int data_len; major_t ulp_major; caddr_t ulp_name; caddr_t data_ptr; caddr_t data_buf; ASSERT(!MUTEX_HELD(&port->fp_mutex)); data_buf = NULL; if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "load-ulp-list", (caddr_t)&data_buf, &data_len) != DDI_PROP_SUCCESS) { return; } len = strlen(data_buf); port->fp_ulp_nload = fctl_atoi(data_buf, 10); data_ptr = data_buf + len + 1; for (count = 0; count < port->fp_ulp_nload; count++) { len = strlen(data_ptr) + 1; ulp_name = kmem_zalloc(len, KM_SLEEP); bcopy(data_ptr, ulp_name, len); ulp_major = ddi_name_to_major(ulp_name); if (ulp_major != (major_t)-1) { if (modload("drv", ulp_name) < 0) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "failed to load %s", ulp_name); } } else { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, "%s isn't a valid driver", ulp_name); } kmem_free(ulp_name, len); data_ptr += len; /* Skip to next field */ } /* * Free the memory allocated by DDI */ if (data_buf != NULL) { kmem_free(data_buf, data_len); } } /* * Perform LOGO operation */ static int fp_logout(fc_local_port_t *port, fc_remote_port_t *pd, job_request_t *job) { int rval; fp_cmd_t *cmd; ASSERT(!MUTEX_HELD(&port->fp_mutex)); ASSERT(!MUTEX_HELD(&pd->pd_mutex)); cmd = fp_alloc_pkt(port, sizeof (la_els_logo_t), FP_PORT_IDENTIFIER_LEN, KM_SLEEP, pd); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); ASSERT(pd->pd_state == PORT_DEVICE_LOGGED_IN); ASSERT(pd->pd_login_count == 1); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = 0; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; fp_logo_init(pd, cmd, job); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_sendcmd(port, cmd, port->fp_fca_handle); if (rval != FC_SUCCESS) { fp_iodone(cmd); } return (rval); } /* * Perform Port attach callbacks to registered ULPs */ static void fp_attach_ulps(fc_local_port_t *port, fc_attach_cmd_t cmd) { fp_soft_attach_t *att; att = kmem_zalloc(sizeof (*att), KM_SLEEP); att->att_cmd = cmd; att->att_port = port; /* * We need to remember whether or not fctl_busy_port * succeeded so we know whether or not to call * fctl_idle_port when the task is complete. */ if (fctl_busy_port(port) == 0) { att->att_need_pm_idle = B_TRUE; } else { att->att_need_pm_idle = B_FALSE; } (void) taskq_dispatch(port->fp_taskq, fp_ulp_port_attach, att, KM_SLEEP); } /* * Forward state change notifications on to interested ULPs. * Spawns a call to fctl_ulp_statec_cb() in a taskq thread to do all the * real work. */ static int fp_ulp_notify(fc_local_port_t *port, uint32_t statec, int sleep) { fc_port_clist_t *clist; clist = kmem_zalloc(sizeof (*clist), sleep); if (clist == NULL) { return (FC_NOMEM); } clist->clist_state = statec; mutex_enter(&port->fp_mutex); clist->clist_flags = port->fp_topology; mutex_exit(&port->fp_mutex); clist->clist_port = (opaque_t)port; clist->clist_len = 0; clist->clist_size = 0; clist->clist_map = NULL; (void) taskq_dispatch(port->fp_taskq, fctl_ulp_statec_cb, clist, KM_SLEEP); return (FC_SUCCESS); } /* * Get name server map */ static int fp_ns_getmap(fc_local_port_t *port, job_request_t *job, fc_portmap_t **map, uint32_t *len, uint32_t sid) { int ret; fctl_ns_req_t *ns_cmd; /* * Don't let the allocator do anything for response; * we have have buffer ready to fillout. */ ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gan_t), sizeof (ns_resp_gan_t), 0, (FCTL_NS_FILL_NS_MAP | FCTL_NS_BUF_IS_FC_PORTMAP), KM_SLEEP); ns_cmd->ns_data_len = sizeof (**map) * (*len); ns_cmd->ns_data_buf = (caddr_t)*map; ASSERT(ns_cmd != NULL); ns_cmd->ns_gan_index = 0; ns_cmd->ns_gan_sid = sid; ns_cmd->ns_cmd_code = NS_GA_NXT; ns_cmd->ns_gan_max = *len; ret = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (ns_cmd->ns_gan_index != *len) { *len = ns_cmd->ns_gan_index; } ns_cmd->ns_data_len = 0; ns_cmd->ns_data_buf = NULL; fctl_free_ns_cmd(ns_cmd); return (ret); } /* * Create a remote port in Fabric topology by using NS services */ static fc_remote_port_t * fp_create_remote_port_by_ns(fc_local_port_t *port, uint32_t d_id, int sleep) { int rval; job_request_t *job; fctl_ns_req_t *ns_cmd; fc_remote_port_t *pd; ASSERT(!MUTEX_HELD(&port->fp_mutex)); FP_TRACE(FP_NHEAD1(1, 0), "PD creation begin; port=%p, d_id=%x", port, d_id); #ifdef DEBUG mutex_enter(&port->fp_mutex); ASSERT(FC_IS_TOP_SWITCH(port->fp_topology)); mutex_exit(&port->fp_mutex); #endif job = fctl_alloc_job(JOB_NS_CMD, 0, NULL, (opaque_t)port, sleep); if (job == NULL) { return (NULL); } ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gan_t), sizeof (ns_resp_gan_t), 0, (FCTL_NS_CREATE_DEVICE | FCTL_NS_NO_DATA_BUF), sleep); if (ns_cmd == NULL) { return (NULL); } job->job_result = FC_SUCCESS; ns_cmd->ns_gan_max = 1; ns_cmd->ns_cmd_code = NS_GA_NXT; ns_cmd->ns_gan_sid = FCTL_GAN_START_ID; ((ns_req_gan_t *)(ns_cmd->ns_cmd_buf))->pid.port_id = d_id - 1; ((ns_req_gan_t *)(ns_cmd->ns_cmd_buf))->pid.priv_lilp_posit = 0; ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); rval = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); fctl_free_ns_cmd(ns_cmd); if (rval != FC_SUCCESS || job->job_result != FC_SUCCESS) { fctl_dealloc_job(job); return (NULL); } fctl_dealloc_job(job); pd = fctl_get_remote_port_by_did(port, d_id); FP_TRACE(FP_NHEAD1(1, 0), "PD creation end; port=%p, d_id=%x, pd=%p", port, d_id, pd); return (pd); } /* * Check for the permissions on an ioctl command. If it is required to have an * EXCLUSIVE open performed, return a FAILURE to just shut the door on it. If * the ioctl command isn't in one of the list built, shut the door on that too. * * Certain ioctls perform hardware accesses in FCA drivers, and it needs * to be made sure that users open the port for an exclusive access while * performing those operations. * * This can prevent a casual user from inflicting damage on the port by * sending these ioctls from multiple processes/threads (there is no good * reason why one would need to do that) without actually realizing how * expensive such commands could turn out to be. * * It is also important to note that, even with an exclusive access, * multiple threads can share the same file descriptor and fire down * commands in parallel. To prevent that the driver needs to make sure * that such commands aren't in progress already. This is taken care of * in the FP_EXCL_BUSY bit of fp_flag. */ static int fp_check_perms(uchar_t open_flag, uint16_t ioctl_cmd) { int ret = FC_FAILURE; int count; for (count = 0; count < sizeof (fp_perm_list) / sizeof (fp_perm_list[0]); count++) { if (fp_perm_list[count].fp_ioctl_cmd == ioctl_cmd) { if (fp_perm_list[count].fp_open_flag & open_flag) { ret = FC_SUCCESS; } break; } } return (ret); } /* * Bind Port driver's unsolicited, state change callbacks */ static int fp_bind_callbacks(fc_local_port_t *port) { fc_fca_bind_info_t bind_info = {0}; fc_fca_port_info_t *port_info; int rval = DDI_SUCCESS; uint16_t class; int node_namelen, port_namelen; char *nname = NULL, *pname = NULL; ASSERT(!MUTEX_HELD(&port->fp_mutex)); if (ddi_prop_lookup_string(DDI_DEV_T_ANY, port->fp_port_dip, DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "node-name", &nname) != DDI_PROP_SUCCESS) { FP_TRACE(FP_NHEAD1(1, 0), "fp_bind_callback fail to get node-name"); } if (nname) { fc_str_to_wwn(nname, &(bind_info.port_nwwn)); } if (ddi_prop_lookup_string(DDI_DEV_T_ANY, port->fp_port_dip, DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "port-name", &pname) != DDI_PROP_SUCCESS) { FP_TRACE(FP_NHEAD1(1, 0), "fp_bind_callback fail to get port-name"); } if (pname) { fc_str_to_wwn(pname, &(bind_info.port_pwwn)); } if (port->fp_npiv_type == FC_NPIV_PORT) { bind_info.port_npiv = 1; } /* * fca_bind_port returns the FCA driver's handle for the local * port instance. If the port number isn't supported it returns NULL. * It also sets up callback in the FCA for various * things like state change, ELS etc.. */ bind_info.port_statec_cb = fp_statec_cb; bind_info.port_unsol_cb = fp_unsol_cb; bind_info.port_num = port->fp_port_num; bind_info.port_handle = (opaque_t)port; port_info = kmem_zalloc(sizeof (*port_info), KM_SLEEP); /* * Hold the port driver mutex as the callbacks are bound until the * service parameters are properly filled in (in order to be able to * properly respond to unsolicited ELS requests) */ mutex_enter(&port->fp_mutex); port->fp_fca_handle = port->fp_fca_tran->fca_bind_port( port->fp_fca_dip, port_info, &bind_info); if (port->fp_fca_handle == NULL) { rval = DDI_FAILURE; goto exit; } /* * Only fcoei will set this bit */ if (port_info->pi_port_state & FC_STATE_FCA_IS_NODMA) { port->fp_soft_state |= FP_SOFT_FCA_IS_NODMA; port_info->pi_port_state &= ~(FC_STATE_FCA_IS_NODMA); } port->fp_bind_state = port->fp_state = port_info->pi_port_state; port->fp_service_params = port_info->pi_login_params; port->fp_hard_addr = port_info->pi_hard_addr; /* Copy from the FCA structure to the FP structure */ port->fp_hba_port_attrs = port_info->pi_attrs; if (port_info->pi_rnid_params.status == FC_SUCCESS) { port->fp_rnid_init = 1; bcopy(&port_info->pi_rnid_params.params, &port->fp_rnid_params, sizeof (port->fp_rnid_params)); } else { port->fp_rnid_init = 0; } node_namelen = strlen((char *)&port_info->pi_attrs.sym_node_name); if (node_namelen) { bcopy(&port_info->pi_attrs.sym_node_name, &port->fp_sym_node_name, node_namelen); port->fp_sym_node_namelen = node_namelen; } port_namelen = strlen((char *)&port_info->pi_attrs.sym_port_name); if (port_namelen) { bcopy(&port_info->pi_attrs.sym_port_name, &port->fp_sym_port_name, port_namelen); port->fp_sym_port_namelen = port_namelen; } /* zero out the normally unused fields right away */ port->fp_service_params.ls_code.mbz = 0; port->fp_service_params.ls_code.ls_code = 0; bzero(&port->fp_service_params.reserved, sizeof (port->fp_service_params.reserved)); class = port_info->pi_login_params.class_1.class_opt; port->fp_cos |= (class & 0x8000) ? FC_NS_CLASS1 : 0; class = port_info->pi_login_params.class_2.class_opt; port->fp_cos |= (class & 0x8000) ? FC_NS_CLASS2 : 0; class = port_info->pi_login_params.class_3.class_opt; port->fp_cos |= (class & 0x8000) ? FC_NS_CLASS3 : 0; exit: if (nname) { ddi_prop_free(nname); } if (pname) { ddi_prop_free(pname); } mutex_exit(&port->fp_mutex); kmem_free(port_info, sizeof (*port_info)); return (rval); } /* * Retrieve FCA capabilities */ static void fp_retrieve_caps(fc_local_port_t *port) { int rval; int ub_count; fc_fcp_dma_t fcp_dma; fc_reset_action_t action; fc_dma_behavior_t dma_behavior; ASSERT(!MUTEX_HELD(&port->fp_mutex)); rval = port->fp_fca_tran->fca_get_cap(port->fp_fca_handle, FC_CAP_UNSOL_BUF, &ub_count); switch (rval) { case FC_CAP_FOUND: case FC_CAP_SETTABLE: switch (ub_count) { case 0: break; case -1: ub_count = fp_unsol_buf_count; break; default: /* 1/4th of total buffers is my share */ ub_count = (ub_count / port->fp_fca_tran->fca_numports) >> 2; break; } break; default: ub_count = 0; break; } mutex_enter(&port->fp_mutex); port->fp_ub_count = ub_count; mutex_exit(&port->fp_mutex); rval = port->fp_fca_tran->fca_get_cap(port->fp_fca_handle, FC_CAP_POST_RESET_BEHAVIOR, &action); switch (rval) { case FC_CAP_FOUND: case FC_CAP_SETTABLE: switch (action) { case FC_RESET_RETURN_NONE: case FC_RESET_RETURN_ALL: case FC_RESET_RETURN_OUTSTANDING: break; default: action = FC_RESET_RETURN_NONE; break; } break; default: action = FC_RESET_RETURN_NONE; break; } mutex_enter(&port->fp_mutex); port->fp_reset_action = action; mutex_exit(&port->fp_mutex); rval = port->fp_fca_tran->fca_get_cap(port->fp_fca_handle, FC_CAP_NOSTREAM_ON_UNALIGN_BUF, &dma_behavior); switch (rval) { case FC_CAP_FOUND: switch (dma_behavior) { case FC_ALLOW_STREAMING: /* FALLTHROUGH */ case FC_NO_STREAMING: break; default: /* * If capability was found and the value * was incorrect assume the worst */ dma_behavior = FC_NO_STREAMING; break; } break; default: /* * If capability was not defined - allow streaming; existing * FCAs should not be affected. */ dma_behavior = FC_ALLOW_STREAMING; break; } mutex_enter(&port->fp_mutex); port->fp_dma_behavior = dma_behavior; mutex_exit(&port->fp_mutex); rval = port->fp_fca_tran->fca_get_cap(port->fp_fca_handle, FC_CAP_FCP_DMA, &fcp_dma); if (rval != FC_CAP_FOUND || (fcp_dma != FC_NO_DVMA_SPACE && fcp_dma != FC_DVMA_SPACE)) { fcp_dma = FC_DVMA_SPACE; } mutex_enter(&port->fp_mutex); port->fp_fcp_dma = fcp_dma; mutex_exit(&port->fp_mutex); } /* * Handle Domain, Area changes in the Fabric. */ static void fp_validate_area_domain(fc_local_port_t *port, uint32_t id, uint32_t mask, job_request_t *job, int sleep) { #ifdef DEBUG uint32_t dcnt; #endif int rval; int send; int index; int listindex; int login; int job_flags; char ww_name[17]; uint32_t d_id; uint32_t count; fctl_ns_req_t *ns_cmd; fc_portmap_t *list; fc_orphan_t *orp; fc_orphan_t *norp; fc_orphan_t *prev; fc_remote_port_t *pd; fc_remote_port_t *npd; struct pwwn_hash *head; ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), 0, sleep); if (ns_cmd == NULL) { mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); return; } ns_cmd->ns_cmd_code = NS_GID_PN; /* * We need to get a new count of devices from the * name server, which will also create any new devices * as needed. */ (void) fp_ns_get_devcount(port, job, 1, sleep); FP_TRACE(FP_NHEAD1(3, 0), "fp_validate_area_domain: get_devcount found %d devices", port->fp_total_devices); mutex_enter(&port->fp_mutex); for (count = index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; pd = head->pwwn_head; while (pd != NULL) { mutex_enter(&pd->pd_mutex); if (pd->pd_flags != PD_ELS_IN_PROGRESS) { if ((pd->pd_port_id.port_id & mask) == id && pd->pd_recepient == PD_PLOGI_INITIATOR) { count++; pd->pd_type = PORT_DEVICE_OLD; pd->pd_flags = PD_ELS_MARK; } } mutex_exit(&pd->pd_mutex); pd = pd->pd_wwn_hnext; } } #ifdef DEBUG dcnt = count; #endif /* DEBUG */ /* * Since port->fp_orphan_count is declared an 'int' it is * theoretically possible that the count could go negative. * * This would be bad and if that happens we really do want * to know. */ ASSERT(port->fp_orphan_count >= 0); count += port->fp_orphan_count; /* * We add the port->fp_total_devices value to the count * in the case where our port is newly attached. This is * because we haven't done any discovery and we don't have * any orphans in the port's orphan list. If we do not do * this addition to count then we won't alloc enough kmem * to do discovery with. */ if (count == 0) { count += port->fp_total_devices; FP_TRACE(FP_NHEAD1(3, 0), "fp_validate_area_domain: " "0x%x orphans found, using 0x%x", port->fp_orphan_count, count); } mutex_exit(&port->fp_mutex); /* * Allocate the change list */ list = kmem_zalloc(sizeof (fc_portmap_t) * count, sleep); if (list == NULL) { fp_printf(port, CE_NOTE, FP_LOG_ONLY, 0, NULL, " Not enough memory to service RSCNs" " for %d ports, continuing...", count); fctl_free_ns_cmd(ns_cmd); mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); return; } /* * Attempt to validate or invalidate the devices that were * already in the pwwn hash table. */ mutex_enter(&port->fp_mutex); for (listindex = 0, index = 0; index < pwwn_table_size; index++) { head = &port->fp_pwwn_table[index]; npd = head->pwwn_head; while ((pd = npd) != NULL) { npd = pd->pd_wwn_hnext; mutex_enter(&pd->pd_mutex); if ((pd->pd_port_id.port_id & mask) == id && pd->pd_flags == PD_ELS_MARK) { la_wwn_t *pwwn; job->job_result = FC_SUCCESS; ((ns_req_gid_pn_t *) (ns_cmd->ns_cmd_buf))->pwwn = pd->pd_port_name; pwwn = &pd->pd_port_name; d_id = pd->pd_port_id.port_id; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_ns_query(port, ns_cmd, job, 1, sleep); if (rval != FC_SUCCESS) { fc_wwn_to_str(pwwn, ww_name); FP_TRACE(FP_NHEAD1(3, 0), "AREA RSCN: PD disappeared; " "d_id=%x, PWWN=%s", d_id, ww_name); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x," " PWWN=%s disappeared from fabric", d_id, ww_name); fp_fillout_old_map(list + listindex++, pd, 1); } else { fctl_copy_portmap(list + listindex++, pd); mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); } mutex_enter(&port->fp_mutex); } else { mutex_exit(&pd->pd_mutex); } } } mutex_exit(&port->fp_mutex); ASSERT(listindex == dcnt); job->job_counter = listindex; job_flags = job->job_flags; job->job_flags |= JOB_TYPE_FP_ASYNC; /* * Login (if we were the initiator) or validate devices in the * port map. */ for (index = 0; index < listindex; index++) { pd = list[index].map_pd; mutex_enter(&pd->pd_mutex); ASSERT((pd->pd_port_id.port_id & mask) == id); if (pd->pd_flags != PD_ELS_IN_PROGRESS) { ASSERT(pd->pd_type == PORT_DEVICE_OLD); mutex_exit(&pd->pd_mutex); fp_jobdone(job); continue; } login = (pd->pd_state == PORT_DEVICE_LOGGED_IN) ? 1 : 0; send = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; d_id = pd->pd_port_id.port_id; mutex_exit(&pd->pd_mutex); if ((d_id & mask) == id && send) { if (login) { FP_TRACE(FP_NHEAD1(6, 0), "RSCN and PLOGI request;" " pd=%p, job=%p d_id=%x, index=%d", pd, job, d_id, index); rval = fp_port_login(port, d_id, job, FP_CMD_PLOGI_RETAIN, sleep, pd, NULL); if (rval != FC_SUCCESS) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); job->job_result = rval; fp_jobdone(job); } FP_TRACE(FP_NHEAD1(1, 0), "PLOGI succeeded:no skip(1) for " "D_ID %x", d_id); list[index].map_flags |= PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY; } else { FP_TRACE(FP_NHEAD1(6, 0), "RSCN and NS request;" " pd=%p, job=%p d_id=%x, index=%d", pd, job, d_id, index); rval = fp_ns_validate_device(port, pd, job, 0, sleep); if (rval != FC_SUCCESS) { fp_jobdone(job); } mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); } } else { FP_TRACE(FP_NHEAD1(6, 0), "RSCN and NO request sent; pd=%p," " d_id=%x, index=%d", pd, d_id, index); mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); fp_jobdone(job); } } if (listindex) { fctl_jobwait(job); } job->job_flags = job_flags; /* * Orphan list validation. */ mutex_enter(&port->fp_mutex); for (prev = NULL, orp = port->fp_orphan_list; port->fp_orphan_count && orp != NULL; orp = norp) { norp = orp->orp_next; mutex_exit(&port->fp_mutex); job->job_counter = 1; job->job_result = FC_SUCCESS; ASSERT((job->job_flags & JOB_TYPE_FP_ASYNC) == 0); ((ns_req_gid_pn_t *)ns_cmd->ns_cmd_buf)->pwwn = orp->orp_pwwn; ((ns_resp_gid_pn_t *)ns_cmd->ns_data_buf)->pid.port_id = 0; ((ns_resp_gid_pn_t *) ns_cmd->ns_data_buf)->pid.priv_lilp_posit = 0; rval = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (rval == FC_SUCCESS) { d_id = BE_32(*((uint32_t *)ns_cmd->ns_data_buf)); pd = fp_create_remote_port_by_ns(port, d_id, KM_SLEEP); if (pd != NULL) { fc_wwn_to_str(&orp->orp_pwwn, ww_name); FP_TRACE(FP_NHEAD1(6, 0), "RSCN and ORPHAN list " "success; d_id=%x, PWWN=%s", d_id, ww_name); FP_TRACE(FP_NHEAD2(6, 0), "N_x Port with D_ID=%x, PWWN=%s reappeared" " in fabric", d_id, ww_name); mutex_enter(&port->fp_mutex); if (prev) { prev->orp_next = orp->orp_next; } else { ASSERT(orp == port->fp_orphan_list); port->fp_orphan_list = orp->orp_next; } port->fp_orphan_count--; mutex_exit(&port->fp_mutex); kmem_free(orp, sizeof (*orp)); fctl_copy_portmap(list + listindex++, pd); } else { prev = orp; } } else { prev = orp; } mutex_enter(&port->fp_mutex); } mutex_exit(&port->fp_mutex); /* * One more pass through the list to delist old devices from * the d_id and pwwn tables and possibly add to the orphan list. */ for (index = 0; index < listindex; index++) { pd = list[index].map_pd; ASSERT(pd != NULL); /* * Update PLOGI results; For NS validation * of orphan list, it is redundant * * Take care to preserve PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY if * appropriate as fctl_copy_portmap() will clear map_flags. */ if (list[index].map_flags & PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY) { fctl_copy_portmap(list + index, pd); list[index].map_flags |= PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY; } else { fctl_copy_portmap(list + index, pd); } FP_TRACE(FP_NHEAD1(6, 0), "RSCN with Area DOMAIN " "results; pd=%p, d_id=%x pwwn=%x %x %x %x %x %x %x %x", pd, pd->pd_port_id.port_id, pd->pd_port_name.raw_wwn[0], pd->pd_port_name.raw_wwn[1], pd->pd_port_name.raw_wwn[2], pd->pd_port_name.raw_wwn[3], pd->pd_port_name.raw_wwn[4], pd->pd_port_name.raw_wwn[5], pd->pd_port_name.raw_wwn[6], pd->pd_port_name.raw_wwn[7]); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with Area DOMAIN " "results continued, pd=%p type=%x, flags=%x, state=%x", pd, pd->pd_type, pd->pd_flags, pd->pd_state); mutex_enter(&pd->pd_mutex); if (pd->pd_type == PORT_DEVICE_OLD) { int initiator; pd->pd_flags = PD_IDLE; initiator = (pd->pd_recepient == PD_PLOGI_INITIATOR) ? 1 : 0; mutex_exit(&pd->pd_mutex); mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); pd->pd_state = PORT_DEVICE_INVALID; fctl_delist_did_table(port, pd); fctl_delist_pwwn_table(port, pd); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (initiator) { (void) fctl_add_orphan(port, pd, sleep); } list[index].map_pd = pd; } else { ASSERT(pd->pd_flags == PD_IDLE); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { /* * Reset LOGO tolerance to zero */ fctl_tc_reset(&pd->pd_logo_tc); } mutex_exit(&pd->pd_mutex); } } if (ns_cmd) { fctl_free_ns_cmd(ns_cmd); } if (listindex) { (void) fp_ulp_devc_cb(port, list, listindex, count, sleep, 0); } else { kmem_free(list, sizeof (*list) * count); mutex_enter(&port->fp_mutex); if (--port->fp_rscn_count == FC_INVALID_RSCN_COUNT) { --port->fp_rscn_count; } mutex_exit(&port->fp_mutex); } } /* * Work hard to make sense out of an RSCN page. */ static void fp_validate_rscn_page(fc_local_port_t *port, fc_affected_id_t *page, job_request_t *job, fctl_ns_req_t *ns_cmd, fc_portmap_t *listptr, int *listindex, int sleep) { int rval; char ww_name[17]; la_wwn_t *pwwn; fc_remote_port_t *pwwn_pd; fc_remote_port_t *did_pd; did_pd = fctl_get_remote_port_by_did(port, page->aff_d_id); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page; " "port=%p, d_id=%x, pd=%p, rscn_count:0x%x", port, page->aff_d_id, did_pd, (uint32_t)(uintptr_t)job->job_cb_arg); if (did_pd != NULL) { mutex_enter(&did_pd->pd_mutex); if (did_pd->pd_flags != PD_IDLE) { mutex_exit(&did_pd->pd_mutex); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page: " "PD is BUSY; port=%p, d_id=%x, pd=%p", port, page->aff_d_id, did_pd); return; } did_pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&did_pd->pd_mutex); } job->job_counter = 1; pwwn = &((ns_resp_gpn_id_t *)ns_cmd->ns_data_buf)->pwwn; ((ns_req_gpn_id_t *)ns_cmd->ns_cmd_buf)->pid.port_id = page->aff_d_id; ((ns_req_gpn_id_t *)ns_cmd->ns_cmd_buf)->pid.priv_lilp_posit = 0; bzero(ns_cmd->ns_data_buf, sizeof (la_wwn_t)); rval = fp_ns_query(port, ns_cmd, job, 1, sleep); FP_TRACE(FP_NHEAD1(1, 0), "NS Query Response for D_ID page; rev=%x," " in_id=%x, cmdrsp=%x, reason=%x, expln=%x", ns_cmd->ns_resp_hdr.ct_rev, ns_cmd->ns_resp_hdr.ct_inid, ns_cmd->ns_resp_hdr.ct_cmdrsp, ns_cmd->ns_resp_hdr.ct_reason, ns_cmd->ns_resp_hdr.ct_expln); job->job_counter = 1; if (rval != FC_SUCCESS || fctl_is_wwn_zero(pwwn) == FC_SUCCESS) { /* * What this means is that the D_ID * disappeared from the Fabric. */ if (did_pd == NULL) { FP_TRACE(FP_NHEAD1(1, 0), "RSCN with D_ID page;" " NULL PD disappeared, rval=%x", rval); return; } fc_wwn_to_str(&did_pd->pd_port_name, ww_name); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fp_fillout_old_map(listptr + (*listindex)++, did_pd, 0); FP_TRACE(FP_NHEAD1(3, 0), "RSCN: PD disappeared; " "d_id=%x, PWWN=%s", page->aff_d_id, ww_name); FP_TRACE(FP_NHEAD2(9, 0), "GPN_ID for D_ID=%x failed", page->aff_d_id); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s disappeared from" " fabric", page->aff_d_id, ww_name); mutex_enter(&did_pd->pd_mutex); did_pd->pd_flags = PD_IDLE; mutex_exit(&did_pd->pd_mutex); FP_TRACE(FP_NHEAD1(3, 0), "RSCN with D_ID (%x) page; " "PD disappeared, pd=%p", page->aff_d_id, did_pd); return; } pwwn_pd = fctl_get_remote_port_by_pwwn(port, pwwn); if (did_pd != NULL && pwwn_pd != NULL && did_pd == pwwn_pd) { /* * There is no change. Do PLOGI again and add it to * ULP portmap baggage and return. Note: When RSCNs * arrive with per page states, the need for PLOGI * can be determined correctly. */ mutex_enter(&pwwn_pd->pd_mutex); pwwn_pd->pd_type = PORT_DEVICE_NOCHANGE; mutex_exit(&pwwn_pd->pd_mutex); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fctl_copy_portmap(listptr + (*listindex)++, pwwn_pd); mutex_enter(&pwwn_pd->pd_mutex); if ((pwwn_pd->pd_state == PORT_DEVICE_LOGGED_IN) || (pwwn_pd->pd_aux_flags & PD_LOGGED_OUT)) { fc_wwn_to_str(&pwwn_pd->pd_port_name, ww_name); mutex_exit(&pwwn_pd->pd_mutex); rval = fp_port_login(port, page->aff_d_id, job, FP_CMD_PLOGI_RETAIN, sleep, pwwn_pd, NULL); if (rval == FC_SUCCESS) { fp_jobwait(job); rval = job->job_result; /* * Reset LOGO tolerance to zero * Also we are the PLOGI initiator now. */ mutex_enter(&pwwn_pd->pd_mutex); fctl_tc_reset(&pwwn_pd->pd_logo_tc); pwwn_pd->pd_recepient = PD_PLOGI_INITIATOR; mutex_exit(&pwwn_pd->pd_mutex); } if (rval == FC_SUCCESS) { struct fc_portmap *map = listptr + *listindex - 1; FP_TRACE(FP_NHEAD1(1, 0), "PLOGI succeeded: no skip(2)" " for D_ID %x", page->aff_d_id); map->map_flags |= PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY; } else { FP_TRACE(FP_NHEAD2(9, rval), "PLOGI to D_ID=%x failed", page->aff_d_id); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s" " disappeared from fabric", page->aff_d_id, ww_name); fp_fillout_old_map(listptr + *listindex - 1, pwwn_pd, 0); } } else { mutex_exit(&pwwn_pd->pd_mutex); } mutex_enter(&did_pd->pd_mutex); did_pd->pd_flags = PD_IDLE; mutex_exit(&did_pd->pd_mutex); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID (0x%x) page; " "Case ONE, rval=%x, result=%x pd=%p", page->aff_d_id, rval, job->job_result, pwwn_pd); return; } if (did_pd == NULL && pwwn_pd == NULL) { fc_orphan_t *orp = NULL; fc_orphan_t *norp = NULL; fc_orphan_t *prev = NULL; /* * Hunt down the orphan list before giving up. */ mutex_enter(&port->fp_mutex); if (port->fp_orphan_count) { for (orp = port->fp_orphan_list; orp; orp = norp) { norp = orp->orp_next; if (fctl_wwn_cmp(&orp->orp_pwwn, pwwn) != 0) { prev = orp; continue; } if (prev) { prev->orp_next = orp->orp_next; } else { ASSERT(orp == port->fp_orphan_list); port->fp_orphan_list = orp->orp_next; } port->fp_orphan_count--; break; } } mutex_exit(&port->fp_mutex); pwwn_pd = fp_create_remote_port_by_ns(port, page->aff_d_id, sleep); if (pwwn_pd != NULL) { if (orp) { fc_wwn_to_str(&orp->orp_pwwn, ww_name); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x," " PWWN=%s reappeared in fabric", page->aff_d_id, ww_name); kmem_free(orp, sizeof (*orp)); } (listptr + *listindex)-> map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fctl_copy_portmap(listptr + (*listindex)++, pwwn_pd); } FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID (0x%x) page; " "Case TWO", page->aff_d_id); return; } if (pwwn_pd != NULL && did_pd == NULL) { uint32_t old_d_id; uint32_t d_id = page->aff_d_id; /* * What this means is there is a new D_ID for this * Port WWN. Take out the port device off D_ID * list and put it back with a new D_ID. Perform * PLOGI if already logged in. */ mutex_enter(&port->fp_mutex); mutex_enter(&pwwn_pd->pd_mutex); old_d_id = pwwn_pd->pd_port_id.port_id; fctl_delist_did_table(port, pwwn_pd); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fp_fillout_changed_map(listptr + (*listindex)++, pwwn_pd, &d_id, NULL); fctl_enlist_did_table(port, pwwn_pd); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page;" " Case THREE, pd=%p," " state=%x", pwwn_pd, pwwn_pd->pd_state); if ((pwwn_pd->pd_state == PORT_DEVICE_LOGGED_IN) || (pwwn_pd->pd_aux_flags & PD_LOGGED_OUT)) { fc_wwn_to_str(&pwwn_pd->pd_port_name, ww_name); mutex_exit(&pwwn_pd->pd_mutex); mutex_exit(&port->fp_mutex); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s has a new" " D_ID=%x now", old_d_id, ww_name, d_id); rval = fp_port_login(port, page->aff_d_id, job, FP_CMD_PLOGI_RETAIN, sleep, pwwn_pd, NULL); if (rval == FC_SUCCESS) { fp_jobwait(job); rval = job->job_result; } if (rval != FC_SUCCESS) { fp_fillout_old_map(listptr + *listindex - 1, pwwn_pd, 0); } } else { mutex_exit(&pwwn_pd->pd_mutex); mutex_exit(&port->fp_mutex); } return; } if (pwwn_pd == NULL && did_pd != NULL) { fc_portmap_t *ptr; uint32_t len = 1; char old_ww_name[17]; mutex_enter(&did_pd->pd_mutex); fc_wwn_to_str(&did_pd->pd_port_name, old_ww_name); mutex_exit(&did_pd->pd_mutex); fc_wwn_to_str(pwwn, ww_name); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; /* * What this means is that there is a new Port WWN for * this D_ID; Mark the Port device as old and provide * the new PWWN and D_ID combination as new. */ fp_fillout_old_map(listptr + (*listindex)++, did_pd, 0); FP_TRACE(FP_NHEAD2(9, 0), "N_x Port with D_ID=%x, PWWN=%s has a new PWWN=%s now", page->aff_d_id, old_ww_name, ww_name); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; ptr = listptr + (*listindex)++; job->job_counter = 1; if (fp_ns_getmap(port, job, &ptr, &len, page->aff_d_id - 1) != FC_SUCCESS) { (*listindex)--; } mutex_enter(&did_pd->pd_mutex); did_pd->pd_flags = PD_IDLE; mutex_exit(&did_pd->pd_mutex); return; } /* * A weird case of Port WWN and D_ID existence but not matching up * between them. Trust your instincts - Take the port device handle * off Port WWN list, fix it with new Port WWN and put it back, In * the mean time mark the port device corresponding to the old port * WWN as OLD. */ FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page; Case WEIRD, pwwn_pd=%p," " did_pd=%p", pwwn_pd, did_pd); mutex_enter(&port->fp_mutex); mutex_enter(&pwwn_pd->pd_mutex); pwwn_pd->pd_type = PORT_DEVICE_OLD; pwwn_pd->pd_state = PORT_DEVICE_INVALID; fctl_delist_did_table(port, pwwn_pd); fctl_delist_pwwn_table(port, pwwn_pd); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page; case WEIRD continued," " pwwn-d_id=%x pwwn-wwn=%x %x %x %x %x %x %x %x", pwwn_pd->pd_port_id.port_id, pwwn_pd->pd_port_name.raw_wwn[0], pwwn_pd->pd_port_name.raw_wwn[1], pwwn_pd->pd_port_name.raw_wwn[2], pwwn_pd->pd_port_name.raw_wwn[3], pwwn_pd->pd_port_name.raw_wwn[4], pwwn_pd->pd_port_name.raw_wwn[5], pwwn_pd->pd_port_name.raw_wwn[6], pwwn_pd->pd_port_name.raw_wwn[7]); mutex_exit(&pwwn_pd->pd_mutex); mutex_exit(&port->fp_mutex); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fctl_copy_portmap(listptr + (*listindex)++, pwwn_pd); mutex_enter(&port->fp_mutex); mutex_enter(&did_pd->pd_mutex); fctl_delist_pwwn_table(port, did_pd); (listptr + *listindex)->map_rscn_info.ulp_rscn_count = (uint32_t)(uintptr_t)job->job_cb_arg; fp_fillout_changed_map(listptr + (*listindex)++, did_pd, NULL, pwwn); fctl_enlist_pwwn_table(port, did_pd); FP_TRACE(FP_NHEAD1(6, 0), "RSCN with D_ID page; case WEIRD continued," " d_id=%x, state=%x, did-wwn=%x %x %x %x %x %x %x %x", did_pd->pd_port_id.port_id, did_pd->pd_state, did_pd->pd_port_name.raw_wwn[0], did_pd->pd_port_name.raw_wwn[1], did_pd->pd_port_name.raw_wwn[2], did_pd->pd_port_name.raw_wwn[3], did_pd->pd_port_name.raw_wwn[4], did_pd->pd_port_name.raw_wwn[5], did_pd->pd_port_name.raw_wwn[6], did_pd->pd_port_name.raw_wwn[7]); if ((did_pd->pd_state == PORT_DEVICE_LOGGED_IN) || (did_pd->pd_aux_flags & PD_LOGGED_OUT)) { mutex_exit(&did_pd->pd_mutex); mutex_exit(&port->fp_mutex); rval = fp_port_login(port, page->aff_d_id, job, FP_CMD_PLOGI_RETAIN, sleep, did_pd, NULL); if (rval == FC_SUCCESS) { fp_jobwait(job); if (job->job_result != FC_SUCCESS) { fp_fillout_old_map(listptr + *listindex - 1, did_pd, 0); } } else { fp_fillout_old_map(listptr + *listindex - 1, did_pd, 0); } } else { mutex_exit(&did_pd->pd_mutex); mutex_exit(&port->fp_mutex); } mutex_enter(&did_pd->pd_mutex); did_pd->pd_flags = PD_IDLE; mutex_exit(&did_pd->pd_mutex); } /* * Check with NS for the presence of this port WWN */ static int fp_ns_validate_device(fc_local_port_t *port, fc_remote_port_t *pd, job_request_t *job, int polled, int sleep) { la_wwn_t pwwn; uint32_t flags; fctl_ns_req_t *ns_cmd; flags = FCTL_NS_VALIDATE_PD | ((polled) ? 0: FCTL_NS_ASYNC_REQUEST); ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), flags, sleep); if (ns_cmd == NULL) { return (FC_NOMEM); } mutex_enter(&pd->pd_mutex); pwwn = pd->pd_port_name; mutex_exit(&pd->pd_mutex); ns_cmd->ns_cmd_code = NS_GID_PN; ns_cmd->ns_pd = pd; ((ns_req_gid_pn_t *)ns_cmd->ns_cmd_buf)->pwwn = pwwn; ((ns_resp_gid_pn_t *)ns_cmd->ns_data_buf)->pid.port_id = 0; ((ns_resp_gid_pn_t *)ns_cmd->ns_data_buf)->pid.priv_lilp_posit = 0; return (fp_ns_query(port, ns_cmd, job, polled, sleep)); } /* * Sanity check the LILP map returned by FCA */ static int fp_validate_lilp_map(fc_lilpmap_t *lilp_map) { int count; if (lilp_map->lilp_length == 0) { return (FC_FAILURE); } for (count = 0; count < lilp_map->lilp_length; count++) { if (fp_is_valid_alpa(lilp_map->lilp_alpalist[count]) != FC_SUCCESS) { return (FC_FAILURE); } } return (FC_SUCCESS); } /* * Sanity check if the AL_PA is a valid address */ static int fp_is_valid_alpa(uchar_t al_pa) { int count; for (count = 0; count < sizeof (fp_valid_alpas); count++) { if (al_pa == fp_valid_alpas[count] || al_pa == 0) { return (FC_SUCCESS); } } return (FC_FAILURE); } /* * Post unsolicited callbacks to ULPs */ static void fp_ulp_unsol_cb(void *arg) { fp_unsol_spec_t *ub_spec = (fp_unsol_spec_t *)arg; fctl_ulp_unsol_cb(ub_spec->port, ub_spec->buf, ub_spec->buf->ub_frame.type); kmem_free(ub_spec, sizeof (*ub_spec)); } /* * Perform message reporting in a consistent manner. Unless there is * a strong reason NOT to use this function (which is very very rare) * all message reporting should go through this. */ static void fp_printf(fc_local_port_t *port, int level, fp_mesg_dest_t dest, int fc_errno, fc_packet_t *pkt, const char *fmt, ...) { caddr_t buf; va_list ap; switch (level) { case CE_NOTE: if ((port->fp_verbose & FP_WARNING_MESSAGES) == 0) { return; } break; case CE_WARN: if ((port->fp_verbose & FP_FATAL_MESSAGES) == 0) { return; } break; } buf = kmem_zalloc(256, KM_NOSLEEP); if (buf == NULL) { return; } (void) sprintf(buf, "fp(%d): ", port->fp_instance); va_start(ap, fmt); (void) vsprintf(buf + strlen(buf), fmt, ap); va_end(ap); if (fc_errno) { char *errmsg; (void) fc_ulp_error(fc_errno, &errmsg); (void) sprintf(buf + strlen(buf), " FC Error=%s", errmsg); } else { if (pkt) { caddr_t state, reason, action, expln; (void) fc_ulp_pkt_error(pkt, &state, &reason, &action, &expln); (void) sprintf(buf + strlen(buf), " state=%s, reason=%s", state, reason); if (pkt->pkt_resp_resid) { (void) sprintf(buf + strlen(buf), " resp resid=%x\n", pkt->pkt_resp_resid); } } } switch (dest) { case FP_CONSOLE_ONLY: cmn_err(level, "^%s", buf); break; case FP_LOG_ONLY: cmn_err(level, "!%s", buf); break; default: cmn_err(level, "%s", buf); break; } kmem_free(buf, 256); } static int fp_fcio_login(fc_local_port_t *port, fcio_t *fcio, job_request_t *job) { int ret; uint32_t d_id; la_wwn_t pwwn; fc_remote_port_t *pd = NULL; fc_remote_port_t *held_pd = NULL; fctl_ns_req_t *ns_cmd; fc_portmap_t *changelist; bcopy(fcio->fcio_ibuf, &pwwn, sizeof (pwwn)); mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); job->job_counter = 1; job->job_result = FC_SUCCESS; ns_cmd = fctl_alloc_ns_cmd(sizeof (ns_req_gid_pn_t), sizeof (ns_resp_gid_pn_t), sizeof (ns_resp_gid_pn_t), FCTL_NS_BUF_IS_USERLAND, KM_SLEEP); ASSERT(ns_cmd != NULL); ns_cmd->ns_cmd_code = NS_GID_PN; ((ns_req_gid_pn_t *)(ns_cmd->ns_cmd_buf))->pwwn = pwwn; ret = fp_ns_query(port, ns_cmd, job, 1, KM_SLEEP); if (ret != FC_SUCCESS || job->job_result != FC_SUCCESS) { if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; } else { fcio->fcio_errno = job->job_result; } fctl_free_ns_cmd(ns_cmd); return (EIO); } d_id = BE_32(*((uint32_t *)ns_cmd->ns_data_buf)); fctl_free_ns_cmd(ns_cmd); } else { mutex_exit(&port->fp_mutex); held_pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (held_pd == NULL) { fcio->fcio_errno = FC_BADWWN; return (EIO); } pd = held_pd; mutex_enter(&pd->pd_mutex); d_id = pd->pd_port_id.port_id; mutex_exit(&pd->pd_mutex); } job->job_counter = 1; pd = fctl_get_remote_port_by_did(port, d_id); if (pd) { mutex_enter(&pd->pd_mutex); if (pd->pd_state == PORT_DEVICE_LOGGED_IN) { pd->pd_login_count++; mutex_exit(&pd->pd_mutex); fcio->fcio_errno = FC_SUCCESS; if (held_pd) { fctl_release_remote_port(held_pd); } return (0); } mutex_exit(&pd->pd_mutex); } else { mutex_enter(&port->fp_mutex); if (FC_IS_TOP_SWITCH(port->fp_topology)) { mutex_exit(&port->fp_mutex); pd = fp_create_remote_port_by_ns(port, d_id, KM_SLEEP); if (pd == NULL) { fcio->fcio_errno = FC_FAILURE; if (held_pd) { fctl_release_remote_port(held_pd); } return (EIO); } } else { mutex_exit(&port->fp_mutex); } } job->job_flags &= ~JOB_TYPE_FP_ASYNC; job->job_counter = 1; ret = fp_port_login(port, d_id, job, FP_CMD_PLOGI_RETAIN, KM_SLEEP, pd, NULL); if (ret != FC_SUCCESS) { fcio->fcio_errno = ret; if (held_pd) { fctl_release_remote_port(held_pd); } return (EIO); } fp_jobwait(job); fcio->fcio_errno = job->job_result; if (held_pd) { fctl_release_remote_port(held_pd); } if (job->job_result != FC_SUCCESS) { return (EIO); } pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { fcio->fcio_errno = FC_BADDEV; return (ENODEV); } changelist = kmem_zalloc(sizeof (*changelist), KM_SLEEP); fctl_copy_portmap(changelist, pd); changelist->map_type = PORT_DEVICE_USER_LOGIN; (void) fp_ulp_devc_cb(port, changelist, 1, 1, KM_SLEEP, 1); mutex_enter(&pd->pd_mutex); pd->pd_type = PORT_DEVICE_NOCHANGE; mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); return (0); } static int fp_fcio_logout(fc_local_port_t *port, fcio_t *fcio, job_request_t *job) { la_wwn_t pwwn; fp_cmd_t *cmd; fc_portmap_t *changelist; fc_remote_port_t *pd; bcopy(fcio->fcio_ibuf, &pwwn, sizeof (pwwn)); pd = fctl_hold_remote_port_by_pwwn(port, &pwwn); if (pd == NULL) { fcio->fcio_errno = FC_BADWWN; return (ENXIO); } mutex_enter(&pd->pd_mutex); if (pd->pd_state != PORT_DEVICE_LOGGED_IN) { fcio->fcio_errno = FC_LOGINREQ; mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); return (EINVAL); } ASSERT(pd->pd_login_count >= 1); if (pd->pd_flags == PD_ELS_IN_PROGRESS) { fcio->fcio_errno = FC_FAILURE; mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); return (EBUSY); } if (pd->pd_login_count > 1) { pd->pd_login_count--; fcio->fcio_errno = FC_SUCCESS; mutex_exit(&pd->pd_mutex); changelist = kmem_zalloc(sizeof (*changelist), KM_SLEEP); fctl_copy_portmap(changelist, pd); changelist->map_type = PORT_DEVICE_USER_LOGOUT; fctl_release_remote_port(pd); (void) fp_ulp_devc_cb(port, changelist, 1, 1, KM_SLEEP, 1); return (0); } pd->pd_flags = PD_ELS_IN_PROGRESS; mutex_exit(&pd->pd_mutex); job->job_counter = 1; cmd = fp_alloc_pkt(port, sizeof (la_els_logo_t), FP_PORT_IDENTIFIER_LEN, KM_SLEEP, pd); if (cmd == NULL) { fcio->fcio_errno = FC_NOMEM; fctl_release_remote_port(pd); mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); return (ENOMEM); } mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); cmd->cmd_pkt.pkt_tran_flags = FC_TRAN_INTR | pd->pd_login_class; cmd->cmd_pkt.pkt_tran_type = FC_PKT_EXCHANGE; cmd->cmd_flags = FP_CMD_PLOGI_DONT_CARE; cmd->cmd_retry_count = 1; cmd->cmd_ulp_pkt = NULL; fp_logo_init(pd, cmd, job); mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); if (fp_sendcmd(port, cmd, port->fp_fca_handle) != FC_SUCCESS) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); fp_free_pkt(cmd); fctl_release_remote_port(pd); return (EIO); } fp_jobwait(job); fcio->fcio_errno = job->job_result; if (job->job_result != FC_SUCCESS) { mutex_enter(&pd->pd_mutex); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); fctl_release_remote_port(pd); return (EIO); } ASSERT(pd != NULL); changelist = kmem_zalloc(sizeof (*changelist), KM_SLEEP); fctl_copy_portmap(changelist, pd); changelist->map_type = PORT_DEVICE_USER_LOGOUT; changelist->map_state = PORT_DEVICE_INVALID; mutex_enter(&port->fp_mutex); mutex_enter(&pd->pd_mutex); fctl_delist_did_table(port, pd); fctl_delist_pwwn_table(port, pd); pd->pd_flags = PD_IDLE; mutex_exit(&pd->pd_mutex); mutex_exit(&port->fp_mutex); (void) fp_ulp_devc_cb(port, changelist, 1, 1, KM_SLEEP, 1); fctl_release_remote_port(pd); return (0); } /* * Send a syslog event for adapter port level events. */ static void fp_log_port_event(fc_local_port_t *port, char *subclass) { nvlist_t *attr_list; if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_SLEEP) != DDI_SUCCESS) { goto alloc_failed; } if (nvlist_add_uint32(attr_list, "instance", port->fp_instance) != DDI_SUCCESS) { goto error; } if (nvlist_add_byte_array(attr_list, "port-wwn", port->fp_service_params.nport_ww_name.raw_wwn, sizeof (la_wwn_t)) != DDI_SUCCESS) { goto error; } (void) ddi_log_sysevent(port->fp_port_dip, DDI_VENDOR_SUNW, EC_SUNFC, subclass, attr_list, NULL, DDI_SLEEP); nvlist_free(attr_list); return; error: nvlist_free(attr_list); alloc_failed: FP_TRACE(FP_NHEAD1(9, 0), "Unable to send %s event", subclass); } static void fp_log_target_event(fc_local_port_t *port, char *subclass, la_wwn_t tgt_pwwn, uint32_t port_id) { nvlist_t *attr_list; if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_SLEEP) != DDI_SUCCESS) { goto alloc_failed; } if (nvlist_add_uint32(attr_list, "instance", port->fp_instance) != DDI_SUCCESS) { goto error; } if (nvlist_add_byte_array(attr_list, "port-wwn", port->fp_service_params.nport_ww_name.raw_wwn, sizeof (la_wwn_t)) != DDI_SUCCESS) { goto error; } if (nvlist_add_byte_array(attr_list, "target-port-wwn", tgt_pwwn.raw_wwn, sizeof (la_wwn_t)) != DDI_SUCCESS) { goto error; } if (nvlist_add_uint32(attr_list, "target-port-id", port_id) != DDI_SUCCESS) { goto error; } (void) ddi_log_sysevent(port->fp_port_dip, DDI_VENDOR_SUNW, EC_SUNFC, subclass, attr_list, NULL, DDI_SLEEP); nvlist_free(attr_list); return; error: nvlist_free(attr_list); alloc_failed: FP_TRACE(FP_NHEAD1(9, 0), "Unable to send %s event", subclass); } static uint32_t fp_map_remote_port_state(uint32_t rm_state) { switch (rm_state) { case PORT_DEVICE_LOGGED_IN: return (FC_HBA_PORTSTATE_ONLINE); case PORT_DEVICE_VALID: case PORT_DEVICE_INVALID: default: return (FC_HBA_PORTSTATE_UNKNOWN); } }