/* * Copyright (c) 2010 Cisco Systems, Inc. * * Portions based on drivers/scsi/libfc/fc_fcp.c and subject to the following: * * Copyright (c) 2007 Intel Corporation. All rights reserved. * Copyright (c) 2008 Red Hat, Inc. All rights reserved. * Copyright (c) 2008 Mike Christie * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. */ #include #include #include #include /* * See also upstream commit e31ac898ac29 ("scsi: libfc: Move scsi/fc_encode.h * to libfc"). That commit moved fc_fill_fc_hdr() from into * . */ #if defined(FC_FILL_FC_HDR_IN_SCSI_FC_ENCODE_H) #include #else #include #endif #include "fcst.h" /* * 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 { #ifdef NEW_LIBFC_API fcmd->seq = fc_seq_start_next(fcmd->seq); #else fcmd->seq = lport->tt.seq_start_next(fcmd->seq); #endif } /* no scatter/gather in skb for odd word length due to fc_seq_send() */ use_sg = !(remaining % 4) && lport->sg_supp; /* * Note: since libfc_function_template.seq_send() sends frames * asynchronously and since the SCST data buffer is freed as soon as * scst_tgt_cmd_done() has been invoked, data has to be copied into * the skb instead of only copying a pointer to the data. To do: defer * invocation of scst_tgt_cmd_done() until sending the data frames * finished once the paged fragment destructor or an equivalent is * upstream. */ use_sg = false; 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_off += tlen; mem_len -= tlen; frame_off += tlen; remaining -= 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); #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) { pr_warn("Sending frame with oid %#x oxid %#x resp_len %d failed at frame_off %u / remaining %zu with error code %d - %s", ep->oid, ep->oxid, scst_cmd_get_resp_data_len(cmd), frame_off, remaining, error, error == -ENOMEM ? "retrying" : "giving up"); return error == -ENOMEM ? SCST_TGT_RES_QUEUE_FULL : SCST_TGT_RES_FATAL_ERROR; } 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; } /* * 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); if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF)) goto drop; rel_off = ntohl(fh->fh_parm_offset); frame_len = fr_len(fp); 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 == bufflen) { spin_lock(&fcmd->lock); if (fcmd->state == FT_STATE_NEED_DATA) { fcmd->state = FT_STATE_DATA_IN; scst_rx_data(cmd, SCST_RX_STATUS_SUCCESS, SCST_CONTEXT_THREAD); } spin_unlock(&fcmd->lock); } drop: fc_frame_free(fp); }