| /* |
| * 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 <linux/kernel.h> |
| #include <linux/types.h> |
| #include <scsi/libfc.h> |
| #include <scsi/fc_encode.h> |
| #include "fcst.h" |
| |
| /* |
| * 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; |
| |
| 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; |
| |
| 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); |
| fcmd->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; |
| |
| 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 |
| fr_dev(rx_fp)->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; |
| } |
| } |