/* * Handle request in the command thread. */ static void ft_exec_req(struct ft_cmd *cmd) { FT_IO_DBG("cmd state %x\n", cmd->state); switch (cmd->state) { case FC_CMD_ST_NEW: ft_send_cmd(cmd); break; default: break; } }
/* * Send a FCP response including SCSI status and optional FCP rsp_code. * status is SAM_STAT_GOOD (zero) iff code is valid. * This is used in error cases, such as allocation failures. */ static void ft_send_resp_status(struct fc_lport *lport, const 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; 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); len = sizeof(*fcp); if (status == SAM_STAT_GOOD) len += sizeof(*info); fp = fc_frame_alloc(lport, len); if (!fp) return; 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; } fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_DD_CMD_STATUS, 0); sp = fr_seq(fp); if (sp) { lport->tt.seq_send(lport, sp, fp); lport->tt.exch_done(sp); } else { lport->tt.frame_send(lport, fp); } }
/* * Handle incoming FCP command. */ static void ft_recv_cmd(struct ft_sess *sess, struct fc_frame *fp) { struct ft_cmd *cmd; struct fc_lport *lport = sess->tport->lport; cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); if (!cmd) goto busy; cmd->sess = sess; cmd->seq = lport->tt.seq_assign(lport, fp); if (!cmd->seq) { kfree(cmd); goto busy; } cmd->req_frame = fp; /* hold frame during cmd */ ft_queue_cmd(sess, cmd); return; busy: FT_IO_DBG("cmd or seq allocation failure - sending BUSY\n"); ft_send_resp_status(lport, fp, SAM_STAT_BUSY, 0); fc_frame_free(fp); ft_sess_put(sess); /* undo get from lookup */ }
/* * Send new command to target. */ static void ft_send_cmd(struct ft_cmd *cmd) { struct fc_frame_header *fh = fc_frame_header_get(cmd->req_frame); struct se_cmd *se_cmd; struct fcp_cmnd *fcp; int data_dir; u32 data_len; int task_attr; int ret; fcp = fc_frame_payload_get(cmd->req_frame, sizeof(*fcp)); if (!fcp) goto err; if (fcp->fc_flags & FCP_CFL_LEN_MASK) goto err; /* not handling longer CDBs yet */ if (fcp->fc_tm_flags) { task_attr = FCP_PTA_SIMPLE; data_dir = DMA_NONE; data_len = 0; } else { switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) { case 0: data_dir = DMA_NONE; break; case FCP_CFL_RDDATA: data_dir = DMA_FROM_DEVICE; break; case FCP_CFL_WRDATA: data_dir = DMA_TO_DEVICE; break; case FCP_CFL_WRDATA | FCP_CFL_RDDATA: goto err; /* TBD not supported by tcm_fc yet */ } /* * Locate the SAM Task Attr from fc_pri_ta */ switch (fcp->fc_pri_ta & FCP_PTA_MASK) { case FCP_PTA_HEADQ: task_attr = MSG_HEAD_TAG; break; case FCP_PTA_ORDERED: task_attr = MSG_ORDERED_TAG; break; case FCP_PTA_ACA: task_attr = MSG_ACA_TAG; break; case FCP_PTA_SIMPLE: /* Fallthrough */ default: task_attr = MSG_SIMPLE_TAG; } task_attr = fcp->fc_pri_ta & FCP_PTA_MASK; data_len = ntohl(fcp->fc_dl); cmd->cdb = fcp->fc_cdb; } se_cmd = &cmd->se_cmd; /* * Initialize struct se_cmd descriptor from target_core_mod * infrastructure */ transport_init_se_cmd(se_cmd, &ft_configfs->tf_ops, cmd->sess->se_sess, data_len, data_dir, task_attr, &cmd->ft_sense_buffer[0]); /* * Check for FCP task management flags */ if (fcp->fc_tm_flags) { ft_send_tm(cmd); return; } fc_seq_exch(cmd->seq)->lp->tt.seq_set_resp(cmd->seq, ft_recv_seq, cmd); cmd->lun = scsilun_to_int((struct scsi_lun *)fcp->fc_lun); ret = transport_get_lun_for_cmd(&cmd->se_cmd, NULL, cmd->lun); if (ret < 0) { ft_dump_cmd(cmd, __func__); transport_send_check_condition_and_sense(&cmd->se_cmd, cmd->se_cmd.scsi_sense_reason, 0); return; } ret = transport_generic_allocate_tasks(se_cmd, cmd->cdb); FT_IO_DBG("r_ctl %x alloc task ret %d\n", fh->fh_r_ctl, ret); ft_dump_cmd(cmd, __func__); if (ret == -1) { transport_send_check_condition_and_sense(se_cmd, TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE, 0); transport_generic_free_cmd(se_cmd, 0, 1, 0); return; } if (ret == -2) { if (se_cmd->se_cmd_flags & SCF_SCSI_RESERVATION_CONFLICT) ft_queue_status(se_cmd); else transport_send_check_condition_and_sense(se_cmd, se_cmd->scsi_sense_reason, 0); transport_generic_free_cmd(se_cmd, 0, 1, 0); return; } transport_generic_handle_cdb(se_cmd); return; err: ft_send_resp_code(cmd, FCP_CMND_FIELDS_INVALID); return; }
/* * Receive write data frame. */ void ft_recv_write_data(struct scst_cmd *cmd, struct fc_frame *fp) { struct ft_cmd *fcmd; struct fc_frame_header *fh; unsigned int bufflen; u32 rel_off; size_t frame_len; size_t mem_len; size_t tlen; void *from; void *to; int dir; u8 *buf; dir = scst_cmd_get_data_direction(cmd); if (dir == SCST_DATA_BIDI) { mem_len = scst_get_out_buf_first(cmd, &buf); bufflen = scst_cmd_get_out_bufflen(cmd); } else { mem_len = scst_get_buf_first(cmd, &buf); bufflen = scst_cmd_get_bufflen(cmd); } to = buf; fcmd = scst_cmd_get_tgt_priv(cmd); fh = fc_frame_header_get(fp); frame_len = fr_len(fp); rel_off = ntohl(fh->fh_parm_offset); FT_IO_DBG("sid %x oxid %x payload_len %zd rel_off %x\n", ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), frame_len - sizeof(*fh), rel_off); if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF)) goto drop; if (frame_len <= sizeof(*fh)) goto drop; frame_len -= sizeof(*fh); from = fc_frame_payload_get(fp, 0); if (rel_off >= bufflen) goto drop; if (frame_len + rel_off > bufflen) frame_len = bufflen - rel_off; while (frame_len) { if (!mem_len) { if (dir == SCST_DATA_BIDI) { scst_put_out_buf(cmd, buf); mem_len = scst_get_out_buf_next(cmd, &buf); } else { scst_put_buf(cmd, buf); mem_len = scst_get_buf_next(cmd, &buf); } to = buf; if (!mem_len) break; } if (rel_off) { if (rel_off >= mem_len) { rel_off -= mem_len; mem_len = 0; continue; } mem_len -= rel_off; to += rel_off; rel_off = 0; } tlen = min(mem_len, frame_len); memcpy(to, from, tlen); from += tlen; frame_len -= tlen; mem_len -= tlen; to += tlen; fcmd->write_data_len += tlen; } if (mem_len) { if (dir == SCST_DATA_BIDI) scst_put_out_buf(cmd, buf); else scst_put_buf(cmd, buf); } if (fcmd->write_data_len == cmd->data_len) scst_rx_data(cmd, SCST_RX_STATUS_SUCCESS, SCST_CONTEXT_THREAD); drop: fc_frame_free(fp); }
/* * Send read data back to initiator. */ int ft_send_read_data(struct scst_cmd *cmd) { struct ft_cmd *fcmd; struct fc_frame *fp = NULL; struct fc_exch *ep; struct fc_lport *lport; size_t remaining; u32 fh_off = 0; u32 frame_off; size_t frame_len = 0; size_t mem_len; u32 mem_off; size_t tlen; struct page *page; int use_sg; int error; void *to = NULL; u8 *from = NULL; int loop_limit = 10000; fcmd = scst_cmd_get_tgt_priv(cmd); ep = fc_seq_exch(fcmd->seq); lport = ep->lp; frame_off = fcmd->read_data_len; tlen = scst_cmd_get_resp_data_len(cmd); FT_IO_DBG("oid %x oxid %x resp_len %zd frame_off %u\n", ep->oid, ep->oxid, tlen, frame_off); if (tlen <= frame_off) return SCST_TGT_RES_SUCCESS; remaining = tlen - frame_off; if (remaining > UINT_MAX) FT_ERR("oid %x oxid %x resp_len %zd frame_off %u\n", ep->oid, ep->oxid, tlen, frame_off); mem_len = scst_get_buf_first(cmd, &from); mem_off = 0; if (!mem_len) { FT_IO_DBG("mem_len 0\n"); return SCST_TGT_RES_SUCCESS; } FT_IO_DBG("sid %x oxid %x mem_len %zd frame_off %u remaining %zd\n", ep->sid, ep->oxid, mem_len, frame_off, remaining); /* * If we've already transferred some of the data, skip through * the buffer over the data already sent and continue with the * same sequence. Otherwise, get a new sequence for the data. */ if (frame_off) { tlen = frame_off; while (mem_len <= tlen) { tlen -= mem_len; scst_put_buf(cmd, from); mem_len = scst_get_buf_next(cmd, &from); if (!mem_len) return SCST_TGT_RES_SUCCESS; } mem_len -= tlen; mem_off = tlen; } else fcmd->seq = lport->tt.seq_start_next(fcmd->seq); /* no scatter/gather in skb for odd word length due to fc_seq_send() */ use_sg = !(remaining % 4) && lport->sg_supp; while (remaining) { if (!loop_limit) { FT_ERR("hit loop limit. remaining %zx mem_len %zx " "frame_len %zx tlen %zx\n", remaining, mem_len, frame_len, tlen); break; } loop_limit--; if (!mem_len) { scst_put_buf(cmd, from); mem_len = scst_get_buf_next(cmd, &from); mem_off = 0; if (!mem_len) { FT_ERR("mem_len 0 from get_buf_next\n"); break; } } if (!frame_len) { frame_len = fcmd->max_lso_payload; frame_len = min(frame_len, remaining); fp = fc_frame_alloc(lport, use_sg ? 0 : frame_len); if (!fp) { FT_IO_DBG("frame_alloc failed. " "use_sg %d frame_len %zd\n", use_sg, frame_len); break; } fr_max_payload(fp) = fcmd->max_payload; to = fc_frame_payload_get(fp, 0); fh_off = frame_off; } tlen = min(mem_len, frame_len); BUG_ON(!tlen); BUG_ON(tlen > remaining); BUG_ON(tlen > mem_len); BUG_ON(tlen > frame_len); if (use_sg) { page = virt_to_page(from + mem_off); get_page(page); tlen = min_t(size_t, tlen, PAGE_SIZE - (mem_off & ~PAGE_MASK)); skb_fill_page_desc(fp_skb(fp), skb_shinfo(fp_skb(fp))->nr_frags, page, offset_in_page(from + mem_off), tlen); fr_len(fp) += tlen; fp_skb(fp)->data_len += tlen; fp_skb(fp)->truesize += PAGE_SIZE << compound_order(page); frame_len -= tlen; if (skb_shinfo(fp_skb(fp))->nr_frags >= FC_FRAME_SG_LEN) frame_len = 0; } else { memcpy(to, from + mem_off, tlen); to += tlen; frame_len -= tlen; } mem_len -= tlen; mem_off += tlen; remaining -= tlen; frame_off += tlen; if (frame_len) continue; fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid, FC_TYPE_FCP, remaining ? (FC_FC_EX_CTX | FC_FC_REL_OFF) : (FC_FC_EX_CTX | FC_FC_REL_OFF | FC_FC_END_SEQ), fh_off); error = lport->tt.seq_send(lport, fcmd->seq, fp); if (error) { WARN_ON(1); /* XXX For now, initiator will retry */ } else fcmd->read_data_len = frame_off; } if (mem_len) scst_put_buf(cmd, from); if (remaining) { FT_IO_DBG("remaining read data %zd\n", remaining); return SCST_TGT_RES_QUEUE_FULL; } return SCST_TGT_RES_SUCCESS; }