/* * Copyright (c) 2010 Cisco Systems, Inc. * * This program is free software; you may redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include "fcst.h" /* * Append string to buffer safely. * Also prepends a space if there's already something the buf. */ static void ft_cmd_flag(char *buf, size_t len, const char *desc) { if (buf[0]) strlcat(buf, " ", len); strlcat(buf, desc, len); } /* * Debug: dump command. */ void ft_cmd_dump(struct scst_cmd *cmd, const char *caller) { static atomic_t serial; struct ft_cmd *fcmd; struct fc_frame_header *fh; char prefix[30]; char buf[150]; if (!(ft_debug_logging & FT_DEBUG_IO)) return; fcmd = scst_cmd_get_tgt_priv(cmd); fh = fc_frame_header_get(fcmd->req_frame); snprintf(prefix, sizeof(prefix), FT_MODULE ": cmd %2x", atomic_inc_return(&serial) & 0xff); pr_info("%s %s oid %x oxid %x resp_len %u\n", prefix, caller, ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), scst_cmd_get_resp_data_len(cmd)); pr_info("%s scst_cmd %p wlen %u rlen %u\n", prefix, cmd, fcmd->write_data_len, fcmd->read_data_len); pr_info("%s exp_dir %x exp_xfer_len %d exp_in_len %d\n", prefix, cmd->expected_data_direction, cmd->expected_transfer_len_full, cmd->expected_out_transfer_len); pr_info("%s dir %x data_len %lld bufflen %d out_bufflen %d\n", prefix, cmd->data_direction, cmd->data_len, cmd->bufflen, cmd->out_bufflen); pr_info("%s sg_cnt reg %d in %d tgt %d tgt_in %d\n", prefix, cmd->sg_cnt, cmd->out_sg_cnt, cmd->tgt_i_sg_cnt, cmd->tgt_out_sg_cnt); buf[0] = '\0'; if (cmd->sent_for_exec) ft_cmd_flag(buf, sizeof(buf), "sent"); if (cmd->completed) ft_cmd_flag(buf, sizeof(buf), "comp"); if (cmd->ua_ignore) ft_cmd_flag(buf, sizeof(buf), "ua_ign"); if (cmd->atomic) ft_cmd_flag(buf, sizeof(buf), "atom"); if (cmd->double_ua_possible) ft_cmd_flag(buf, sizeof(buf), "dbl_ua_poss"); if (cmd->is_send_status) ft_cmd_flag(buf, sizeof(buf), "send_stat"); if (cmd->retry) ft_cmd_flag(buf, sizeof(buf), "retry"); if (cmd->internal) ft_cmd_flag(buf, sizeof(buf), "internal"); if (cmd->unblock_dev) ft_cmd_flag(buf, sizeof(buf), "unblock_dev"); if (cmd->cmd_hw_pending) ft_cmd_flag(buf, sizeof(buf), "hw_pend"); if (cmd->tgt_need_alloc_data_buf) ft_cmd_flag(buf, sizeof(buf), "tgt_need_alloc"); if (cmd->tgt_i_data_buf_alloced) ft_cmd_flag(buf, sizeof(buf), "tgt_i_alloced"); if (cmd->dh_data_buf_alloced) ft_cmd_flag(buf, sizeof(buf), "dh_alloced"); if (cmd->expected_values_set) ft_cmd_flag(buf, sizeof(buf), "exp_val"); if (cmd->sg_buff_modified) ft_cmd_flag(buf, sizeof(buf), "sg_buf_mod"); if (cmd->preprocessing_only) ft_cmd_flag(buf, sizeof(buf), "pre_only"); if (cmd->sn_set) ft_cmd_flag(buf, sizeof(buf), "sn_set"); if (cmd->hq_cmd_inced) ft_cmd_flag(buf, sizeof(buf), "hq_cmd_inc"); if (cmd->set_sn_on_restart_cmd) ft_cmd_flag(buf, sizeof(buf), "set_sn_on_restart"); if (cmd->no_sgv) ft_cmd_flag(buf, sizeof(buf), "no_sgv"); if (cmd->may_need_dma_sync) ft_cmd_flag(buf, sizeof(buf), "dma_sync"); if (cmd->out_of_sn) ft_cmd_flag(buf, sizeof(buf), "oo_sn"); if (cmd->inc_expected_sn_on_done) ft_cmd_flag(buf, sizeof(buf), "inc_sn_exp"); if (cmd->done) ft_cmd_flag(buf, sizeof(buf), "done"); if (cmd->finished) ft_cmd_flag(buf, sizeof(buf), "fin"); pr_info("%s flags %s\n", prefix, buf); pr_info("%s lun %lld sn %d tag %lld cmd_flags %lx\n", prefix, cmd->lun, cmd->sn, cmd->tag, cmd->cmd_flags); pr_info("%s tgt_sn %d op_flags %x op %s\n", prefix, cmd->tgt_sn, cmd->op_flags, cmd->op_name); pr_info("%s status %x msg_status %x " "host_status %x driver_status %x\n", prefix, cmd->status, cmd->msg_status, cmd->host_status, cmd->driver_status); pr_info("%s cdb_len %d\n", prefix, cmd->cdb_len); snprintf(buf, sizeof(buf), "%s cdb ", prefix); print_hex_dump(KERN_INFO, buf, DUMP_PREFIX_NONE, 16, 4, cmd->cdb, SCST_MAX_CDB_SIZE, 0); } /* * Debug: dump mgmt command. */ static void ft_cmd_tm_dump(struct scst_mgmt_cmd *mcmd, const char *caller) { struct ft_cmd *fcmd; struct fc_frame_header *fh; char prefix[30]; char buf[150]; if (!(ft_debug_logging & FT_DEBUG_IO)) return; fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd); fh = fc_frame_header_get(fcmd->req_frame); snprintf(prefix, sizeof(prefix), FT_MODULE ": mcmd"); pr_info("%s %s oid %x oxid %x lun %lld\n", prefix, caller, ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), (unsigned long long)mcmd->lun); pr_info("%s state %d fn %d fin_wait %d done_wait %d comp %d\n", prefix, mcmd->state, mcmd->fn, mcmd->cmd_finish_wait_count, mcmd->cmd_done_wait_count, mcmd->completed_cmd_count); buf[0] = '\0'; if (mcmd->needs_unblocking) ft_cmd_flag(buf, sizeof(buf), "needs_unblock"); if (mcmd->lun_set) ft_cmd_flag(buf, sizeof(buf), "lun_set"); if (mcmd->cmd_sn_set) ft_cmd_flag(buf, sizeof(buf), "cmd_sn_set"); pr_info("%s flags %s\n", prefix, buf); if (mcmd->cmd_to_abort) ft_cmd_dump(mcmd->cmd_to_abort, caller); } /** * ft_set_cmd_state() - set the state of a command */ static enum ft_cmd_state ft_set_cmd_state(struct ft_cmd *fcmd, enum ft_cmd_state new) { enum ft_cmd_state previous; spin_lock(&fcmd->lock); previous = fcmd->state; if (previous != FT_STATE_DONE) fcmd->state = new; spin_unlock(&fcmd->lock); return previous; } /** * ft_test_and_set_cmd_state() - test and set the state of a command * * Returns true if and only if the previous command state was equal to 'old'. */ bool ft_test_and_set_cmd_state(struct ft_cmd *fcmd, enum ft_cmd_state old, enum ft_cmd_state new) { enum ft_cmd_state previous; WARN_ON(old == FT_STATE_DONE); WARN_ON(new == FT_STATE_NEW); spin_lock(&fcmd->lock); previous = fcmd->state; if (previous == old) fcmd->state = new; spin_unlock(&fcmd->lock); return previous == old; } static void ft_abort_cmd(struct scst_cmd *cmd) { struct ft_cmd *fcmd = scst_cmd_get_tgt_priv(cmd); struct fc_seq *sp = fcmd->seq; struct fc_exch *ep = fc_seq_exch(sp); pr_err("%s: cmd %p ox_id %#x rx_id %#x state %d\n", __func__, cmd, ep->oxid, ep->rxid, fcmd->state); spin_lock(&fcmd->lock); switch (fcmd->state) { case FT_STATE_NEW: case FT_STATE_DATA_IN: case FT_STATE_MGMT: /* * Do nothing - defer abort processing until * srpt_xmit_response() is invoked. */ break; case FT_STATE_NEED_DATA: /* SCST_DATA_WRITE */ fcmd->state = FT_STATE_DATA_IN; scst_rx_data(cmd, SCST_RX_STATUS_ERROR_FATAL, SCST_CONTEXT_THREAD); break; case FT_STATE_CMD_RSP_SENT: /* * ft_send_response() is either in progress or has finished. * Wait until the SCST core has invoked ft_cmd_done(). */ break; case FT_STATE_MGMT_RSP_SENT: default: pr_info("Unexpected command state %d\n", fcmd->state); __WARN(); fcmd->state = FT_STATE_DONE; break; } spin_unlock(&fcmd->lock); } /* * Free command and associated frame. */ static void ft_cmd_done(struct ft_cmd *fcmd) { struct fc_frame *fp = fcmd->req_frame; struct fc_seq *sp = fcmd->seq; #ifndef NEW_LIBFC_API struct fc_lport *lport = fr_dev(fp); #endif if (sp) #ifdef NEW_LIBFC_API fc_exch_done(sp); #else lport->tt.exch_done(sp); #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) if (fr_seq(fp)) #ifdef NEW_LIBFC_API fc_seq_release(fr_seq(fp)); #else lport->tt.seq_release(fr_seq(fp)); #endif #endif fc_frame_free(fp); kfree(fcmd); } /* * Free command - callback from SCST. */ void ft_cmd_free(struct scst_cmd *cmd) { struct ft_cmd *fcmd = scst_cmd_get_tgt_priv(cmd); ft_cmd_done(fcmd); } /* * Send response. */ int ft_send_response(struct scst_cmd *cmd) { struct ft_cmd *fcmd; struct fc_frame *fp; struct fcp_resp_with_ext *fcp; struct fc_lport *lport; struct fc_exch *ep; unsigned int slen; size_t len; enum ft_cmd_state prev_state; int resid = 0; int bi_resid = 0; int error; int dir; u32 status; ft_cmd_dump(cmd, __func__); fcmd = scst_cmd_get_tgt_priv(cmd); ep = fc_seq_exch(fcmd->seq); lport = ep->lp; WARN_ON(fcmd->state != FT_STATE_NEW && fcmd->state != FT_STATE_DATA_IN); prev_state = ft_set_cmd_state(fcmd, FT_STATE_CMD_RSP_SENT); if (scst_cmd_aborted_on_xmit(cmd)) { FT_IO_DBG("cmd aborted did %x oxid %x\n", ep->did, ep->oxid); ft_abort_cmd(cmd); goto done; } if (!scst_cmd_get_is_send_status(cmd)) { FT_IO_DBG("send status not set. feature not implemented\n"); error = SCST_TGT_RES_FATAL_ERROR; goto err; } status = scst_cmd_get_status(cmd); dir = scst_cmd_get_data_direction(cmd); slen = scst_cmd_get_sense_buffer_len(cmd); len = sizeof(*fcp) + slen; /* * Send read data and set underflow/overflow residual count. * For bi-directional comands, the bi_resid is for the read direction. */ if (dir & SCST_DATA_WRITE) resid = (signed int)scst_cmd_get_bufflen(cmd) - fcmd->write_data_len; if (dir & SCST_DATA_READ) { error = ft_send_read_data(cmd); if (error) { FT_ERR("ft_send_read_data returned %d\n", error); goto err; } if (dir == SCST_DATA_BIDI) { bi_resid = (signed int)scst_cmd_get_out_bufflen(cmd) - scst_cmd_get_resp_data_len(cmd); if (bi_resid) len += sizeof(__be32); } else resid = (signed int)scst_cmd_get_bufflen(cmd) - scst_cmd_get_resp_data_len(cmd); } fp = fc_frame_alloc(lport, len); if (!fp) { error = SCST_TGT_RES_QUEUE_FULL; goto err; } fcp = fc_frame_payload_get(fp, len); memset(fcp, 0, sizeof(*fcp)); fcp->resp.fr_status = status; if (slen) { fcp->resp.fr_flags |= FCP_SNS_LEN_VAL; fcp->ext.fr_sns_len = htonl(slen); memcpy(fcp + 1, scst_cmd_get_sense_buffer(cmd), slen); } if (bi_resid) { if (bi_resid < 0) { fcp->resp.fr_flags |= FCP_BIDI_READ_OVER; bi_resid = -bi_resid; } else fcp->resp.fr_flags |= FCP_BIDI_READ_UNDER; *(__be32 *)((u8 *)(fcp + 1) + slen) = htonl(bi_resid); } if (resid) { if (resid < 0) { resid = -resid; fcp->resp.fr_flags |= FCP_RESID_OVER; } else fcp->resp.fr_flags |= FCP_RESID_UNDER; fcp->ext.fr_resid = htonl(resid); } FT_IO_DBG("response did %x oxid %x\n", ep->did, ep->oxid); /* * Send response. */ #ifdef NEW_LIBFC_API fcmd->seq = fc_seq_start_next(fcmd->seq); #else fcmd->seq = lport->tt.seq_start_next(fcmd->seq); #endif fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP, FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0); #ifdef NEW_LIBFC_API error = FCST_INJ_SEND_ERR(fc_seq_send(lport, fcmd->seq, fp)); #else error = FCST_INJ_SEND_ERR(lport->tt.seq_send(lport, fcmd->seq, fp)); #endif if (error < 0) { pr_err("Sending response for exchange with OX_ID %#x and RX_ID" " %#x failed: %d\n", ep->oxid, ep->rxid, error); error = error == -ENOMEM ? SCST_TGT_RES_QUEUE_FULL : SCST_TGT_RES_FATAL_ERROR; goto err; } done: scst_tgt_cmd_done(cmd, SCST_CONTEXT_SAME); return SCST_TGT_RES_SUCCESS; err: ft_set_cmd_state(fcmd, prev_state); WARN_ONCE(error != SCST_TGT_RES_QUEUE_FULL && error != SCST_TGT_RES_FATAL_ERROR, "%s: invalid error code %d\n", __func__, error); return error; } /* * FC sequence response handler for follow-on sequences (data) and aborts. */ static void ft_recv_seq(struct fc_seq *sp, struct fc_frame *fp, void *arg) { struct scst_cmd *cmd = arg; struct fc_frame_header *fh; /* * If an error is being reported, it must be FC_EX_CLOSED. * Timeouts don't occur on incoming requests, and there are * currently no other errors. * The PRLO handler will be also called by libfc to delete * the session and all pending commands, so we ignore this response. */ if (IS_ERR(fp)) { pr_err("exchange error %ld - aborting cmd %p / tag %lld\n", -PTR_ERR(fp), cmd, cmd->tag); ft_abort_cmd(cmd); return; } fh = fc_frame_header_get(fp); switch (fh->fh_r_ctl) { case FC_RCTL_DD_SOL_DATA: /* write data */ ft_recv_write_data(cmd, fp); break; case FC_RCTL_DD_UNSOL_CTL: /* command */ case FC_RCTL_DD_SOL_CTL: /* transfer ready */ case FC_RCTL_DD_DATA_DESC: /* transfer ready */ default: pr_info("%s: unhandled frame r_ctl %x\n", __func__, fh->fh_r_ctl); fc_frame_free(fp); break; } } /* * Command timeout. * SCST calls this when the command has taken too long in the device handler. */ void ft_cmd_timeout(struct scst_cmd *cmd) { FT_IO_DBG("%p: timeout\n", cmd); ft_abort_cmd(cmd); } /* * Send TX_RDY (transfer ready). */ int ft_send_xfer_rdy(struct scst_cmd *cmd) { struct ft_cmd *fcmd; struct fc_frame *fp; struct fcp_txrdy *txrdy; struct fc_lport *lport; struct fc_exch *ep; int error; fcmd = scst_cmd_get_tgt_priv(cmd); ep = fc_seq_exch(fcmd->seq); lport = ep->lp; fp = fc_frame_alloc(lport, sizeof(*txrdy)); if (!fp) return SCST_TGT_RES_QUEUE_FULL; WARN_ON(!ft_test_and_set_cmd_state(fcmd, FT_STATE_NEW, FT_STATE_NEED_DATA)); txrdy = fc_frame_payload_get(fp, sizeof(*txrdy)); memset(txrdy, 0, sizeof(*txrdy)); txrdy->ft_data_ro = 0; txrdy->ft_burst_len = htonl(scst_cmd_get_bufflen(cmd)); #ifdef NEW_LIBFC_API fcmd->seq = fc_seq_start_next(fcmd->seq); #else fcmd->seq = lport->tt.seq_start_next(fcmd->seq); #endif fc_fill_fc_hdr(fp, FC_RCTL_DD_DATA_DESC, ep->did, ep->sid, FC_TYPE_FCP, FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); #ifdef NEW_LIBFC_API error = FCST_INJ_SEND_ERR(fc_seq_send(lport, fcmd->seq, fp)); #else error = FCST_INJ_SEND_ERR(lport->tt.seq_send(lport, fcmd->seq, fp)); #endif switch (error) { case 0: return SCST_TGT_RES_SUCCESS; case -ENOMEM: ft_set_cmd_state(fcmd, FT_STATE_NEW); return SCST_TGT_RES_QUEUE_FULL; default: ft_set_cmd_state(fcmd, FT_STATE_NEW); return SCST_TGT_RES_FATAL_ERROR; } } /* * Send a FCP response including SCSI status and optional FCP rsp_code. * status is SAM_STAT_GOOD (zero) if code is valid. * This is used in error cases, such as allocation failures. */ static void ft_send_resp_status(struct fc_frame *rx_fp, u32 status, enum fcp_resp_rsp_codes code) { struct fc_frame *fp; struct fc_seq *sp; const struct fc_frame_header *fh; size_t len; struct fcp_resp_with_ext *fcp; struct fcp_resp_rsp_info *info; struct fc_lport *lport; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) struct fc_exch *ep; #endif fh = fc_frame_header_get(rx_fp); FT_IO_DBG("FCP error response: did %x oxid %x status %x code %x\n", ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), status, code); lport = fr_dev(rx_fp); len = sizeof(*fcp); if (status == SAM_STAT_GOOD) len += sizeof(*info); fp = fc_frame_alloc(lport, len); if (!fp) #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) goto out; #else return; #endif fcp = fc_frame_payload_get(fp, len); memset(fcp, 0, len); fcp->resp.fr_status = status; if (status == SAM_STAT_GOOD) { fcp->ext.fr_rsp_len = htonl(sizeof(*info)); fcp->resp.fr_flags |= FCP_RSP_LEN_VAL; info = (struct fcp_resp_rsp_info *)(fcp + 1); info->rsp_code = code; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) sp = fr_seq(rx_fp); sp = lport->tt.seq_start_next(sp); ep = fc_seq_exch(sp); fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP, FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0); lport->tt.seq_send(lport, sp, fp); out: ; #else fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_DD_CMD_STATUS, 0); sp = fr_seq(fp); if (sp) #ifdef NEW_LIBFC_API fc_seq_send(lport, sp, fp); #else lport->tt.seq_send(lport, sp, fp); #endif else lport->tt.frame_send(lport, fp); #endif } /* * Send error or task management response. * Always frees the cmd and associated state. */ static void ft_send_resp_code(struct ft_cmd *fcmd, enum fcp_resp_rsp_codes code) { ft_send_resp_status(fcmd->req_frame, SAM_STAT_GOOD, code); ft_cmd_done(fcmd); } void ft_cmd_tm_done(struct scst_mgmt_cmd *mcmd) { struct ft_cmd *fcmd; enum fcp_resp_rsp_codes code; ft_cmd_tm_dump(mcmd, __func__); fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd); if (!fcmd) return; ft_set_cmd_state(fcmd, FT_STATE_MGMT_RSP_SENT); switch (scst_mgmt_cmd_get_status(mcmd)) { case SCST_MGMT_STATUS_SUCCESS: code = FCP_TMF_CMPL; break; case SCST_MGMT_STATUS_REJECTED: code = FCP_TMF_REJECTED; break; case SCST_MGMT_STATUS_LUN_NOT_EXIST: code = FCP_TMF_INVALID_LUN; break; case SCST_MGMT_STATUS_TASK_NOT_EXIST: case SCST_MGMT_STATUS_FN_NOT_SUPPORTED: case SCST_MGMT_STATUS_FAILED: default: code = FCP_TMF_FAILED; break; } FT_IO_DBG("tm cmd done fn %d code %d\n", mcmd->fn, code); ft_send_resp_code(fcmd, code); } /* * Handle an incoming FCP task management command frame. * Note that this may be called directly from the softirq context. */ static void ft_recv_tm(struct scst_session *scst_sess, struct ft_cmd *fcmd, struct fcp_cmnd *fcp) { struct scst_rx_mgmt_params params; int ret; ft_set_cmd_state(fcmd, FT_STATE_MGMT); scst_rx_mgmt_params_init(¶ms); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) || \ defined(CONFIG_SUSE_KERNEL) && \ LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 101) params.lun = fcp->fc_lun.scsi_lun; #else params.lun = fcp->fc_lun; #endif params.lun_len = sizeof(fcp->fc_lun); params.lun_set = 1; params.atomic = SCST_ATOMIC; params.tgt_priv = fcmd; switch (fcp->fc_tm_flags) { case FCP_TMF_LUN_RESET: params.fn = SCST_LUN_RESET; break; case FCP_TMF_TGT_RESET: params.fn = SCST_TARGET_RESET; params.lun_set = 0; break; case FCP_TMF_CLR_TASK_SET: params.fn = SCST_CLEAR_TASK_SET; break; case FCP_TMF_ABT_TASK_SET: params.fn = SCST_ABORT_TASK_SET; break; case FCP_TMF_CLR_ACA: params.fn = SCST_CLEAR_ACA; break; default: /* * FCP4r01 indicates having a combination of * tm_flags set is invalid. */ FT_IO_DBG("invalid FCP tm_flags %x\n", fcp->fc_tm_flags); ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID); return; } FT_IO_DBG("submit tm cmd fn %d\n", params.fn); ret = scst_rx_mgmt_fn(scst_sess, ¶ms); FT_IO_DBG("scst_rx_mgmt_fn ret %d\n", ret); if (ret) ft_send_resp_code(fcmd, FCP_TMF_FAILED); } /* * Handle an incoming FCP command frame. * Note that this may be called directly from the softirq context. */ static void ft_recv_cmd(struct ft_sess *sess, struct fc_frame *fp) { struct fc_seq *sp; struct scst_cmd *cmd; struct ft_cmd *fcmd = NULL; struct fcp_cmnd *fcp; struct fc_lport *lport; int data_dir; u32 data_len; int cdb_len; lport = sess->tport->lport; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) sp = fr_seq(fp); #else #ifdef NEW_LIBFC_API sp = fc_seq_assign(lport, fp); #else sp = lport->tt.seq_assign(lport, fp); #endif if (!sp) goto busy; #endif fcmd = kzalloc(sizeof(*fcmd), GFP_ATOMIC); if (!fcmd) goto busy; fcmd->max_payload = sess->max_payload; fcmd->max_lso_payload = sess->max_lso_payload; fcmd->req_frame = fp; spin_lock_init(&fcmd->lock); fcp = fc_frame_payload_get(fp, sizeof(*fcp)); if (!fcp) goto err; if (fcp->fc_tm_flags) { ft_recv_tm(sess->scst_sess, fcmd, fcp); return; } /* * re-check length including specified CDB length. * data_len is just after the CDB. */ cdb_len = fcp->fc_flags & FCP_CFL_LEN_MASK; fcp = fc_frame_payload_get(fp, sizeof(*fcp) + cdb_len); if (!fcp) goto err; cdb_len += sizeof(fcp->fc_cdb); data_len = ntohl(*(__be32 *)(fcp->fc_cdb + cdb_len)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) || \ defined(CONFIG_SUSE_KERNEL) && \ LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 101) cmd = scst_rx_cmd(sess->scst_sess, fcp->fc_lun.scsi_lun, sizeof(fcp->fc_lun), fcp->fc_cdb, cdb_len, SCST_ATOMIC); #else cmd = scst_rx_cmd(sess->scst_sess, fcp->fc_lun, sizeof(fcp->fc_lun), fcp->fc_cdb, cdb_len, SCST_ATOMIC); #endif if (!cmd) goto busy; fcmd->scst_cmd = cmd; scst_cmd_set_tgt_priv(cmd, fcmd); cmd->state = FT_STATE_NEW; fcmd->seq = sp; #ifdef NEW_LIBFC_API fc_seq_set_resp(sp, ft_recv_seq, cmd); #else lport->tt.seq_set_resp(sp, ft_recv_seq, cmd); #endif switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) { case 0: default: data_dir = SCST_DATA_NONE; break; case FCP_CFL_RDDATA: data_dir = SCST_DATA_READ; break; case FCP_CFL_WRDATA: data_dir = SCST_DATA_WRITE; break; case FCP_CFL_RDDATA | FCP_CFL_WRDATA: data_dir = SCST_DATA_BIDI; break; } scst_cmd_set_expected(cmd, data_dir, data_len); switch (fcp->fc_pri_ta & FCP_PTA_MASK) { case FCP_PTA_SIMPLE: scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_SIMPLE); break; case FCP_PTA_HEADQ: scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE); break; case FCP_PTA_ACA: scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_ACA); break; case FCP_PTA_ORDERED: default: scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_ORDERED); break; } scst_cmd_set_tag(cmd, fc_seq_exch(sp)->rxid); scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD); return; err: ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID); return; busy: FT_IO_DBG("cmd allocation failure - sending BUSY\n"); ft_send_resp_status(fp, SAM_STAT_BUSY, 0); if (fcmd) ft_cmd_done(fcmd); else if (sp) #ifdef NEW_LIBFC_API fc_exch_done(sp); #else lport->tt.exch_done(sp); #endif } /* * Send FCP ELS-4 Reject. */ static void ft_cmd_ls_rjt(struct fc_frame *rx_fp, enum fc_els_rjt_reason reason, enum fc_els_rjt_explan explan) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) struct fc_seq *sp = fr_seq(rx_fp); struct fc_frame *fp; struct fc_els_ls_rjt *rjt; struct fc_lport *lport; struct fc_exch *ep; ep = fc_seq_exch(sp); lport = ep->lp; fp = fc_frame_alloc(lport, sizeof(*rjt)); if (!fp) return; rjt = fc_frame_payload_get(fp, sizeof(*rjt)); memset(rjt, 0, sizeof(*rjt)); rjt->er_cmd = ELS_LS_RJT; rjt->er_reason = reason; rjt->er_explan = explan; sp = lport->tt.seq_start_next(sp); fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid, FC_TYPE_FCP, FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_LAST_SEQ, 0); lport->tt.seq_send(lport, sp, fp); #else struct fc_seq_els_data rjt_data; struct fc_lport *lport; lport = fr_dev(rx_fp); rjt_data.reason = reason; rjt_data.explan = explan; #ifdef NEW_LIBFC_API fc_seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data); #else lport->tt.seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data); #endif #endif } /* * Handle an incoming FCP ELS-4 command frame. * Note that this may be called directly from the softirq context. */ static void ft_recv_els4(struct ft_sess *sess, struct fc_frame *fp) { u8 op = fc_frame_payload_op(fp); switch (op) { case ELS_SRR: /* TBD */ default: FT_IO_DBG("unsupported ELS-4 op %x\n", op); ft_cmd_ls_rjt(fp, ELS_RJT_INVAL, ELS_EXPL_NONE); fc_frame_free(fp); break; } } /* * Handle an incoming FCP frame. * Note that this may be called directly from the softirq context. */ void ft_recv_req(struct ft_sess *sess, struct fc_frame *fp) { struct fc_frame_header *fh = fc_frame_header_get(fp); switch (fh->fh_r_ctl) { case FC_RCTL_DD_UNSOL_CMD: ft_recv_cmd(sess, fp); break; case FC_RCTL_ELS4_REQ: ft_recv_els4(sess, fp); break; default: pr_info("%s: unhandled frame r_ctl %x\n", __func__, fh->fh_r_ctl); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) sess->tport->lport->tt.exch_done(fr_seq(fp)); #endif fc_frame_free(fp); break; } }