/* * 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 2000 by Cisco Systems, Inc. All rights reserved. * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * iSCSI Pseudo HBA Driver */ #include /* networking stuff */ #include /* networking stuff */ #include /* networking stuff */ #include /* networking stuff */ #include /* TCP_NODELAY */ #include /* _ALLOC_SLEEP */ #include /* DB_TYPE() */ #include "iscsi.h" /* iscsi driver */ #include /* iscsi protocol */ /* generic io helpers */ static uint32_t n2h24(uchar_t *ptr); static int iscsi_sna_lt(uint32_t n1, uint32_t n2); static void iscsi_update_flow_control(iscsi_sess_t *isp, uint32_t max, uint32_t exp); /* receivers */ static iscsi_status_t iscsi_rx_process_hdr(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data, int data_size); static iscsi_status_t iscsi_rx_process_nop(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_data_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp); static iscsi_status_t iscsi_rx_process_cmd_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_rtt_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_reject_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_rejected_tsk_mgt(iscsi_conn_t *icp, iscsi_hdr_t *old_ihp); static iscsi_status_t iscsi_rx_process_itt_to_icmdp(iscsi_sess_t *isp, iscsi_hdr_t *ihp, iscsi_cmd_t **icmdp); static iscsi_status_t iscsi_rx_process_task_mgt_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, void *data); static iscsi_status_t iscsi_rx_process_logout_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_async_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); static iscsi_status_t iscsi_rx_process_text_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data); /* senders */ static iscsi_status_t iscsi_tx_scsi(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_r2t(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_data(iscsi_sess_t *isp, iscsi_conn_t *icp, iscsi_cmd_t *icmdp, uint32_t ttt, size_t datalen, size_t offset); static iscsi_status_t iscsi_tx_nop(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_abort(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_reset(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_logout(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); static iscsi_status_t iscsi_tx_text(iscsi_sess_t *isp, iscsi_cmd_t *icmdp); /* helpers */ static void iscsi_handle_r2t(iscsi_conn_t *icp, iscsi_cmd_t *icmdp, uint32_t offset, uint32_t length, uint32_t ttt); static void iscsi_handle_passthru_callback(struct scsi_pkt *pkt); static void iscsi_handle_nop(iscsi_conn_t *icp, uint32_t itt, uint32_t ttt); static void iscsi_timeout_checks(iscsi_sess_t *isp); static void iscsi_nop_checks(iscsi_sess_t *isp); #define ISCSI_CONN_TO_NET_DIGEST(icp) \ ((icp->conn_params.header_digest ? ISCSI_NET_HEADER_DIGEST : 0) | \ (icp->conn_params.data_digest ? ISCSI_NET_DATA_DIGEST : 0)) /* * This file contains the main guts of the iSCSI protocol layer. * It's broken into 5 sections; Basic helper functions, RX IO path, * TX IO path, Completion (IC) IO path, and watchdog (WD) routines. * * The IO flow model is similiar to the below diagram. The * iscsi session, connection and command state machines are used * to drive IO through this flow diagram. Reference those files * to get a detailed description of their respective state models * prior to their xxx_state_machine_function(). * * tran_start() -> CMD_E1 TX_THREAD RX_THREAD * | T T * V T T * PENDING_Q --CMD_E2--> ACTIVE_Q - --CMD_E3--+ * T \ C T | * T \M T | * D T | * WD_THREAD TT|TT T | * /E T | * / 6 T | * ABORTING_Q<- --CMD_E3--+ * T | * T T | * T | * callback() <--CMD_E#-- COMPLETION_Q <------------+ * T * T * IC_THREAD * * External and internal command are ran thru this same state * machine. All commands enter the state machine by receiving an * ISCSI_CMD_EVENT_E1. This event places the command into the * PENDING_Q. Next when resources are available the TX_THREAD * issues a E2 event on the command. This sends the command * to the TCP stack and places the command on the ACTIVE_Q. While * on the PENDIING_Q and ACTIVE_Q, the command is monitored via the * WD_THREAD to ensure the pkt_time has not elapsed. If elapsed the * command is issued an E6(timeout) event which moves either (if pending) * completed the command or (if active) moves the command to the * aborting queue and issues a SCSI TASK MANAGEMENT ABORT command * to cancel the IO request. If the original command is completed * or the TASK MANAGEMENT command completes the command is moved * to the COMPLETION_Q via a E3 event. The IC_THREAD then processes * the COMPLETION_Q and issues the scsi_pkt callback. This * callback can not be processed directly from the RX_THREAD * because the callback might call back into the iscsi driver * causing a deadlock condition. * * For more details on the complete CMD state machine reference * the state machine diagram in iscsi_cmd.c. The connection state * machine is driven via IO events in this file. Then session * events are driven by the connection events. For complete * details on these state machines reference iscsi_sess.c and * iscsi_conn.c */ /* * +--------------------------------------------------------------------+ * | io helper routines | * +--------------------------------------------------------------------+ */ /* * n2h24 - native to host 24 bit integer translation. */ static uint32_t n2h24(uchar_t *ptr) { uint32_t idx; bcopy(ptr, &idx, 3); return (ntohl(idx) >> 8); } /* * iscsi_sna_lt - Serial Number Arithmetic, 32 bits, less than, RFC1982 */ static int iscsi_sna_lt(uint32_t n1, uint32_t n2) { return ((n1 != n2) && (((n1 < n2) && ((n2 - n1) < ISCSI_SNA32_CHECK)) || ((n1 > n2) && ((n1 - n2) > ISCSI_SNA32_CHECK)))); } /* * iscsi_sna_lte - Serial Number Arithmetic, 32 bits, less than or equal, * RFC1982 */ int iscsi_sna_lte(uint32_t n1, uint32_t n2) { return ((n1 == n2) || (((n1 < n2) && ((n2 - n1) < ISCSI_SNA32_CHECK)) || ((n1 > n2) && ((n1 - n2) > ISCSI_SNA32_CHECK)))); } /* * iscsi_update_flow_control - Update expcmdsn and maxcmdsn iSCSI * flow control information for a session */ static void iscsi_update_flow_control(iscsi_sess_t *isp, uint32_t max, uint32_t exp) { ASSERT(isp != NULL); ASSERT(mutex_owned(&isp->sess_cmdsn_mutex)); if (!iscsi_sna_lt(max, (exp - 1))) { if (!iscsi_sna_lte(exp, isp->sess_expcmdsn)) { isp->sess_expcmdsn = exp; } if (!iscsi_sna_lte(max, isp->sess_maxcmdsn)) { isp->sess_maxcmdsn = max; if (iscsi_sna_lte(isp->sess_cmdsn, isp->sess_maxcmdsn)) { /* * the window is open again - schedule * to send any held tasks soon */ iscsi_sess_redrive_io(isp); } } } } /* * +--------------------------------------------------------------------+ * | io receive and processing routines | * +--------------------------------------------------------------------+ */ /* * iscsi_rx_thread - The connection creates a thread of this * function during login. After which point this thread is * used to receive and process all iSCSI PDUs on this connection. * The PDUs received on this connection are used to drive the * commands through their state machine. This thread will * continue processing while the connection is on a LOGGED_IN * or IN_LOGOUT state. Once the connection moves out of this * state the thread will die. */ void iscsi_rx_thread(iscsi_thread_t *thread, void *arg) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = (iscsi_conn_t *)arg; iscsi_sess_t *isp = NULL; char *hdr = NULL; int hdr_size = 0; char *data = NULL; int data_size = 0; iscsi_hdr_t *ihp; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); /* pre-alloc recv header buffer for common actions */ hdr_size = sizeof (iscsi_hdr_t) + 255; /* 255 = one byte hlength */ hdr = (char *)kmem_zalloc(hdr_size, KM_SLEEP); ihp = (iscsi_hdr_t *)hdr; ASSERT(ihp != NULL); /* pre-alloc max_recv_size buffer for common actions */ data_size = icp->conn_params.max_recv_data_seg_len; data = (char *)kmem_zalloc(data_size, KM_SLEEP); ASSERT(data != NULL); do { /* Wait for the next iSCSI header */ rval = iscsi_net->recvhdr(icp->conn_socket, ihp, hdr_size, 0, (icp->conn_params.header_digest ? ISCSI_NET_HEADER_DIGEST : 0)); if (ISCSI_SUCCESS(rval)) { isp->sess_rx_lbolt = icp->conn_rx_lbolt = ddi_get_lbolt(); /* Perform specific hdr handling */ rval = iscsi_rx_process_hdr(icp, ihp, data, data_size); } /* * handle failures */ switch (rval) { case ISCSI_STATUS_SUCCESS: /* * If we successfully completed a receive * and we are in an IN_FLUSH state then * check the active queue count to see * if its empty. If its empty then force * a disconnect event on the connection. * This will move the session from IN_FLUSH * to FLUSHED and complete the login * parameter update. */ if ((isp->sess_state == ISCSI_SESS_STATE_IN_FLUSH) && (icp->conn_queue_active.count == 0)) { mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); } break; case ISCSI_STATUS_TCP_RX_ERROR: /* connection had an error */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T15); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_STATUS_HEADER_DIGEST_ERROR: /* * If we encounter a digest error we have to restart * all the connections on this session. per iSCSI * Level 0 Recovery. */ KSTAT_INC_CONN_ERR_HEADER_DIGEST(icp); mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_STATUS_DATA_DIGEST_ERROR: /* * We can continue with a data digest error. The * icmdp was flaged as having a crc problem. It * will be aborted when all data is received. This * saves us from restarting the session when we * might be able to keep it going. If the data * digest issue was really bad we will hit a * status protocol error on the next pdu, which * will force a connection retstart. */ KSTAT_INC_CONN_ERR_DATA_DIGEST(icp); break; case ISCSI_STATUS_PROTOCOL_ERROR: /* * A protocol problem was encountered. Reset * session to try and repair issue. */ KSTAT_INC_CONN_ERR_PROTOCOL(icp); mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_STATUS_INTERNAL_ERROR: /* * These should have all been handled before now. */ break; default: cmn_err(CE_WARN, "iscsi connection(%u) encountered " "unknown error(%d) on a receive", icp->conn_oid, rval); ASSERT(B_FALSE); } } while ((ISCSI_CONN_STATE_FULL_FEATURE(icp->conn_state)) && (iscsi_thread_wait(thread, 0) != 0)); kmem_free(hdr, hdr_size); kmem_free(data, data_size); } /* * iscsi_rx_process_hdr - This function collects data for all PDUs * that do not have data that will be mapped to a specific scsi_pkt. * Then for each hdr type fan out the processing. */ static iscsi_status_t iscsi_rx_process_hdr(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data, int data_size) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_sess_t *isp = NULL; ASSERT(icp != NULL); ASSERT(ihp != NULL); ASSERT(data != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); /* If this is not a SCSI_DATA_RSP we can go ahead and get the data */ if ((ihp->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_SCSI_DATA_RSP) { rval = iscsi_net->recvdata(icp->conn_socket, ihp, data, data_size, 0, (icp->conn_params.data_digest) ? ISCSI_NET_DATA_DIGEST : 0); if (!ISCSI_SUCCESS(rval)) { return (rval); } isp->sess_rx_lbolt = icp->conn_rx_lbolt = ddi_get_lbolt(); } /* fan out the hdr processing */ switch (ihp->opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_SCSI_DATA_RSP: rval = iscsi_rx_process_data_rsp(icp, ihp); break; case ISCSI_OP_SCSI_RSP: rval = iscsi_rx_process_cmd_rsp(icp, ihp, data); break; case ISCSI_OP_RTT_RSP: rval = iscsi_rx_process_rtt_rsp(icp, ihp, data); break; case ISCSI_OP_NOOP_IN: rval = iscsi_rx_process_nop(icp, ihp, data); break; case ISCSI_OP_REJECT_MSG: rval = iscsi_rx_process_reject_rsp(icp, ihp, data); break; case ISCSI_OP_SCSI_TASK_MGT_RSP: rval = iscsi_rx_process_task_mgt_rsp(icp, ihp, data); break; case ISCSI_OP_LOGOUT_RSP: rval = iscsi_rx_process_logout_rsp(icp, ihp, data); break; case ISCSI_OP_ASYNC_EVENT: rval = iscsi_rx_process_async_rsp(icp, ihp, data); break; case ISCSI_OP_TEXT_RSP: rval = iscsi_rx_process_text_rsp(icp, ihp, data); break; default: cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received an unsupported opcode 0x%02x", icp->conn_oid, ihp->opcode); rval = ISCSI_STATUS_PROTOCOL_ERROR; } return (rval); } /* * iscsi_rx_process_data_rsp - Processed received data header. Once * header is processed we read data off the connection directly into * the scsi_pkt to avoid duplicate bcopy of a large amount of data. * If this is the final data sequence denoted by the data response * PDU Status bit being set. We will not receive the SCSI response. * This bit denotes that the PDU is the successful completion of the * command. In this case complete the command. If This bit isn't * set we wait for more data or a scsi command response. */ static iscsi_status_t iscsi_rx_process_data_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_sess_t *isp = NULL; iscsi_data_rsp_hdr_t *idrhp = (iscsi_data_rsp_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; struct scsi_pkt *pkt = NULL; struct buf *bp = NULL; uint32_t offset = 0; uint32_t dlength = 0; char *bcp = NULL; ASSERT(icp != NULL); ASSERT(ihp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (idrhp->flags & ISCSI_FLAG_DATA_STATUS) { /* make sure we got status in order */ if (icp->conn_expstatsn == ntohl(idrhp->statsn)) { icp->conn_expstatsn++; } else { cmn_err(CE_WARN, "iscsi connection(%u) protocol error " "- received status out of order itt:0x%x " "statsn:0x%x expstatsn:0x%x", icp->conn_oid, idrhp->itt, ntohl(idrhp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } } /* match itt in the session's command table */ mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp(isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* * Holding the pending/active queue locks across the * iscsi_rx_data call later in this function may cause * deadlock during fault injections. Instead remove * the cmd from the active queue and release the locks. * Then before returning or completing the command * return the cmd to the active queue and reacquire * the locks. */ iscsi_dequeue_active_cmd(icp, icmdp); /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(idrhp->maxcmdsn), ntohl(idrhp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); /* shorthand some values */ pkt = icmdp->cmd_un.scsi.pkt; bp = icmdp->cmd_un.scsi.bp; offset = ntohl(idrhp->offset); dlength = n2h24(idrhp->dlength); /* * some poorly behaved targets have been observed * sending data-in pdu's during a write operation */ if (bp != NULL) { if (!(bp->b_flags & B_READ)) { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received data response during write operation " "itt:0x%x", icp->conn_oid, idrhp->itt); mutex_enter(&icp->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icp, icmdp); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* * We can't tolerate the target sending too much * data for our buffer */ if ((dlength > (bp->b_bcount - icmdp->cmd_un.scsi.data_transferred)) || (dlength > (bp->b_bcount - offset))) { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received too much data itt:0x%x", icp->conn_oid, idrhp->itt); mutex_enter(&icp->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icp, icmdp); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } bcp = ((char *)bp->b_un.b_addr) + offset; /* * Get the rest of the data and copy it directly into * the scsi_pkt. */ rval = iscsi_net->recvdata(icp->conn_socket, ihp, bcp, dlength, 0, (icp->conn_params.data_digest ? ISCSI_NET_DATA_DIGEST : 0)); if (ISCSI_SUCCESS(rval)) { KSTAT_ADD_CONN_RX_BYTES(icp, dlength); } else { /* If digest error flag icmdp with a crc error */ if (rval == ISCSI_STATUS_DATA_DIGEST_ERROR) { icmdp->cmd_crc_error_seen = B_TRUE; } mutex_enter(&icp->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icp, icmdp); mutex_exit(&icp->conn_queue_active.mutex); return (rval); } isp->sess_rx_lbolt = icp->conn_rx_lbolt = ddi_get_lbolt(); /* update icmdp statistics */ icmdp->cmd_un.scsi.data_transferred += dlength; } /* * We got status. This should only happen if we have * received all the data with no errors. The command * must be completed now, since we won't get a command * response PDU. The cmd_status and residual_count are * not meaningful unless status_present is set. */ if (idrhp->flags & ISCSI_FLAG_DATA_STATUS) { pkt->pkt_resid = 0; /* Check the residual count */ if (bp && (icmdp->cmd_un.scsi.data_transferred != bp->b_bcount)) { /* * We didn't xfer the expected amount of data - * the residual_count in the header is only valid * if the underflow flag is set. */ if (idrhp->flags & ISCSI_FLAG_DATA_UNDERFLOW) { pkt->pkt_resid = ntohl(idrhp->residual_count); } else { if (bp->b_bcount > icmdp->cmd_un.scsi.data_transferred) { /* Some data fell on the floor somehw */ pkt->pkt_resid = bp->b_bcount - icmdp->cmd_un.scsi.data_transferred; } } } pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_XFERRED_DATA | STATE_GOT_STATUS); if (((idrhp->cmd_status & STATUS_MASK) != STATUS_GOOD) && (icmdp->cmd_un.scsi.statuslen >= sizeof (struct scsi_arq_status)) && pkt->pkt_scbp) { /* * Not supposed to get exception status here! * We have no request sense data so just do the * best we can */ struct scsi_arq_status *arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; bzero(arqstat, sizeof (struct scsi_arq_status)); *((uchar_t *)&arqstat->sts_status) = idrhp->cmd_status; arqstat->sts_rqpkt_resid = sizeof (struct scsi_extended_sense); } else if (pkt->pkt_scbp) { /* just pass along the status we got */ pkt->pkt_scbp[0] = idrhp->cmd_status; } mutex_enter(&icp->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icp, icmdp); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); mutex_exit(&icp->conn_queue_active.mutex); } else { mutex_enter(&icp->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icp, icmdp); mutex_exit(&icp->conn_queue_active.mutex); } return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_cmd_rsp - Process received scsi command response. This * will contain sense data if the command was not successful. This data needs * to be copied into the scsi_pkt. Otherwise we just complete the IO. */ static iscsi_status_t iscsi_rx_process_cmd_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_sess_t *isp = icp->conn_sess; iscsi_scsi_rsp_hdr_t *issrhp = (iscsi_scsi_rsp_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; struct scsi_pkt *pkt = NULL; uint32_t dlength = 0; struct scsi_arq_status *arqstat = NULL; size_t senselen = 0; int statuslen = 0; /* make sure we get status in order */ if (icp->conn_expstatsn == ntohl(issrhp->statsn)) { icp->conn_expstatsn++; } else { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, issrhp->itt, ntohl(issrhp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp(isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(issrhp->maxcmdsn), ntohl(issrhp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); pkt = icmdp->cmd_un.scsi.pkt; if (issrhp->response) { /* The target failed the command. */ pkt->pkt_reason = CMD_TRAN_ERR; if (icmdp->cmd_un.scsi.bp) { pkt->pkt_resid = icmdp->cmd_un.scsi.bp->b_bcount; } else { pkt->pkt_resid = 0; } } else { /* success */ pkt->pkt_resid = 0; /* Check the residual count */ if ((icmdp->cmd_un.scsi.bp) && (icmdp->cmd_un.scsi.data_transferred != icmdp->cmd_un.scsi.bp->b_bcount)) { /* * We didn't xfer the expected amount of data - * the residual_count in the header is only * valid if the underflow flag is set. */ if (issrhp->flags & ISCSI_FLAG_CMD_UNDERFLOW) { pkt->pkt_resid = ntohl(issrhp->residual_count); } else { if (icmdp->cmd_un.scsi.bp->b_bcount > icmdp->cmd_un.scsi.data_transferred) { /* * Some data fell on the floor * somehow - probably a CRC error */ pkt->pkt_resid = icmdp->cmd_un.scsi.bp->b_bcount - icmdp->cmd_un.scsi.data_transferred; } } } /* set flags that tell SCSA that the command is complete */ if (icmdp->cmd_crc_error_seen == B_FALSE) { /* Set successful completion */ pkt->pkt_reason = CMD_CMPLT; if (icmdp->cmd_un.scsi.bp) { pkt->pkt_state |= (STATE_XFERRED_DATA | STATE_GOT_STATUS); } else { pkt->pkt_state |= STATE_GOT_STATUS; } } else { /* * Some of the data was found to have an incorrect * error at the protocol error. */ pkt->pkt_reason = CMD_PER_FAIL; pkt->pkt_statistics |= STAT_PERR; if (icmdp->cmd_un.scsi.bp) { pkt->pkt_resid = icmdp->cmd_un.scsi.bp->b_bcount; } else { pkt->pkt_resid = 0; } } dlength = n2h24(issrhp->dlength); /* * Process iSCSI Cmd Response Status * RFC 3720 Sectionn 10.4.2. */ switch (issrhp->cmd_status & STATUS_MASK) { case STATUS_GOOD: /* pass SCSI status up stack */ if (pkt->pkt_scbp) { pkt->pkt_scbp[0] = issrhp->cmd_status; } break; case STATUS_CHECK: /* * Verify we received a sense buffer and * that there is the correct amount of * request sense space to copy it to. */ if ((dlength > 1) && (pkt->pkt_scbp != NULL) && (icmdp->cmd_un.scsi.statuslen >= sizeof (struct scsi_arq_status))) { /* * If a bad command status is received we * need to reset the pkt_resid to zero. * The target driver compares its value * before checking other error flags. * (ex. check conditions) */ pkt->pkt_resid = 0; /* get sense length from first 2 bytes */ senselen = ((data[0] << 8) | data[1]) & (size_t)0xFFFF; /* Sanity-check on the sense length */ if ((senselen + 2) > dlength) { senselen = dlength - 2; } /* * If there was a Data Digest error then * the sense data cannot be trusted. */ if (icmdp->cmd_crc_error_seen) { senselen = 0; } /* automatic request sense */ arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; /* pass SCSI status up stack */ *((uchar_t *)&arqstat->sts_status) = issrhp->cmd_status; /* * Set the status for the automatic * request sense command */ arqstat->sts_rqpkt_state = (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS | STATE_ARQ_DONE); *((uchar_t *)&arqstat->sts_rqpkt_status) = STATUS_GOOD; arqstat->sts_rqpkt_reason = CMD_CMPLT; statuslen = icmdp->cmd_un.scsi.statuslen; if (senselen == 0) { /* auto request sense failed */ arqstat->sts_rqpkt_status.sts_chk = 1; arqstat->sts_rqpkt_resid = statuslen; } else if (senselen < statuslen) { /* auto request sense short */ arqstat->sts_rqpkt_resid = statuslen - senselen; } else { /* auto request sense complete */ arqstat->sts_rqpkt_resid = 0; } arqstat->sts_rqpkt_statistics = 0; pkt->pkt_state |= STATE_ARQ_DONE; if (icmdp->cmd_misc_flags & ISCSI_CMD_MISCFLAG_XARQ) { pkt->pkt_state |= STATE_XARQ_DONE; } /* copy auto request sense */ dlength = min(senselen, statuslen); if (dlength) { bcopy(&data[2], (uchar_t *)&arqstat-> sts_sensedata, dlength); } break; } /* FALLTHRU */ case STATUS_BUSY: case STATUS_RESERVATION_CONFLICT: case STATUS_QFULL: case STATUS_ACA_ACTIVE: default: /* * If a bad command status is received we need to * reset the pkt_resid to zero. The target driver * compares its value before checking other error * flags. (ex. check conditions) */ pkt->pkt_resid = 0; /* pass SCSI status up stack */ if (pkt->pkt_scbp) { pkt->pkt_scbp[0] = issrhp->cmd_status; } } } iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_rtt_rsp - Process received RTT. This means the target is * requesting data. */ /* ARGSUSED */ static iscsi_status_t iscsi_rx_process_rtt_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_sess_t *isp = (iscsi_sess_t *)icp->conn_sess; iscsi_rtt_hdr_t *irhp = (iscsi_rtt_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; struct buf *bp = NULL; uint32_t data_length; iscsi_status_t status = ISCSI_STATUS_PROTOCOL_ERROR; mutex_enter(&isp->sess_queue_pending.mutex); mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp(isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); mutex_exit(&isp->sess_queue_pending.mutex); return (status); } /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(irhp->maxcmdsn), ntohl(irhp->expcmdsn)); mutex_enter(&icmdp->cmd_mutex); mutex_exit(&isp->sess_cmdsn_mutex); bp = icmdp->cmd_un.scsi.bp; data_length = ntohl(irhp->data_length); /* * Perform boundary-checks per RFC 3720 (section 10.8.4). * The Desired Data Transfer Length must satisfy this relation: * * 0 < Desired Data Transfer Length <= MaxBurstLength */ if ((bp == NULL) || (data_length == 0)) { cmn_err(CE_WARN, "iscsi connection(%u) received r2t but pkt " "has no data itt:0x%x - protocol error", icp->conn_oid, irhp->itt); } else if (data_length > icp->conn_params.max_burst_length) { cmn_err(CE_WARN, "iscsi connection(%u) received r2t but pkt " "is larger than MaxBurstLength itt:0x%x len:0x%x - " "protocol error", icp->conn_oid, irhp->itt, data_length); } else { iscsi_handle_r2t(icp, icmdp, ntohl(irhp->data_offset), data_length, irhp->ttt); status = ISCSI_STATUS_SUCCESS; } mutex_exit(&icmdp->cmd_mutex); mutex_exit(&icp->conn_queue_active.mutex); mutex_exit(&isp->sess_queue_pending.mutex); return (status); } /* * iscsi_rx_process_nop - Process a received nop. If nop is in response * to a ping we sent update stats. If initiated by the target we need * to response back to the target with a nop. Schedule the response. */ /* ARGSUSED */ static iscsi_status_t iscsi_rx_process_nop(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_sess_t *isp = NULL; iscsi_nop_in_hdr_t *inihp = (iscsi_nop_in_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; ASSERT(icp != NULL); ASSERT(ihp != NULL); /* ASSERT(data != NULL) data is allowed to be NULL */ isp = icp->conn_sess; ASSERT(isp != NULL); if (icp->conn_expstatsn != ntohl(inihp->statsn)) { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, inihp->itt, ntohl(inihp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_enter(&isp->sess_queue_pending.mutex); mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (inihp->itt != ISCSI_RSVD_TASK_TAG) { if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp( isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); mutex_exit(&isp->sess_queue_pending.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } } /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(inihp->maxcmdsn), ntohl(inihp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); if ((inihp->itt != ISCSI_RSVD_TASK_TAG) && (inihp->ttt == ISCSI_RSVD_TASK_TAG)) { /* This is the only type of nop that incs. the expstatsn */ icp->conn_expstatsn++; /* * This is a targets response to our nop */ iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); } else if (inihp->ttt != ISCSI_RSVD_TASK_TAG) { /* * Target requested a nop. Send one. */ iscsi_handle_nop(icp, ISCSI_RSVD_TASK_TAG, inihp->ttt); } else { /* * This is a target-initiated ping that doesn't expect * a response; nothing to do except update our flow control * (which we do in all cases above). */ /* EMPTY */ } mutex_exit(&icp->conn_queue_active.mutex); mutex_exit(&isp->sess_queue_pending.mutex); return (rval); } /* * iscsi_rx_process_reject_rsp - The server rejected a PDU */ static iscsi_status_t iscsi_rx_process_reject_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_reject_rsp_hdr_t *irrhp = (iscsi_reject_rsp_hdr_t *)ihp; iscsi_sess_t *isp = NULL; uint32_t dlength = 0; iscsi_hdr_t *old_ihp = NULL; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(ihp != NULL); ASSERT(data != NULL); /* make sure we only Ack Status numbers that we've actually received. */ if (icp->conn_expstatsn == ntohl(irrhp->statsn)) { icp->conn_expstatsn++; } else { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, ihp->itt, ntohl(irrhp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* update expcmdsn and maxcmdsn */ mutex_enter(&isp->sess_cmdsn_mutex); iscsi_update_flow_control(isp, ntohl(irrhp->maxcmdsn), ntohl(irrhp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); /* If we don't have the rejected header we can't do anything */ dlength = n2h24(irrhp->dlength); if (dlength < sizeof (iscsi_hdr_t)) { return (ISCSI_STATUS_PROTOCOL_ERROR); } /* map old ihp */ old_ihp = (iscsi_hdr_t *)data; switch (irrhp->reason) { /* * ISCSI_REJECT_IMM_CMD_REJECT - Immediate Command Reject * too many immediate commands (original cmd can be resent) */ case ISCSI_REJECT_IMM_CMD_REJECT: /* * We have exceeded the server's capacity for outstanding * immediate commands. This must be a task management * command so try to find it in the abortingqueue and * complete it. */ if (!(old_ihp->opcode & ISCSI_OP_IMMEDIATE)) { /* Rejecting IMM but old old_hdr wasn't IMM */ return (ISCSI_STATUS_PROTOCOL_ERROR); } /* * We only send NOP and TASK_MGT as IMM. All other * cases should be considered as a protocol error. */ switch (old_ihp->opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_NOOP_OUT: /* * A ping was rejected - treat this like * ping response. The down side is we * didn't get an updated MaxCmdSn. */ break; case ISCSI_OP_SCSI_TASK_MGT_MSG: (void) iscsi_rx_process_rejected_tsk_mgt(icp, old_ihp); break; default: cmn_err(CE_WARN, "iscsi connection(%u) protocol error " "- received a reject for a command(0x%02x) not " "sent as an immediate", icp->conn_oid, old_ihp->opcode); return (ISCSI_STATUS_PROTOCOL_ERROR); } break; /* * For the rest of the reject cases just use the general * hammer of dis/reconnecting. This will resolve all * noted issues although could be more graceful. */ case ISCSI_REJECT_DATA_DIGEST_ERROR: case ISCSI_REJECT_CMD_BEFORE_LOGIN: case ISCSI_REJECT_SNACK_REJECT: case ISCSI_REJECT_PROTOCOL_ERROR: case ISCSI_REJECT_CMD_NOT_SUPPORTED: case ISCSI_REJECT_TASK_IN_PROGRESS: case ISCSI_REJECT_INVALID_DATA_ACK: case ISCSI_REJECT_INVALID_PDU_FIELD: case ISCSI_REJECT_LONG_OPERATION_REJECT: case ISCSI_REJECT_NEGOTIATION_RESET: default: cmn_err(CE_WARN, "iscsi connection(%u) closing connection - " "target requested itt:0x%x reason:0x%x", icp->conn_oid, ihp->itt, irrhp->reason); return (ISCSI_STATUS_PROTOCOL_ERROR); } return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_rejected_tsk_mgt - */ static iscsi_status_t iscsi_rx_process_rejected_tsk_mgt(iscsi_conn_t *icp, iscsi_hdr_t *old_ihp) { iscsi_sess_t *isp = NULL; iscsi_cmd_t *icmdp = NULL; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(old_ihp != NULL); ASSERT(icp->conn_sess != NULL); mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp( isp, old_ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_exit(&isp->sess_cmdsn_mutex); switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_ABORT: case ISCSI_CMD_TYPE_RESET: iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E4, icp->conn_sess); break; /* We don't send any other task mgr types */ default: ASSERT(B_FALSE); break; } mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_task_mgt_rsp - */ /* ARGSUSED */ static iscsi_status_t iscsi_rx_process_task_mgt_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, void *data) { iscsi_sess_t *isp = NULL; iscsi_scsi_task_mgt_rsp_hdr_t *istmrhp = NULL; iscsi_cmd_t *icmdp = NULL; ASSERT(ihp != NULL); ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); istmrhp = (iscsi_scsi_task_mgt_rsp_hdr_t *)ihp; if (icp->conn_expstatsn == ntohl(istmrhp->statsn)) { icp->conn_expstatsn++; } else { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, istmrhp->itt, ntohl(istmrhp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* make sure we only Ack Status numbers that we've actually received. */ mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp(isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* update expcmdsn and maxcmdn */ iscsi_update_flow_control(isp, ntohl(istmrhp->maxcmdsn), ntohl(istmrhp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_ABORT: case ISCSI_CMD_TYPE_RESET: switch (istmrhp->response) { case SCSI_TCP_TM_RESP_COMPLETE: /* success */ iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); break; case SCSI_TCP_TM_RESP_NO_TASK: /* * If the array no longer knows about * an ABORT RTT and we no longer have * a parent SCSI command it was just * completed, free this ABORT resource. * Otherwise FALLTHRU this will flag a * protocol problem. */ if ((icmdp->cmd_type == ISCSI_CMD_TYPE_ABORT) && (icmdp->cmd_un.abort.icmdp == NULL)) { iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E4, isp); break; } /* FALLTHRU */ case SCSI_TCP_TM_RESP_NO_LUN: case SCSI_TCP_TM_RESP_TASK_ALLEGIANT: case SCSI_TCP_TM_RESP_NO_FAILOVER: case SCSI_TCP_TM_RESP_IN_PRGRESS: case SCSI_TCP_TM_RESP_REJECTED: default: /* * Something is out of sync. Flush * active queues and resync the * the connection to try and recover * to a known state. */ mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } break; default: cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received a task mgt response for a non-task mgt " "cmd itt:0x%x type:%d", icp->conn_oid, istmrhp->itt, icmdp->cmd_type); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_logout - * */ /* ARGSUSED */ static iscsi_status_t iscsi_rx_process_logout_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_sess_t *isp = icp->conn_sess; iscsi_logout_rsp_hdr_t *ilrhp = (iscsi_logout_rsp_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; ASSERT(icp != NULL); ASSERT(ihp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (icp->conn_expstatsn != ntohl(ilrhp->statsn)) { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, ilrhp->itt, ntohl(ilrhp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (ilrhp->itt != ISCSI_RSVD_TASK_TAG) { if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp( isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } } /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(ilrhp->maxcmdsn), ntohl(ilrhp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); switch (ilrhp->response) { case ISCSI_LOGOUT_CID_NOT_FOUND: /* * If the target doesn't know about our connection * then we can consider our self disconnected. */ /* FALLTHRU */ case ISCSI_LOGOUT_RECOVERY_UNSUPPORTED: /* * We don't support ErrorRecovery levels above 0 * currently so consider this success. */ /* FALLTHRU */ case ISCSI_LOGOUT_CLEANUP_FAILED: /* * per spec. "cleanup failed for various reasons." * Although those various reasons are undefined. * Not sure what to do here. So fake success, * which will disconnect the connection. */ /* FALLTHRU */ case ISCSI_LOGOUT_SUCCESS: iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); mutex_exit(&icp->conn_queue_active.mutex); /* logout completed successfully notify the conn */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T17); mutex_exit(&icp->conn_state_mutex); break; default: mutex_exit(&icp->conn_queue_active.mutex); rval = ISCSI_STATUS_PROTOCOL_ERROR; } return (rval); } /* * iscsi_rx_process_logout - * */ /* ARGSUSED */ static iscsi_status_t iscsi_rx_process_async_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_async_evt_hdr_t *iaehp = (iscsi_async_evt_hdr_t *)ihp; ASSERT(icp != NULL); ASSERT(ihp != NULL); ASSERT(icp->conn_sess != NULL); if (icp->conn_expstatsn != ntohl(iaehp->statsn)) { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, ihp->itt, ntohl(iaehp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } switch (iaehp->async_event) { case ISCSI_ASYNC_EVENT_SCSI_EVENT: /* * SCSI asynchronous event is reported in * the sense data. Sense data that accompanies * the report in the data segment identifies the * condition. If the target supports SCSI * asynchronous events reporting (see [SAM2]) * as indicated in the stardard INQUIRY data * (see [SPC3]), its use may be enabled by * parameters in the SCSI control mode page * (see [SPC3]). * * T-10 has removed SCSI asunchronous events * from the standard. Although we have seen * a couple targets still spending these requests. * Those targets were specifically sending them * for notification of a LUN/Volume change * (ex. LUN addition/removal). Take a general * action to these events of dis/reconnecting. * Once reconnected we perform a reenumeration. */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT: /* Target has requested this connection to logout. */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION: /* * Target is going to drop our connection. * param1 - CID which will be dropped. * param2 - Min time to reconnect. * param3 - Max time to reconnect. * * For now just let fail as another disconnect. * * MC/S Once we support > 1 connections then * we need to check the CID and drop that * specific connection. */ iscsi_conn_set_login_min_max(icp, iaehp->param2, iaehp->param3); mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS: /* * Target is going to drop ALL connections. * param2 - Min time to reconnect. * param3 - Max time to reconnect. * * For now just let fail as anyother disconnect. * * MC/S Once we support more than > 1 connections * then we need to drop all connections on the * session. */ iscsi_conn_set_login_min_max(icp, iaehp->param2, iaehp->param3); mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION: /* * Target requests parameter negotiation * on this connection. * * The initiator must honor this request. For * now we will request a logout. We can't * just ignore this or it might force corruption? */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; case ISCSI_ASYNC_EVENT_VENDOR_SPECIFIC: /* * We currently don't handle any vendor * specific async events. So just ignore * the request. */ mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T14); mutex_exit(&icp->conn_state_mutex); break; default: rval = ISCSI_STATUS_PROTOCOL_ERROR; } return (rval); } /* * iscsi_rx_process_text_rsp - processes iSCSI text response. It sets * the cmd_result field of the command data structure with the actual * status value instead of returning the status value. The return value * is SUCCESS in order to let iscsi_handle_text control the operation of * a text request. * Test requests are a handled a little different than other types of * iSCSI commands because the initiator sends additional empty text requests * in order to obtain the remaining responses required to complete the * request. iscsi_handle_text controls the operation of text request, while * iscsi_rx_process_text_rsp just process the current response. */ static iscsi_status_t iscsi_rx_process_text_rsp(iscsi_conn_t *icp, iscsi_hdr_t *ihp, char *data) { iscsi_sess_t *isp = NULL; iscsi_text_rsp_hdr_t *ithp = (iscsi_text_rsp_hdr_t *)ihp; iscsi_cmd_t *icmdp = NULL; boolean_t final = B_FALSE; uint32_t data_len; ASSERT(icp != NULL); ASSERT(ihp != NULL); ASSERT(data != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (icp->conn_expstatsn == ntohl(ithp->statsn)) { icp->conn_expstatsn++; } else { cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received status out of order itt:0x%x statsn:0x%x " "expstatsn:0x%x", icp->conn_oid, ithp->itt, ntohl(ithp->statsn), icp->conn_expstatsn); return (ISCSI_STATUS_PROTOCOL_ERROR); } mutex_enter(&icp->conn_queue_active.mutex); mutex_enter(&isp->sess_cmdsn_mutex); if (!ISCSI_SUCCESS(iscsi_rx_process_itt_to_icmdp(isp, ihp, &icmdp))) { mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* update expcmdsn and maxcmdsn */ iscsi_update_flow_control(isp, ntohl(ithp->maxcmdsn), ntohl(ithp->expcmdsn)); mutex_exit(&isp->sess_cmdsn_mutex); /* update local final response flag */ if (ithp->flags & ISCSI_FLAG_FINAL) { final = B_TRUE; } /* * validate received TTT value. RFC3720 specifies the following: * - F bit set to 1 MUST have a reserved TTT value 0xffffffff * - F bit set to 0 MUST have a non-reserved TTT value !0xffffffff * In addition, the received TTT value must not change between * responses of a long text response */ if (((final == B_TRUE) && (ithp->ttt != ISCSI_RSVD_TASK_TAG)) || ((final == B_FALSE) && (ithp->ttt == ISCSI_RSVD_TASK_TAG))) { icmdp->cmd_result = ISCSI_STATUS_PROTOCOL_ERROR; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; mutex_exit(&icp->conn_queue_active.mutex); cmn_err(CE_WARN, "iscsi connection(%u) protocol error - " "received text response with invalid flags:0x%x or " "ttt:0x%x", icp->conn_oid, ithp->flags, ithp->itt); return (ISCSI_STATUS_PROTOCOL_ERROR); } if ((icmdp->cmd_un.text.stage == ISCSI_CMD_TEXT_INITIAL_REQ) && (ithp->ttt == ISCSI_RSVD_TASK_TAG) && (final == B_FALSE)) { /* TTT should have matched reserved value */ icmdp->cmd_result = ISCSI_STATUS_PROTOCOL_ERROR; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; mutex_exit(&icp->conn_queue_active.mutex); cmn_err(CE_WARN, "iscsi connection(%u) protocol " "error - received text response with invalid " "ttt:0x%x", icp->conn_oid, ithp->ttt); return (ISCSI_STATUS_PROTOCOL_ERROR); } /* * If this is first response, save away TTT value for later use * in a long text request/response sequence */ if (icmdp->cmd_un.text.stage == ISCSI_CMD_TEXT_INITIAL_REQ) { icmdp->cmd_un.text.ttt = ithp->ttt; } data_len = ntoh24(ithp->dlength); /* check whether enough buffer available to copy data */ if ((icmdp->cmd_un.text.total_rx_len + data_len) > icmdp->cmd_un.text.buf_len) { icmdp->cmd_un.text.total_rx_len += data_len; icmdp->cmd_result = ISCSI_STATUS_DATA_OVERFLOW; /* * DATA_OVERFLOW will result in a SUCCESS return so that * iscsi_handle_text can continue to obtain the remaining * text response if needed. */ } else { char *buf_data = (icmdp->cmd_un.text.buf + icmdp->cmd_un.text.offset); bcopy(data, buf_data, data_len); icmdp->cmd_un.text.offset += data_len; icmdp->cmd_un.text.total_rx_len += data_len; icmdp->cmd_result = ISCSI_STATUS_SUCCESS; bcopy(ithp->rsvd4, icmdp->cmd_un.text.lun, sizeof (icmdp->cmd_un.text.lun)); } /* update stage */ if (final == B_TRUE) { icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; } else { icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_CONTINUATION; } iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E3, isp); mutex_exit(&icp->conn_queue_active.mutex); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_rx_process_itt_to_icmdp - Lookup itt in the session's * cmd table to find matching icmdp. Verify itt in hdr and * icmdp are the same. */ static iscsi_status_t iscsi_rx_process_itt_to_icmdp(iscsi_sess_t *isp, iscsi_hdr_t *ihp, iscsi_cmd_t **icmdp) { int cmd_table_idx = 0; ASSERT(isp != NULL); ASSERT(ihp != NULL); ASSERT(icmdp != NULL); ASSERT(mutex_owned(&isp->sess_cmdsn_mutex)); /* try to find an associated iscsi_pkt */ cmd_table_idx = ihp->itt % ISCSI_CMD_TABLE_SIZE; if (isp->sess_cmd_table[cmd_table_idx] == NULL) { cmn_err(CE_WARN, "iscsi session(%u) protocol error - " "received unknown itt:0x%x - protocol error", isp->sess_oid, ihp->itt); return (ISCSI_STATUS_INTERNAL_ERROR); } /* verify itt */ if (isp->sess_cmd_table[cmd_table_idx]->cmd_itt != ihp->itt) { cmn_err(CE_WARN, "iscsi session(%u) received itt:0x%x " " which is out of sync with itt:0x%x", isp->sess_oid, ihp->itt, isp->sess_cmd_table[cmd_table_idx]->cmd_itt); return (ISCSI_STATUS_INTERNAL_ERROR); } /* ensure that icmdp is still in Active state */ if (isp->sess_cmd_table[cmd_table_idx]->cmd_state != ISCSI_CMD_STATE_ACTIVE) { cmn_err(CE_WARN, "iscsi session(%u) received itt:0x%x " "but icmdp (%p) is not in active state", isp->sess_oid, ihp->itt, (void *)isp->sess_cmd_table[cmd_table_idx]); return (ISCSI_STATUS_INTERNAL_ERROR); } /* make sure this is a SCSI cmd */ *icmdp = isp->sess_cmd_table[cmd_table_idx]; return (ISCSI_STATUS_SUCCESS); } /* * +--------------------------------------------------------------------+ * | End of protocol receive routines | * +--------------------------------------------------------------------+ */ /* * +--------------------------------------------------------------------+ * | Beginning of protocol send routines | * +--------------------------------------------------------------------+ */ /* * iscsi_tx_thread - This thread is the driving point for all * iSCSI PDUs after login. No PDUs should call sendpdu() * directly they should be funneled through iscsi_tx_thread. */ void iscsi_tx_thread(iscsi_thread_t *thread, void *arg) { iscsi_conn_t *icp = (iscsi_conn_t *)arg; iscsi_sess_t *isp = NULL; iscsi_cmd_t *icmdp = NULL; clock_t tout; int ret = 1; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); ASSERT(thread != NULL); ASSERT(thread->signature == SIG_ISCSI_THREAD); tout = SEC_TO_TICK(1); /* * Transfer icmdps until shutdown by owning session. */ while (ret != 0) { isp->sess_window_open = B_TRUE; /* * While the window is open, there are commands available * to send and the session state allows those commands to * be sent try to transfer them. */ mutex_enter(&isp->sess_queue_pending.mutex); while ((isp->sess_window_open == B_TRUE) && ((icmdp = isp->sess_queue_pending.head) != NULL) && (((icmdp->cmd_type != ISCSI_CMD_TYPE_SCSI) && (ISCSI_CONN_STATE_FULL_FEATURE(icp->conn_state))) || (icp->conn_state == ISCSI_CONN_STATE_LOGGED_IN))) { /* update command with this connection info */ icmdp->cmd_conn = icp; /* attempt to send this command */ iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E2, isp); ASSERT(!mutex_owned(&isp->sess_queue_pending.mutex)); mutex_enter(&isp->sess_queue_pending.mutex); } mutex_exit(&isp->sess_queue_pending.mutex); /* * Go to sleep until there is something new * to process (awoken via cv_boardcast). * Or the timer goes off. */ ret = iscsi_thread_wait(thread, tout); } } /* * iscsi_tx_cmd - transfers icmdp across wire as iscsi pdu * * Just prior to sending the command to the networking layer the * pending queue lock will be dropped. At this point only local * resources will be used, not the icmdp. Holding the queue lock * across the networking call can lead to a hang. (This is due * to the the target driver and networking layers competing use * of the timeout() resources and the queue lock being held for * both sides.) Upon the completion of this command the lock * will have been re-acquired. */ iscsi_status_t iscsi_tx_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_INTERNAL_ERROR; ASSERT(isp != NULL); ASSERT(icmdp != NULL); /* transfer specific command type */ switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: rval = iscsi_tx_scsi(isp, icmdp); break; case ISCSI_CMD_TYPE_R2T: rval = iscsi_tx_r2t(isp, icmdp); break; case ISCSI_CMD_TYPE_NOP: rval = iscsi_tx_nop(isp, icmdp); break; case ISCSI_CMD_TYPE_ABORT: rval = iscsi_tx_abort(isp, icmdp); break; case ISCSI_CMD_TYPE_RESET: rval = iscsi_tx_reset(isp, icmdp); break; case ISCSI_CMD_TYPE_LOGOUT: rval = iscsi_tx_logout(isp, icmdp); break; case ISCSI_CMD_TYPE_TEXT: rval = iscsi_tx_text(isp, icmdp); break; default: ASSERT(FALSE); } ASSERT(!mutex_owned(&isp->sess_queue_pending.mutex)); return (rval); } /* * a variable length cdb can be up to 16K, but we obviously don't want * to put that on the stack; go with 200 bytes; if we get something * bigger than that we will kmem_alloc a buffer */ #define DEF_CDB_LEN 200 /* * given the size of the cdb, return how many bytes the header takes, * which is the sizeof addl_hdr_t + the CDB size, minus the 16 bytes * stored in the basic header, minus sizeof (ahs_extscb) */ #define ADDLHDRSZ(x) (sizeof (iscsi_addl_hdr_t) + (x) - \ 16 - 4) /* * iscsi_tx_scsi - * */ static iscsi_status_t iscsi_tx_scsi(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; struct scsi_pkt *pkt = NULL; struct buf *bp = NULL; union { iscsi_scsi_cmd_hdr_t isch; iscsi_addl_hdr_t iah; uchar_t arr[ADDLHDRSZ(DEF_CDB_LEN)]; } hdr_un; iscsi_scsi_cmd_hdr_t *ihp = (iscsi_scsi_cmd_hdr_t *)&hdr_un.isch; int cdblen = 0; size_t buflen = 0; uint32_t imdata = 0; uint32_t first_burst_length = 0; ASSERT(isp != NULL); ASSERT(icmdp != NULL); pkt = icmdp->cmd_un.scsi.pkt; ASSERT(pkt != NULL); bp = icmdp->cmd_un.scsi.bp; icp = icmdp->cmd_conn; ASSERT(icp != NULL); /* Reset counts in case we are on a retry */ icmdp->cmd_un.scsi.data_transferred = 0; if (icmdp->cmd_un.scsi.cmdlen > DEF_CDB_LEN) { cdblen = icmdp->cmd_un.scsi.cmdlen; ihp = kmem_zalloc(ADDLHDRSZ(cdblen), KM_SLEEP); } else { /* * only bzero the basic header; the additional header * will be set up correctly later, if needed */ bzero(ihp, sizeof (iscsi_scsi_cmd_hdr_t)); } ihp->opcode = ISCSI_OP_SCSI_CMD; ihp->itt = icmdp->cmd_itt; mutex_enter(&isp->sess_cmdsn_mutex); ihp->cmdsn = htonl(isp->sess_cmdsn); isp->sess_cmdsn++; mutex_exit(&isp->sess_cmdsn_mutex); ihp->expstatsn = htonl(icp->conn_expstatsn); icp->conn_laststatsn = icp->conn_expstatsn; pkt->pkt_state = (STATE_GOT_BUS | STATE_GOT_TARGET); pkt->pkt_reason = CMD_INCOMPLETE; /* * Sestion 12.11 of the iSCSI specification has a good table * describing when uncolicited data and/or immediate data * should be sent. */ bp = icmdp->cmd_un.scsi.bp; if ((bp != NULL) && bp->b_bcount) { buflen = bp->b_bcount; first_burst_length = icp->conn_params.first_burst_length; if (bp->b_flags & B_READ) { ihp->flags = ISCSI_FLAG_FINAL; /* * fix problem where OS sends bp (B_READ & * b_bcount!=0) for a TUR or START_STOP. * (comment came from cisco code.) */ if ((pkt->pkt_cdbp[0] != SCMD_TEST_UNIT_READY) && (pkt->pkt_cdbp[0] != SCMD_START_STOP)) { ihp->flags |= ISCSI_FLAG_CMD_READ; ihp->data_length = htonl(buflen); } } else { ihp->flags = ISCSI_FLAG_CMD_WRITE; /* * FinalBit on the the iSCSI PDU denotes this * is the last PDU in the sequence. * * initial_r2t = true means R2T is required * for additional PDU, so there will be no more * unsolicited PDUs following */ if (icp->conn_params.initial_r2t) { ihp->flags |= ISCSI_FLAG_FINAL; } /* Check if we should send ImmediateData */ if (icp->conn_params.immediate_data) { imdata = MIN(MIN(buflen, first_burst_length), icmdp->cmd_conn->conn_params. max_xmit_data_seg_len); /* * if everything fits immediate, or * we can send all burst data immediate * (not unsol), set F */ if ((imdata == buflen) || (imdata == first_burst_length)) { ihp->flags |= ISCSI_FLAG_FINAL; } hton24(ihp->dlength, imdata); } /* total data transfer length */ ihp->data_length = htonl(buflen); } } else { ihp->flags = ISCSI_FLAG_FINAL; buflen = 0; } /* tagged queuing */ if (pkt->pkt_flags & FLAG_HTAG) { ihp->flags |= ISCSI_ATTR_HEAD_OF_QUEUE; } else if (pkt->pkt_flags & FLAG_OTAG) { ihp->flags |= ISCSI_ATTR_ORDERED; } else if (pkt->pkt_flags & FLAG_STAG) { ihp->flags |= ISCSI_ATTR_SIMPLE; } else { /* ihp->flags |= ISCSI_ATTR_UNTAGGED; */ /* EMPTY */ } /* iscsi states lun is based on spc.2 */ ISCSI_LUN_BYTE_COPY(ihp->lun, icmdp->cmd_un.scsi.lun); if (icmdp->cmd_un.scsi.cmdlen <= 16) { /* copy the SCSI Command Block into the PDU */ bcopy(pkt->pkt_cdbp, ihp->scb, icmdp->cmd_un.scsi.cmdlen); } else { iscsi_addl_hdr_t *iahp; iahp = (iscsi_addl_hdr_t *)ihp; ihp->hlength = (ADDLHDRSZ(icmdp->cmd_un.scsi.cmdlen) - sizeof (iscsi_scsi_cmd_hdr_t) + 3) / 4; iahp->ahs_hlen_hi = 0; iahp->ahs_hlen_lo = (icmdp->cmd_un.scsi.cmdlen - 15); iahp->ahs_key = 0x01; iahp->ahs_resv = 0; bcopy(pkt->pkt_cdbp, ihp->scb, 16); bcopy(((char *)pkt->pkt_cdbp) + 16, &iahp->ahs_extscb[0], icmdp->cmd_un.scsi.cmdlen); } /* * Update all values before transfering. * We should never touch the icmdp after * transfering if there is no more data * to send. The only case the sendpdu() * will fail is a on a connection disconnect * in that case the command will be flushed. */ pkt->pkt_state |= STATE_SENT_CMD; icmdp->cmd_un.scsi.data_transferred += imdata; /* * Check if there is additional data to transfer beyond what * will be sent as part of the initial command. If InitialR2T * is disabled then we should fake up a R2T so all the data, * up to first burst length, is sent in an unsolicited * fashion. We have already sent as much immediate data * as possible. */ if ((buflen > 0) && ((bp->b_flags & B_READ) == 0) && (icp->conn_params.initial_r2t == 0) && (MIN(first_burst_length, buflen) - imdata > 0)) { uint32_t xfer_len = MIN(first_burst_length, buflen) - imdata; /* data will be chunked at tx */ iscsi_handle_r2t(icp, icmdp, imdata, xfer_len, ISCSI_RSVD_TASK_TAG); } /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); /* Transfer Cmd PDU */ if (imdata) { rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)ihp, icmdp->cmd_un.scsi.bp->b_un.b_addr, ISCSI_CONN_TO_NET_DIGEST(icp)); if (ISCSI_SUCCESS(rval)) { KSTAT_ADD_CONN_TX_BYTES(icp, imdata); } } else { rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)ihp, NULL, ISCSI_CONN_TO_NET_DIGEST(icp)); } if (cdblen) { kmem_free(ihp, ADDLHDRSZ(cdblen)); } return (rval); } /* * iscsi_tx_r2t - * */ static iscsi_status_t iscsi_tx_r2t(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_cmd_t *orig_icmdp = NULL; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp); orig_icmdp = icmdp->cmd_un.r2t.icmdp; ASSERT(orig_icmdp); /* validate the offset and length against the buffer size */ if ((icmdp->cmd_un.r2t.offset + icmdp->cmd_un.r2t.length) > orig_icmdp->cmd_un.scsi.bp->b_bcount) { cmn_err(CE_WARN, "iscsi session(%u) ignoring invalid r2t " "for icmd itt:0x%x offset:0x%x length:0x%x bufsize:0x%lx", isp->sess_oid, icmdp->cmd_itt, icmdp->cmd_un.r2t.offset, icmdp->cmd_un.r2t.length, orig_icmdp->cmd_un.scsi.bp-> b_bcount); mutex_exit(&isp->sess_queue_pending.mutex); return (ISCSI_STATUS_INTERNAL_ERROR); } ASSERT(orig_icmdp->cmd_un.scsi.r2t_icmdp); rval = iscsi_tx_data(isp, icp, orig_icmdp, icmdp->cmd_ttt, icmdp->cmd_un.r2t.length, icmdp->cmd_un.r2t.offset); mutex_enter(&orig_icmdp->cmd_mutex); orig_icmdp->cmd_un.scsi.r2t_icmdp = NULL; icmdp->cmd_un.r2t.icmdp = NULL; /* * we're finished with this r2t; there could be another r2t * waiting on us to finish, so signal it. */ cv_broadcast(&orig_icmdp->cmd_completion); mutex_exit(&orig_icmdp->cmd_mutex); /* * the parent command may be waiting for us to finish; if so, * wake the _ic_ thread */ if ((orig_icmdp->cmd_state == ISCSI_CMD_STATE_COMPLETED) && (ISCSI_SESS_STATE_FULL_FEATURE(isp->sess_state)) && (orig_icmdp->cmd_un.scsi.r2t_more == B_FALSE)) iscsi_thread_send_wakeup(isp->sess_ic_thread); ASSERT(!mutex_owned(&isp->sess_queue_pending.mutex)); return (rval); } /* * iscsi_tx_data - */ static iscsi_status_t iscsi_tx_data(iscsi_sess_t *isp, iscsi_conn_t *icp, iscsi_cmd_t *icmdp, uint32_t ttt, size_t datalen, size_t offset) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; struct buf *bp = NULL; size_t remainder = 0; size_t chunk = 0; char *data = NULL; uint32_t data_sn = 0; iscsi_data_hdr_t idhp; uint32_t itt; uint32_t lun; ASSERT(isp != NULL); ASSERT(icp != NULL); ASSERT(icmdp != NULL); bp = icmdp->cmd_un.scsi.bp; /* verify there is data to send */ if (bp == NULL) { mutex_exit(&isp->sess_queue_pending.mutex); return (ISCSI_STATUS_INTERNAL_ERROR); } itt = icmdp->cmd_itt; lun = icmdp->cmd_un.scsi.lun; /* * update the LUN with the amount of data we will * transfer. If there is a failure it's because of * a network fault and the command will get flushed. */ icmdp->cmd_un.scsi.data_transferred += datalen; /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); remainder = datalen; while (remainder) { /* Check so see if we need to chunk the data */ if ((icp->conn_params.max_xmit_data_seg_len > 0) && (remainder > icp->conn_params.max_xmit_data_seg_len)) { chunk = icp->conn_params.max_xmit_data_seg_len; } else { chunk = remainder; } /* setup iscsi data hdr */ bzero(&idhp, sizeof (iscsi_data_hdr_t)); idhp.opcode = ISCSI_OP_SCSI_DATA; idhp.itt = itt; idhp.ttt = ttt; ISCSI_LUN_BYTE_COPY(idhp.lun, lun); idhp.expstatsn = htonl(icp->conn_expstatsn); icp->conn_laststatsn = icp->conn_expstatsn; idhp.datasn = htonl(data_sn); data_sn++; idhp.offset = htonl(offset); hton24(idhp.dlength, chunk); if (chunk == remainder) { idhp.flags = ISCSI_FLAG_FINAL; /* final chunk */ } /* setup data */ data = bp->b_un.b_addr + offset; /* * Keep track of how much data we have * transfer so far and how much is remaining. */ remainder -= chunk; offset += chunk; rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&idhp, data, ISCSI_CONN_TO_NET_DIGEST(icp)); if (ISCSI_SUCCESS(rval)) { KSTAT_ADD_CONN_TX_BYTES(icp, chunk); } else { break; } } return (rval); } /* * iscsi_tx_nop - * */ static iscsi_status_t iscsi_tx_nop(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_nop_out_hdr_t inohp; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); bzero(&inohp, sizeof (iscsi_nop_out_hdr_t)); inohp.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; inohp.flags = ISCSI_FLAG_FINAL; inohp.itt = icmdp->cmd_itt; inohp.ttt = icmdp->cmd_ttt; mutex_enter(&isp->sess_cmdsn_mutex); inohp.cmdsn = htonl(isp->sess_cmdsn); mutex_exit(&isp->sess_cmdsn_mutex); inohp.expstatsn = htonl(icp->conn_expstatsn); icp->conn_laststatsn = icp->conn_expstatsn; /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&inohp, NULL, ISCSI_CONN_TO_NET_DIGEST(icp)); return (rval); } /* * iscsi_tx_abort - * */ static iscsi_status_t iscsi_tx_abort(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_scsi_task_mgt_hdr_t istmh; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); bzero(&istmh, sizeof (iscsi_scsi_task_mgt_hdr_t)); mutex_enter(&isp->sess_cmdsn_mutex); istmh.cmdsn = htonl(isp->sess_cmdsn); mutex_exit(&isp->sess_cmdsn_mutex); istmh.expstatsn = htonl(icp->conn_expstatsn); icp->conn_laststatsn = icp->conn_expstatsn; istmh.itt = icmdp->cmd_itt; istmh.opcode = ISCSI_OP_SCSI_TASK_MGT_MSG | ISCSI_OP_IMMEDIATE; istmh.function = ISCSI_FLAG_FINAL | ISCSI_TM_FUNC_ABORT_TASK; ISCSI_LUN_BYTE_COPY(istmh.lun, icmdp->cmd_un.abort.icmdp->cmd_un.scsi.lun); istmh.rtt = icmdp->cmd_un.abort.icmdp->cmd_itt; /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&istmh, NULL, ISCSI_CONN_TO_NET_DIGEST(icp)); return (rval); } /* * iscsi_tx_reset - * */ static iscsi_status_t iscsi_tx_reset(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_scsi_task_mgt_hdr_t istmh; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); bzero(&istmh, sizeof (iscsi_scsi_task_mgt_hdr_t)); istmh.opcode = ISCSI_OP_SCSI_TASK_MGT_MSG | ISCSI_OP_IMMEDIATE; mutex_enter(&isp->sess_cmdsn_mutex); istmh.cmdsn = htonl(isp->sess_cmdsn); mutex_exit(&isp->sess_cmdsn_mutex); istmh.expstatsn = htonl(icp->conn_expstatsn); istmh.itt = icmdp->cmd_itt; switch (icmdp->cmd_un.reset.level) { case RESET_LUN: istmh.function = ISCSI_FLAG_FINAL | ISCSI_TM_FUNC_LOGICAL_UNIT_RESET; ISCSI_LUN_BYTE_COPY(istmh.lun, icmdp->cmd_lun->lun_num); break; case RESET_TARGET: case RESET_BUS: istmh.function = ISCSI_FLAG_FINAL | ISCSI_TM_FUNC_TARGET_WARM_RESET; break; default: /* unsupported / unknown level */ ASSERT(FALSE); break; } /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&istmh, NULL, ISCSI_CONN_TO_NET_DIGEST(icp)); return (rval); } /* * iscsi_tx_logout - * */ static iscsi_status_t iscsi_tx_logout(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_logout_hdr_t ilh; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); bzero(&ilh, sizeof (iscsi_logout_hdr_t)); ilh.opcode = ISCSI_OP_LOGOUT_CMD | ISCSI_OP_IMMEDIATE; ilh.flags = ISCSI_FLAG_FINAL | ISCSI_LOGOUT_REASON_CLOSE_SESSION; ilh.itt = icmdp->cmd_itt; ilh.cid = icp->conn_cid; mutex_enter(&isp->sess_cmdsn_mutex); ilh.cmdsn = htonl(isp->sess_cmdsn); mutex_exit(&isp->sess_cmdsn_mutex); ilh.expstatsn = htonl(icp->conn_expstatsn); /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&ilh, NULL, ISCSI_CONN_TO_NET_DIGEST(icp)); return (rval); } /* * iscsi_tx_text - setup iSCSI text request header and send PDU with * data given in the buffer attached to the command. For a single * text request, the target may need to send its response in multiple * text response. In this case, empty text requests are sent after * each received response to notify the target the initiator is ready * for more response. For the initial request, the data_len field in * the text specific portion of a command is set to the amount of data * the initiator wants to send as part of the request. If additional * empty text requests are required for long responses, the data_len * field is set to 0 by the iscsi_handle_text function. */ static iscsi_status_t iscsi_tx_text(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp = NULL; iscsi_text_hdr_t ith; ASSERT(isp != NULL); ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); bzero(&ith, sizeof (iscsi_text_hdr_t)); ith.opcode = ISCSI_OP_TEXT_CMD; ith.flags = ISCSI_FLAG_FINAL; hton24(ith.dlength, icmdp->cmd_un.text.data_len); ith.itt = icmdp->cmd_itt; ith.ttt = icmdp->cmd_un.text.ttt; mutex_enter(&isp->sess_cmdsn_mutex); ith.cmdsn = htonl(isp->sess_cmdsn); isp->sess_cmdsn++; ith.expstatsn = htonl(icp->conn_expstatsn); mutex_exit(&isp->sess_cmdsn_mutex); bcopy(icmdp->cmd_un.text.lun, ith.rsvd4, sizeof (ith.rsvd4)); /* release pending queue mutex across the network call */ mutex_exit(&isp->sess_queue_pending.mutex); rval = iscsi_net->sendpdu(icp->conn_socket, (iscsi_hdr_t *)&ith, icmdp->cmd_un.text.buf, ISCSI_CONN_TO_NET_DIGEST(icp)); return (rval); } /* * +--------------------------------------------------------------------+ * | End of protocol send routines | * +--------------------------------------------------------------------+ */ /* * iscsi_handle_r2t - Create a R2T and put it into the pending queue. * * Since the rx thread can hold the pending mutex, the tx thread or wd * thread may not have chance to check the commands in the pending queue. * So if the previous R2T is still there, we will release the related * mutex and wait for its completion in case of deadlock. */ static void iscsi_handle_r2t(iscsi_conn_t *icp, iscsi_cmd_t *icmdp, uint32_t offset, uint32_t length, uint32_t ttt) { iscsi_sess_t *isp = NULL; iscsi_cmd_t *new_icmdp = NULL; int owned = 0; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (icmdp->cmd_un.scsi.r2t_icmdp != NULL) { /* * Occasionally the tx thread doesn't have a chance to * send commands when we hold the pending mutex. * So we should mark this scsi command with more R2T * and release the mutex. Then wait for completion. */ icmdp->cmd_un.scsi.r2t_more = B_TRUE; mutex_exit(&icmdp->cmd_mutex); owned = mutex_owned(&icp->conn_queue_active.mutex); if (owned != 0) { mutex_exit(&icp->conn_queue_active.mutex); } mutex_exit(&isp->sess_queue_pending.mutex); /* * the transmission from a previous r2t can be * slow to return; the array may have sent * another r2t at this point, so wait until * the first one finishes and signals us. */ mutex_enter(&icmdp->cmd_mutex); while (icmdp->cmd_un.scsi.r2t_icmdp != NULL) { ASSERT(icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED); cv_wait(&icmdp->cmd_completion, &icmdp->cmd_mutex); } mutex_exit(&icmdp->cmd_mutex); mutex_enter(&isp->sess_queue_pending.mutex); if (owned != 0) { mutex_enter(&icp->conn_queue_active.mutex); } mutex_enter(&icmdp->cmd_mutex); } /* * try to create an R2T task to send it later. If we can't, * we're screwed, and the command will eventually time out * and be retried by the SCSI layer. */ new_icmdp = iscsi_cmd_alloc(icp, KM_SLEEP); new_icmdp->cmd_type = ISCSI_CMD_TYPE_R2T; new_icmdp->cmd_un.r2t.icmdp = icmdp; new_icmdp->cmd_un.r2t.offset = offset; new_icmdp->cmd_un.r2t.length = length; new_icmdp->cmd_ttt = ttt; new_icmdp->cmd_itt = icmdp->cmd_itt; new_icmdp->cmd_lun = icmdp->cmd_lun; icmdp->cmd_un.scsi.r2t_icmdp = new_icmdp; icmdp->cmd_un.scsi.r2t_more = B_FALSE; /* * pending queue mutex is already held by the * tx_thread or rtt_rsp function. */ iscsi_cmd_state_machine(new_icmdp, ISCSI_CMD_EVENT_E1, isp); } /* * iscsi_handle_abort - * */ void iscsi_handle_abort(void *arg) { iscsi_sess_t *isp = NULL; iscsi_cmd_t *icmdp = (iscsi_cmd_t *)arg; iscsi_cmd_t *new_icmdp; iscsi_conn_t *icp; ASSERT(icmdp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); /* there should only be one abort */ ASSERT(icmdp->cmd_un.scsi.abort_icmdp == NULL); new_icmdp = iscsi_cmd_alloc(icp, KM_SLEEP); new_icmdp->cmd_type = ISCSI_CMD_TYPE_ABORT; new_icmdp->cmd_lun = icmdp->cmd_lun; new_icmdp->cmd_un.abort.icmdp = icmdp; new_icmdp->cmd_conn = icmdp->cmd_conn; icmdp->cmd_un.scsi.abort_icmdp = new_icmdp; /* pending queue mutex is already held by timeout_checks */ iscsi_cmd_state_machine(new_icmdp, ISCSI_CMD_EVENT_E1, isp); } /* * iscsi_handle_nop - * */ static void iscsi_handle_nop(iscsi_conn_t *icp, uint32_t itt, uint32_t ttt) { iscsi_sess_t *isp = NULL; iscsi_cmd_t *icmdp = NULL; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); icmdp = iscsi_cmd_alloc(icp, KM_NOSLEEP); if (icmdp == NULL) { return; } icmdp->cmd_type = ISCSI_CMD_TYPE_NOP; icmdp->cmd_itt = itt; icmdp->cmd_ttt = ttt; icmdp->cmd_lun = NULL; icp->conn_nop_lbolt = ddi_get_lbolt(); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E1, isp); } /* * iscsi_handle_reset - * */ iscsi_status_t iscsi_handle_reset(iscsi_sess_t *isp, int level, iscsi_lun_t *ilp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_conn_t *icp; iscsi_cmd_t icmd; ASSERT(isp != NULL); bzero(&icmd, sizeof (iscsi_cmd_t)); icmd.cmd_sig = ISCSI_SIG_CMD; icmd.cmd_state = ISCSI_CMD_STATE_FREE; icmd.cmd_type = ISCSI_CMD_TYPE_RESET; icmd.cmd_lun = ilp; icmd.cmd_un.reset.level = level; icmd.cmd_result = ISCSI_STATUS_SUCCESS; icmd.cmd_completed = B_FALSE; mutex_init(&icmd.cmd_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&icmd.cmd_completion, NULL, CV_DRIVER, NULL); /* * If we received an IO and we are not in the * LOGGED_IN state we are in the process of * failing. Just respond that we are BUSY. */ mutex_enter(&isp->sess_state_mutex); if (!ISCSI_SESS_STATE_FULL_FEATURE(isp->sess_state)) { /* We aren't connected to the target fake success */ mutex_exit(&isp->sess_state_mutex); return (ISCSI_STATUS_SUCCESS); } mutex_enter(&isp->sess_queue_pending.mutex); iscsi_cmd_state_machine(&icmd, ISCSI_CMD_EVENT_E1, isp); mutex_exit(&isp->sess_queue_pending.mutex); mutex_exit(&isp->sess_state_mutex); /* stall until completed */ mutex_enter(&icmd.cmd_mutex); while (icmd.cmd_completed == B_FALSE) { cv_wait(&icmd.cmd_completion, &icmd.cmd_mutex); } mutex_exit(&icmd.cmd_mutex); /* copy rval */ rval = icmd.cmd_result; if (rval == ISCSI_STATUS_SUCCESS) { /* * Reset was successful. We need to flush * all active IOs. */ rw_enter(&isp->sess_conn_list_rwlock, RW_READER); icp = isp->sess_conn_list; while (icp != NULL) { iscsi_cmd_t *t_icmdp = NULL; mutex_enter(&icp->conn_queue_active.mutex); t_icmdp = icp->conn_queue_active.head; while (t_icmdp != NULL) { iscsi_cmd_state_machine(t_icmdp, ISCSI_CMD_EVENT_E7, isp); t_icmdp = icp->conn_queue_active.head; } mutex_exit(&icp->conn_queue_active.mutex); icp = icp->conn_next; } rw_exit(&isp->sess_conn_list_rwlock); } /* clean up */ cv_destroy(&icmd.cmd_completion); mutex_destroy(&icmd.cmd_mutex); return (rval); } /* * iscsi_handle_logout - This function will issue a logout for * the session from a specific connection. */ iscsi_status_t iscsi_handle_logout(iscsi_conn_t *icp) { iscsi_sess_t *isp; iscsi_cmd_t *icmdp; int rval; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); ASSERT(isp->sess_hba != NULL); icmdp = iscsi_cmd_alloc(icp, KM_SLEEP); ASSERT(icmdp != NULL); icmdp->cmd_type = ISCSI_CMD_TYPE_LOGOUT; icmdp->cmd_result = ISCSI_STATUS_SUCCESS; icmdp->cmd_completed = B_FALSE; mutex_enter(&isp->sess_queue_pending.mutex); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E1, isp); mutex_exit(&isp->sess_queue_pending.mutex); /* * release connection state mutex to avoid a deadlock. This * function is called from within the connection state * machine with the lock held. When the logout response is * received another call to the connection state machine * occurs which causes the deadlock */ mutex_exit(&icp->conn_state_mutex); /* stall until completed */ mutex_enter(&icmdp->cmd_mutex); while (icmdp->cmd_completed == B_FALSE) { cv_wait(&icmdp->cmd_completion, &icmdp->cmd_mutex); } mutex_exit(&icmdp->cmd_mutex); mutex_enter(&icp->conn_state_mutex); /* copy rval */ rval = icmdp->cmd_result; /* * another way to do this would be to send t17 unconditionally, * but then the _rx_ thread would get bumped out with a receive * error, and send another t17. */ if (rval != ISCSI_STATUS_SUCCESS) { (void) iscsi_conn_state_machine(icp, ISCSI_CONN_EVENT_T17); } /* clean up */ iscsi_cmd_free(icmdp); return (rval); } /* * iscsi_handle_text - main control function for iSCSI text requests. This * function handles allocating the command, sending initial text request, and * handling long response sequence. * If a data overflow condition occurs, iscsi_handle_text continues to * receive responses until the all data has been recieved. This allows * the full data length to be returned to the caller. */ iscsi_status_t iscsi_handle_text(iscsi_conn_t *icp, char *buf, uint32_t buf_len, uint32_t data_len, uint32_t *rx_data_len) { iscsi_sess_t *isp; iscsi_cmd_t *icmdp; iscsi_status_t rval = ISCSI_STATUS_SUCCESS; ASSERT(icp != NULL); ASSERT(buf != NULL); ASSERT(rx_data_len != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); /* * Ensure data for text request command is not greater * than the negotiated maximum receive data seqment length. * * Although iSCSI allows for long text requests (multiple * pdus), this function places a restriction on text * requests to ensure it is handled by a single PDU. */ if (data_len > icp->conn_params.max_xmit_data_seg_len) { return (ISCSI_STATUS_CMD_FAILED); } icmdp = iscsi_cmd_alloc(icp, KM_SLEEP); ASSERT(icmdp != NULL); icmdp->cmd_type = ISCSI_CMD_TYPE_TEXT; icmdp->cmd_result = ISCSI_STATUS_SUCCESS; icmdp->cmd_misc_flags &= ~ISCSI_CMD_MISCFLAG_FREE; icmdp->cmd_completed = B_FALSE; icmdp->cmd_un.text.buf = buf; icmdp->cmd_un.text.buf_len = buf_len; icmdp->cmd_un.text.offset = 0; icmdp->cmd_un.text.data_len = data_len; icmdp->cmd_un.text.total_rx_len = 0; icmdp->cmd_un.text.ttt = ISCSI_RSVD_TASK_TAG; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_INITIAL_REQ; long_text_response: mutex_enter(&isp->sess_state_mutex); if (!ISCSI_SESS_STATE_FULL_FEATURE(isp->sess_state)) { iscsi_cmd_free(icmdp); mutex_exit(&isp->sess_state_mutex); return (ISCSI_STATUS_NO_CONN_LOGGED_IN); } mutex_enter(&isp->sess_queue_pending.mutex); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E1, isp); mutex_exit(&isp->sess_queue_pending.mutex); mutex_exit(&isp->sess_state_mutex); /* stall until completed */ mutex_enter(&icmdp->cmd_mutex); while (icmdp->cmd_completed == B_FALSE) { cv_wait(&icmdp->cmd_completion, &icmdp->cmd_mutex); } mutex_exit(&icmdp->cmd_mutex); /* * check if error occured. If data overflow occured, continue on * to ensure we get all data so that the full data length can be * returned to the user */ if ((icmdp->cmd_result != ISCSI_STATUS_SUCCESS) && (icmdp->cmd_result != ISCSI_STATUS_DATA_OVERFLOW)) { cmn_err(CE_NOTE, "iscsi: SendTarget discovery failed (%d)", icmdp->cmd_result); rval = icmdp->cmd_result; iscsi_cmd_free(icmdp); return (rval); } /* check if this was a partial text PDU */ if (icmdp->cmd_un.text.stage != ISCSI_CMD_TEXT_FINAL_RSP) { /* * If a paritial text rexponse received, send an empty * text request. This follows the behaviour specified * in RFC3720 regarding long text responses. */ icmdp->cmd_misc_flags &= ~ISCSI_CMD_MISCFLAG_FREE; icmdp->cmd_completed = B_FALSE; icmdp->cmd_un.text.data_len = 0; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_CONTINUATION; goto long_text_response; } /* * set total received data length. If data overflow this would be * amount of data that would have been received if buffer large * enough. */ *rx_data_len = icmdp->cmd_un.text.total_rx_len; /* copy rval */ rval = icmdp->cmd_result; /* clean up */ iscsi_cmd_free(icmdp); return (rval); } /* * iscsi_handle_passthru - This function is used to send a uscsi_cmd * to a specific target lun. This routine is used for internal purposes * during enumeration and via the ISCSI_USCSICMD IOCTL. We restrict * the CDBs that can be issued to a target/lun to INQUIRY, REPORT_LUNS, * and READ_CAPACITY for security purposes. * * The logic here is broken into three phases. * 1) Allocate and initialize a pkt/icmdp * 2) Send the pkt/icmdp * 3) cv_wait for completion */ iscsi_status_t iscsi_handle_passthru(iscsi_sess_t *isp, uint16_t lun, struct uscsi_cmd *ucmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_cmd_t *icmdp = NULL; struct scsi_pkt *pkt = NULL; struct buf *bp = NULL; struct scsi_arq_status *arqstat = NULL; int rqlen = SENSE_LENGTH; ASSERT(isp != NULL); ASSERT(ucmdp != NULL); /* * If the caller didn't provide a sense buffer we need * to allocation one to get the scsi status. */ if (ucmdp->uscsi_rqlen > SENSE_LENGTH) { rqlen = ucmdp->uscsi_rqlen; } /* * Step 1. Setup structs - KM_SLEEP will always succeed */ bp = kmem_zalloc(sizeof (struct buf), KM_SLEEP); ASSERT(bp != NULL); pkt = kmem_zalloc(sizeof (struct scsi_pkt), KM_SLEEP); ASSERT(pkt != NULL); icmdp = iscsi_cmd_alloc(NULL, KM_SLEEP); ASSERT(icmdp != NULL); /* setup bp structure */ bp->b_flags = B_READ; bp->b_bcount = ucmdp->uscsi_buflen; bp->b_un.b_addr = ucmdp->uscsi_bufaddr; /* setup scsi_pkt structure */ pkt->pkt_ha_private = icmdp; pkt->pkt_scbp = kmem_zalloc(rqlen, KM_SLEEP); pkt->pkt_cdbp = kmem_zalloc(ucmdp->uscsi_cdblen, KM_SLEEP); /* callback routine for passthru, will wake cv_wait */ pkt->pkt_comp = iscsi_handle_passthru_callback; pkt->pkt_time = ucmdp->uscsi_timeout; /* setup iscsi_cmd structure */ icmdp->cmd_lun = NULL; icmdp->cmd_type = ISCSI_CMD_TYPE_SCSI; icmdp->cmd_un.scsi.lun = lun; icmdp->cmd_un.scsi.pkt = pkt; icmdp->cmd_un.scsi.bp = bp; bcopy(ucmdp->uscsi_cdb, pkt->pkt_cdbp, ucmdp->uscsi_cdblen); icmdp->cmd_un.scsi.cmdlen = ucmdp->uscsi_cdblen; icmdp->cmd_un.scsi.statuslen = rqlen; icmdp->cmd_crc_error_seen = B_FALSE; icmdp->cmd_completed = B_FALSE; icmdp->cmd_result = ISCSI_STATUS_SUCCESS; /* * Step 2. Push IO onto pending queue. If we aren't in * FULL_FEATURE we need to fail the IO. */ mutex_enter(&isp->sess_state_mutex); if (!ISCSI_SESS_STATE_FULL_FEATURE(isp->sess_state)) { mutex_exit(&isp->sess_state_mutex); iscsi_cmd_free(icmdp); kmem_free(pkt->pkt_cdbp, ucmdp->uscsi_cdblen); kmem_free(pkt->pkt_scbp, rqlen); kmem_free(pkt, sizeof (struct scsi_pkt)); kmem_free(bp, sizeof (struct buf)); return (ISCSI_STATUS_CMD_FAILED); } mutex_enter(&isp->sess_queue_pending.mutex); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E1, isp); mutex_exit(&isp->sess_queue_pending.mutex); mutex_exit(&isp->sess_state_mutex); /* * Step 3. Wait on cv_wait for completion routine */ mutex_enter(&icmdp->cmd_mutex); while (icmdp->cmd_completed == B_FALSE) { cv_wait(&icmdp->cmd_completion, &icmdp->cmd_mutex); } mutex_exit(&icmdp->cmd_mutex); /* copy rval */ rval = icmdp->cmd_result; ucmdp->uscsi_resid = pkt->pkt_resid; /* update scsi status */ arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; ucmdp->uscsi_status = ((char *)&arqstat->sts_status)[0]; /* copy request sense buffers if caller gave space */ if ((ucmdp->uscsi_rqlen > 0) && (ucmdp->uscsi_rqbuf != NULL)) { bcopy(arqstat, ucmdp->uscsi_rqbuf, MIN(sizeof (struct scsi_arq_status), rqlen)); } /* clean up */ iscsi_cmd_free(icmdp); kmem_free(pkt->pkt_cdbp, ucmdp->uscsi_cdblen); kmem_free(pkt->pkt_scbp, rqlen); kmem_free(pkt, sizeof (struct scsi_pkt)); kmem_free(bp, sizeof (struct buf)); return (rval); } /* * iscsi_handle_passthru_callback - * */ static void iscsi_handle_passthru_callback(struct scsi_pkt *pkt) { iscsi_cmd_t *icmdp = NULL; ASSERT(pkt != NULL); icmdp = (iscsi_cmd_t *)pkt->pkt_ha_private; ASSERT(icmdp != NULL); mutex_enter(&icmdp->cmd_mutex); icmdp->cmd_completed = B_TRUE; icmdp->cmd_result = ISCSI_STATUS_SUCCESS; cv_broadcast(&icmdp->cmd_completion); mutex_exit(&icmdp->cmd_mutex); } /* * +--------------------------------------------------------------------+ * | Beginning of completion routines | * +--------------------------------------------------------------------+ */ /* * iscsi_ic_thread - */ void iscsi_ic_thread(iscsi_thread_t *thread, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; int ret; iscsi_queue_t q; iscsi_cmd_t *icmdp; iscsi_cmd_t *next_icmdp; ASSERT(isp != NULL); ASSERT(thread != NULL); ASSERT(thread->signature == SIG_ISCSI_THREAD); for (;;) { /* * We wait till iodone or somebody else wakes us up. */ ret = iscsi_thread_wait(thread, -1); /* * The value should never be negative since we never timeout. */ ASSERT(ret >= 0); q.count = 0; q.head = NULL; q.tail = NULL; mutex_enter(&isp->sess_queue_completion.mutex); icmdp = isp->sess_queue_completion.head; while (icmdp != NULL) { next_icmdp = icmdp->cmd_next; mutex_enter(&icmdp->cmd_mutex); /* * check if the associated r2t/abort has finished * yet, and make sure this command has no R2T * to handle. If not, don't complete this command. */ if ((icmdp->cmd_un.scsi.r2t_icmdp == NULL) && (icmdp->cmd_un.scsi.abort_icmdp == NULL) && (icmdp->cmd_un.scsi.r2t_more == B_FALSE)) { mutex_exit(&icmdp->cmd_mutex); (void) iscsi_dequeue_cmd(&isp-> sess_queue_completion.head, &isp->sess_queue_completion.tail, icmdp); --isp->sess_queue_completion.count; iscsi_enqueue_cmd_head(&q.head, &q.tail, icmdp); } else mutex_exit(&icmdp->cmd_mutex); icmdp = next_icmdp; } mutex_exit(&isp->sess_queue_completion.mutex); icmdp = q.head; while (icmdp != NULL) { next_icmdp = icmdp->cmd_next; iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E8, isp); icmdp = next_icmdp; } if (ret > 0) /* Somebody woke us up to work */ continue; else /* * Somebody woke us up to kill ourselves. We will * make sure, however that the completion queue is * empty before leaving. After we've done that it * is the originator of the signal that has to make * sure no other SCSI command is posted. */ break; } } /* * iscsi_iodone - * */ void iscsi_iodone(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { struct scsi_pkt *pkt = NULL; struct buf *bp = icmdp->cmd_un.scsi.bp; ASSERT(isp != NULL); ASSERT(icmdp != NULL); pkt = icmdp->cmd_un.scsi.pkt; ASSERT(pkt != NULL); ASSERT(icmdp->cmd_un.scsi.abort_icmdp == NULL); ASSERT(icmdp->cmd_un.scsi.r2t_icmdp == NULL); if (pkt->pkt_reason == CMD_CMPLT) { if (bp) { if (bp->b_flags & B_READ) { KSTAT_SESS_RX_IO_DONE(isp, bp->b_bcount); } else { KSTAT_SESS_TX_IO_DONE(isp, bp->b_bcount); } } } if (pkt->pkt_flags & FLAG_NOINTR) { cv_broadcast(&icmdp->cmd_completion); mutex_exit(&icmdp->cmd_mutex); } else { /* * Release mutex. As soon as callback is * issued the caller may destroy the command. */ mutex_exit(&icmdp->cmd_mutex); /* * We can't just directly call the pk_comp routine. In * many error cases the target driver will use the calling * thread to re-drive error handling (reset, retries...) * back into the hba driver (iscsi). If the target redrives * a reset back into the iscsi driver off this thead we have * a chance of deadlocking. So instead use the io completion * thread. */ (*icmdp->cmd_un.scsi.pkt->pkt_comp)(icmdp->cmd_un.scsi.pkt); } } /* * +--------------------------------------------------------------------+ * | End of completion routines | * +--------------------------------------------------------------------+ */ /* * +--------------------------------------------------------------------+ * | Beginning of watchdog routines | * +--------------------------------------------------------------------+ */ /* * iscsi_watchdog_thread - * */ void iscsi_wd_thread(iscsi_thread_t *thread, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; int rc = 1; ASSERT(isp != NULL); while (rc != NULL) { iscsi_timeout_checks(isp); iscsi_nop_checks(isp); rc = iscsi_thread_wait(thread, SEC_TO_TICK(1)); } } /* * iscsi_timeout_checks - * */ static void iscsi_timeout_checks(iscsi_sess_t *isp) { clock_t now = ddi_get_lbolt(); iscsi_cmd_t *icmdp, *nicmdp; iscsi_conn_t *icp; ASSERT(isp != NULL); /* PENDING */ mutex_enter(&isp->sess_state_mutex); mutex_enter(&isp->sess_queue_pending.mutex); for (icmdp = isp->sess_queue_pending.head; icmdp; icmdp = nicmdp) { nicmdp = icmdp->cmd_next; /* Skip entries with no timeout */ if (icmdp->cmd_lbolt_timeout == 0) continue; /* * Skip pending queue entries for cmd_type values that depend * on having an open cmdsn window for successfull transition * from pending to the active (i.e. ones that depend on * sess_cmdsn .vs. sess_maxcmdsn). For them, the timer starts * when they are successfully moved to the active queue by * iscsi_cmd_state_pending() code. */ /* * If the cmd is stuck, at least give it a chance * to timeout */ if (((icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) || (icmdp->cmd_type == ISCSI_CMD_TYPE_TEXT)) && !(icmdp->cmd_misc_flags & ISCSI_CMD_MISCFLAG_STUCK)) continue; /* Skip if timeout still in the future */ if (now <= icmdp->cmd_lbolt_timeout) continue; /* timeout */ iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E6, isp); } mutex_exit(&isp->sess_queue_pending.mutex); mutex_exit(&isp->sess_state_mutex); rw_enter(&isp->sess_conn_list_rwlock, RW_READER); icp = isp->sess_conn_list; while (icp != NULL) { /* ACTIVE */ mutex_enter(&icp->conn_state_mutex); mutex_enter(&isp->sess_queue_pending.mutex); mutex_enter(&icp->conn_queue_active.mutex); for (icmdp = icp->conn_queue_active.head; icmdp; icmdp = nicmdp) { nicmdp = icmdp->cmd_next; /* Skip entries with no timeout */ if (icmdp->cmd_lbolt_timeout == 0) continue; /* Skip if command is not active */ if (icmdp->cmd_state != ISCSI_CMD_STATE_ACTIVE) continue; /* Skip if timeout still in the future */ if (now <= icmdp->cmd_lbolt_timeout) continue; /* timeout */ iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E6, isp); } mutex_exit(&icp->conn_queue_active.mutex); mutex_exit(&isp->sess_queue_pending.mutex); mutex_exit(&icp->conn_state_mutex); icp = icp->conn_next; } rw_exit(&isp->sess_conn_list_rwlock); } /* * iscsi_nop_checks - sends a NOP on idle connections * * This function walks the connections on a session and * issues NOPs on those connections that are in FULL * FEATURE mode and have not received data for the * time period specified by iscsi_nop_delay (global). */ static void iscsi_nop_checks(iscsi_sess_t *isp) { iscsi_conn_t *icp; ASSERT(isp != NULL); if (isp->sess_type == ISCSI_SESS_TYPE_DISCOVERY) { return; } rw_enter(&isp->sess_conn_list_rwlock, RW_READER); icp = isp->sess_conn_act; if (icp != NULL) { mutex_enter(&icp->conn_state_mutex); if ((ISCSI_CONN_STATE_FULL_FEATURE(icp->conn_state)) && (ddi_get_lbolt() > isp->sess_conn_act->conn_rx_lbolt + SEC_TO_TICK(iscsi_nop_delay)) && (ddi_get_lbolt() > isp->sess_conn_act->conn_nop_lbolt + SEC_TO_TICK(iscsi_nop_delay))) { /* * We haven't received anything from the * target is a defined period of time, * send NOP to see if the target is alive. */ mutex_enter(&isp->sess_queue_pending.mutex); iscsi_handle_nop(isp->sess_conn_act, 0, ISCSI_RSVD_TASK_TAG); mutex_exit(&isp->sess_queue_pending.mutex); } mutex_exit(&icp->conn_state_mutex); icp = icp->conn_next; } rw_exit(&isp->sess_conn_list_rwlock); } /* * +--------------------------------------------------------------------+ * | End of wd routines | * +--------------------------------------------------------------------+ */