/** * iscsi_sendpage - send one page of iSCSI Data-Out. * @conn: iscsi connection * @buf: buffer to write from * @count: remaining data * @sent: number of bytes sent * * Notes: * (Tx, Fast Path) **/ inline int iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf, int *count, int *sent) { int flags = 0; /* MSG_DONTWAIT; */ int res, size; size = buf->sg.length - buf->sent; BUG_ON(buf->sent + size > buf->sg.length); if (size > *count) size = *count; if (buf->sent + size != buf->sg.length || *count != size) flags |= MSG_MORE; res = iscsi_send(conn, buf, size, flags); debug_tcp("sendpage: %d bytes, sent %d left %d sent %d res %d\n", size, buf->sent, *count, *sent, res); if (res >= 0) { *count -= res; *sent += res; if (size != res) return -EAGAIN; return 0; } return res; }
static void iscsi_sw_tcp_state_change(struct sock *sk) { struct iscsi_tcp_conn *tcp_conn; struct iscsi_sw_tcp_conn *tcp_sw_conn; struct iscsi_conn *conn; struct iscsi_session *session; void (*old_state_change)(struct sock *); read_lock(&sk->sk_callback_lock); conn = (struct iscsi_conn*)sk->sk_user_data; session = conn->session; if ((sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) && !atomic_read(&sk->sk_rmem_alloc)) { debug_tcp("iscsi_tcp_state_change: TCP_CLOSE|TCP_CLOSE_WAIT\n"); iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); } tcp_conn = conn->dd_data; tcp_sw_conn = tcp_conn->dd_data; old_state_change = tcp_sw_conn->old_state_change; read_unlock(&sk->sk_callback_lock); old_state_change(sk); }
/** * iscsi_tcp_copy - copy skb bits to the destanation buffer * @conn: iscsi tcp connection * * Notes: * The function calls skb_copy_bits() and updates per-connection * byte counters. **/ inline int iscsi_tcp_copy(struct iscsi_tcp_conn *tcp_conn) { void *buf = tcp_conn->data; int buf_size = tcp_conn->in.datalen; int buf_left = buf_size - tcp_conn->data_copied; int size = min(tcp_conn->in.copy, buf_left); int rc; debug_tcp("tcp_copy %d bytes at offset %d copied %d\n", size, tcp_conn->in.offset, tcp_conn->data_copied); BUG_ON(size <= 0); rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset, (char*)buf + tcp_conn->data_copied, size); BUG_ON(rc); tcp_conn->in.offset += size; tcp_conn->in.copy -= size; tcp_conn->in.copied += size; tcp_conn->data_copied += size; if (buf_size != tcp_conn->data_copied) return -EAGAIN; return 0; }
/** * iscsi_sw_tcp_recv - TCP receive in sendfile fashion * @rd_desc: read descriptor * @skb: socket buffer * @offset: offset in skb * @len: skb->len - offset */ static int iscsi_sw_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb, unsigned int offset, size_t len) { struct iscsi_conn *conn = rd_desc->arg.data; unsigned int consumed, total_consumed = 0; int status; debug_tcp("in %d bytes\n", skb->len - offset); do { status = 0; consumed = iscsi_tcp_recv_skb(conn, skb, offset, 0, &status); offset += consumed; total_consumed += consumed; } while (consumed != 0 && status != ISCSI_TCP_SKB_DONE); debug_tcp("read %d bytes status %d\n", skb->len - offset, status); return total_consumed; }
/** * iscsi_write_space - Called when more output buffer space is available * @sk: socket space is available for **/ static void iscsi_sw_tcp_write_space(struct sock *sk) { struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; tcp_sw_conn->old_write_space(sk); debug_tcp("iscsi_write_space: cid %d\n", conn->id); scsi_queue_work(conn->session->host, &conn->xmitwork); }
/** * iscsi_ctask_copy - copy skb bits to the destanation cmd task * @conn: iscsi tcp connection * @ctask: scsi command task * @buf: buffer to copy to * @buf_size: size of buffer * @offset: offset within the buffer * * Notes: * The function calls skb_copy_bits() and updates per-connection and * per-cmd byte counters. * * Read counters (in bytes): * * conn->in.offset offset within in progress SKB * conn->in.copy left to copy from in progress SKB * including padding * conn->in.copied copied already from in progress SKB * conn->data_copied copied already from in progress buffer * ctask->sent total bytes sent up to the MidLayer * ctask->data_count left to copy from in progress Data-In * buf_left left to copy from in progress buffer **/ static inline int iscsi_ctask_copy(struct iscsi_tcp_conn *tcp_conn, struct iscsi_cmd_task *ctask, void *buf, int buf_size, int offset) { struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; int buf_left = buf_size - (tcp_conn->data_copied + offset); int size = min(tcp_conn->in.copy, buf_left); int rc; size = min(size, ctask->data_count); debug_tcp("ctask_copy %d bytes at offset %d copied %d\n", size, tcp_conn->in.offset, tcp_conn->in.copied); BUG_ON(size <= 0); BUG_ON(tcp_ctask->sent + size > ctask->total_length); rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset, (char*)buf + (offset + tcp_conn->data_copied), size); /* must fit into skb->len */ BUG_ON(rc); tcp_conn->in.offset += size; tcp_conn->in.copy -= size; tcp_conn->in.copied += size; tcp_conn->data_copied += size; tcp_ctask->sent += size; ctask->data_count -= size; BUG_ON(tcp_conn->in.copy < 0); BUG_ON(ctask->data_count < 0); if (buf_size != (tcp_conn->data_copied + offset)) { if (!ctask->data_count) { BUG_ON(buf_size - tcp_conn->data_copied < 0); /* done with this PDU */ return buf_size - tcp_conn->data_copied; } return -EAGAIN; } /* done with this buffer or with both - PDU and buffer */ tcp_conn->data_copied = 0; return 0; }
/** * iscsi_sendhdr - send PDU Header via tcp_sendpage() * @conn: iscsi connection * @buf: buffer to write from * @datalen: lenght of data to be sent after the header * * Notes: * (Tx, Fast Path) **/ inline int iscsi_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen) { int flags = 0; /* MSG_DONTWAIT; */ int res, size; size = buf->sg.length - buf->sent; BUG_ON(buf->sent + size > buf->sg.length); if (buf->sent + size != buf->sg.length || datalen) flags |= MSG_MORE; res = iscsi_send(conn, buf, size, flags); debug_tcp("sendhdr %d bytes, sent %d res %d\n", size, buf->sent, res); if (res >= 0) { if (size != res) return -EAGAIN; return 0; } return res; }
/** * iscsi_sw_tcp_xmit_segment - transmit segment * @tcp_conn: the iSCSI TCP connection * @segment: the buffer to transmnit * * This function transmits as much of the buffer as * the network layer will accept, and returns the number of * bytes transmitted. * * If CRC hashing is enabled, the function will compute the * hash as it goes. When the entire segment has been transmitted, * it will retrieve the hash value and send it as well. */ static int iscsi_sw_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn, struct iscsi_segment *segment) { struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct socket *sk = tcp_sw_conn->sock; unsigned int copied = 0; int r = 0; while (!iscsi_tcp_segment_done(tcp_conn, segment, 0, r)) { struct scatterlist *sg; unsigned int offset, copy; int flags = 0; r = 0; offset = segment->copied; copy = segment->size - offset; if (segment->total_copied + segment->size < segment->total_size) flags |= MSG_MORE; /* Use sendpage if we can; else fall back to sendmsg */ if (!segment->data) { sg = segment->sg; offset += segment->sg_offset + sg->offset; r = tcp_sw_conn->sendpage(sk, sg_page(sg), offset, copy, flags); } else { struct msghdr msg = { .msg_flags = flags }; struct kvec iov = { .iov_base = segment->data + offset, .iov_len = copy }; r = kernel_sendmsg(sk, &msg, &iov, 1, copy); } if (r < 0) { iscsi_tcp_segment_unmap(segment); if (copied || r == -EAGAIN) break; return r; } copied += r; } return copied; } /** * iscsi_sw_tcp_xmit - TCP transmit **/ static int iscsi_sw_tcp_xmit(struct iscsi_conn *conn) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct iscsi_segment *segment = &tcp_sw_conn->out.segment; unsigned int consumed = 0; int rc = 0; while (1) { rc = iscsi_sw_tcp_xmit_segment(tcp_conn, segment); if (rc < 0) { rc = ISCSI_ERR_XMIT_FAILED; goto error; } if (rc == 0) break; consumed += rc; if (segment->total_copied >= segment->total_size) { if (segment->done != NULL) { rc = segment->done(tcp_conn, segment); if (rc != 0) goto error; } } } debug_tcp("xmit %d bytes\n", consumed); conn->txdata_octets += consumed; return consumed; error: /* Transmit error. We could initiate error recovery * here. */ debug_tcp("Error sending PDU, errno=%d\n", rc); iscsi_conn_failure(conn, rc); return -EIO; } /** * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit */ static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct iscsi_segment *segment = &tcp_sw_conn->out.segment; return segment->total_copied - segment->total_size; } static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task) { struct iscsi_conn *conn = task->conn; int rc; while (iscsi_sw_tcp_xmit_qlen(conn)) { rc = iscsi_sw_tcp_xmit(conn); if (rc == 0) return -EAGAIN; if (rc < 0) return rc; } return 0; } /* * This is called when we're done sending the header. * Simply copy the data_segment to the send segment, and return. */ static int iscsi_sw_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn, struct iscsi_segment *segment) { struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; tcp_sw_conn->out.segment = tcp_sw_conn->out.data_segment; debug_tcp("Header done. Next segment size %u total_size %u\n", tcp_sw_conn->out.segment.size, tcp_sw_conn->out.segment.total_size); return 0; } static void iscsi_sw_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; debug_tcp("%s(%p%s)\n", __func__, tcp_conn, conn->hdrdgst_en? ", digest enabled" : ""); /* Clear the data segment - needs to be filled in by the * caller using iscsi_tcp_send_data_prep() */ memset(&tcp_sw_conn->out.data_segment, 0, sizeof(struct iscsi_segment)); /* If header digest is enabled, compute the CRC and * place the digest into the same buffer. We make * sure that both iscsi_tcp_task and mtask have * sufficient room. */ if (conn->hdrdgst_en) { iscsi_tcp_dgst_header(&tcp_sw_conn->tx_hash, hdr, hdrlen, hdr + hdrlen); hdrlen += ISCSI_DIGEST_SIZE; } /* Remember header pointer for later, when we need * to decide whether there's a payload to go along * with the header. */ tcp_sw_conn->out.hdr = hdr; iscsi_segment_init_linear(&tcp_sw_conn->out.segment, hdr, hdrlen, iscsi_sw_tcp_send_hdr_done, NULL); } /* * Prepare the send buffer for the payload data. * Padding and checksumming will all be taken care * of by the iscsi_segment routines. */ static int iscsi_sw_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg, unsigned int count, unsigned int offset, unsigned int len) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct hash_desc *tx_hash = NULL; unsigned int hdr_spec_len; debug_tcp("%s(%p, offset=%d, datalen=%d%s)\n", __func__, tcp_conn, offset, len, conn->datadgst_en? ", digest enabled" : ""); /* Make sure the datalen matches what the caller said he would send. */ hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength); WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len)); if (conn->datadgst_en) tx_hash = &tcp_sw_conn->tx_hash; return iscsi_segment_seek_sg(&tcp_sw_conn->out.data_segment, sg, count, offset, len, NULL, tx_hash); } static void iscsi_sw_tcp_send_linear_data_prep(struct iscsi_conn *conn, void *data, size_t len) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct hash_desc *tx_hash = NULL; unsigned int hdr_spec_len; debug_tcp("%s(%p, datalen=%d%s)\n", __func__, tcp_conn, len, conn->datadgst_en? ", digest enabled" : ""); /* Make sure the datalen matches what the caller said he would send. */ hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength); WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len)); if (conn->datadgst_en) tx_hash = &tcp_sw_conn->tx_hash; iscsi_segment_init_linear(&tcp_sw_conn->out.data_segment, data, len, NULL, tx_hash); } static int iscsi_sw_tcp_pdu_init(struct iscsi_task *task, unsigned int offset, unsigned int count) { struct iscsi_conn *conn = task->conn; int err = 0; iscsi_sw_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len); if (!count) return 0; if (!task->sc) iscsi_sw_tcp_send_linear_data_prep(conn, task->data, count); else { struct scsi_data_buffer *sdb = scsi_out(task->sc); err = iscsi_sw_tcp_send_data_prep(conn, sdb->table.sgl, sdb->table.nents, offset, count); } if (err) { iscsi_conn_failure(conn, err); return -EIO; } return 0; } static int iscsi_sw_tcp_pdu_alloc(struct iscsi_task *task, uint8_t opcode) { struct iscsi_tcp_task *tcp_task = task->dd_data; task->hdr = task->dd_data + sizeof(*tcp_task); task->hdr_max = sizeof(struct iscsi_sw_tcp_hdrbuf) - ISCSI_DIGEST_SIZE; return 0; } static struct iscsi_cls_conn * iscsi_sw_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx) { struct iscsi_conn *conn; struct iscsi_cls_conn *cls_conn; struct iscsi_tcp_conn *tcp_conn; struct iscsi_sw_tcp_conn *tcp_sw_conn; cls_conn = iscsi_tcp_conn_setup(cls_session, sizeof(*tcp_sw_conn), conn_idx); if (!cls_conn) return NULL; conn = cls_conn->dd_data; tcp_conn = conn->dd_data; tcp_sw_conn = tcp_conn->dd_data; tcp_sw_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0, CRYPTO_ALG_ASYNC); tcp_sw_conn->tx_hash.flags = 0; if (IS_ERR(tcp_sw_conn->tx_hash.tfm)) goto free_conn; tcp_sw_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0, CRYPTO_ALG_ASYNC); tcp_sw_conn->rx_hash.flags = 0; if (IS_ERR(tcp_sw_conn->rx_hash.tfm)) goto free_tx_tfm; tcp_conn->rx_hash = &tcp_sw_conn->rx_hash; return cls_conn; free_tx_tfm: crypto_free_hash(tcp_sw_conn->tx_hash.tfm); free_conn: iscsi_conn_printk(KERN_ERR, conn, "Could not create connection due to crc32c " "loading error. Make sure the crc32c " "module is built as a module or into the " "kernel\n"); iscsi_tcp_conn_teardown(cls_conn); return NULL; } static void iscsi_sw_tcp_release_conn(struct iscsi_conn *conn) { struct iscsi_session *session = conn->session; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct socket *sock = tcp_sw_conn->sock; if (!sock) return; sock_hold(sock->sk); iscsi_sw_tcp_conn_restore_callbacks(tcp_sw_conn); sock_put(sock->sk); spin_lock_bh(&session->lock); tcp_sw_conn->sock = NULL; spin_unlock_bh(&session->lock); sockfd_put(sock); } static void iscsi_sw_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; iscsi_sw_tcp_release_conn(conn); if (tcp_sw_conn->tx_hash.tfm) crypto_free_hash(tcp_sw_conn->tx_hash.tfm); if (tcp_sw_conn->rx_hash.tfm) crypto_free_hash(tcp_sw_conn->rx_hash.tfm); iscsi_tcp_conn_teardown(cls_conn); } static void iscsi_sw_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; /* userspace may have goofed up and not bound us */ if (!tcp_sw_conn->sock) return; /* * Make sure our recv side is stopped. * Older tools called conn stop before ep_disconnect * so IO could still be coming in. */ write_lock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock); set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); write_unlock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock); iscsi_conn_stop(cls_conn, flag); iscsi_sw_tcp_release_conn(conn); } static int iscsi_sw_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock, char *buf, int *port, int (*getname)(struct socket *, struct sockaddr *, int *addrlen)) { struct sockaddr_storage *addr; struct sockaddr_in6 *sin6; struct sockaddr_in *sin; int rc = 0, len; addr = kmalloc(sizeof(*addr), GFP_KERNEL); if (!addr) return -ENOMEM; if (getname(sock, (struct sockaddr *) addr, &len)) { rc = -ENODEV; goto free_addr; } switch (addr->ss_family) { case AF_INET: sin = (struct sockaddr_in *)addr; spin_lock_bh(&conn->session->lock); sprintf(buf, "%pI4", &sin->sin_addr.s_addr); *port = be16_to_cpu(sin->sin_port); spin_unlock_bh(&conn->session->lock); break; case AF_INET6: sin6 = (struct sockaddr_in6 *)addr; spin_lock_bh(&conn->session->lock); sprintf(buf, "%pI6", &sin6->sin6_addr); *port = be16_to_cpu(sin6->sin6_port); spin_unlock_bh(&conn->session->lock); break; } free_addr: kfree(addr); return rc; } static int iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session, struct iscsi_cls_conn *cls_conn, uint64_t transport_eph, int is_leading) { struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); struct iscsi_host *ihost = shost_priv(shost); struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct sock *sk; struct socket *sock; int err; /* lookup for existing socket */ sock = sockfd_lookup((int)transport_eph, &err); if (!sock) { iscsi_conn_printk(KERN_ERR, conn, "sockfd_lookup failed %d\n", err); return -EEXIST; } /* * copy these values now because if we drop the session * userspace may still want to query the values since we will * be using them for the reconnect */ err = iscsi_sw_tcp_get_addr(conn, sock, conn->portal_address, &conn->portal_port, kernel_getpeername); if (err) goto free_socket; err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address, &ihost->local_port, kernel_getsockname); if (err) goto free_socket; err = iscsi_conn_bind(cls_session, cls_conn, is_leading); if (err) goto free_socket; /* bind iSCSI connection and socket */ tcp_sw_conn->sock = sock; /* setup Socket parameters */ sk = sock->sk; sk->sk_reuse = 1; sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */ sk->sk_allocation = GFP_ATOMIC; iscsi_sw_tcp_conn_set_callbacks(conn); tcp_sw_conn->sendpage = tcp_sw_conn->sock->ops->sendpage; /* * set receive state machine into initial state */ iscsi_tcp_hdr_recv_prep(tcp_conn); return 0; free_socket: sockfd_put(sock); return err; } static int iscsi_sw_tcp_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf, int buflen) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_session *session = conn->session; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; int value; switch(param) { case ISCSI_PARAM_HDRDGST_EN: iscsi_set_param(cls_conn, param, buf, buflen); break; case ISCSI_PARAM_DATADGST_EN: iscsi_set_param(cls_conn, param, buf, buflen); tcp_sw_conn->sendpage = conn->datadgst_en ? sock_no_sendpage : tcp_sw_conn->sock->ops->sendpage; break; case ISCSI_PARAM_MAX_R2T: sscanf(buf, "%d", &value); if (value <= 0 || !is_power_of_2(value)) return -EINVAL; if (session->max_r2t == value) break; iscsi_tcp_r2tpool_free(session); iscsi_set_param(cls_conn, param, buf, buflen); if (iscsi_tcp_r2tpool_alloc(session)) return -ENOMEM; break; default: return iscsi_set_param(cls_conn, param, buf, buflen); } return 0; } static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf) { struct iscsi_conn *conn = cls_conn->dd_data; int len; switch(param) { case ISCSI_PARAM_CONN_PORT: spin_lock_bh(&conn->session->lock); len = sprintf(buf, "%hu\n", conn->portal_port); spin_unlock_bh(&conn->session->lock); break; case ISCSI_PARAM_CONN_ADDRESS: spin_lock_bh(&conn->session->lock); len = sprintf(buf, "%s\n", conn->portal_address); spin_unlock_bh(&conn->session->lock); break; default: return iscsi_conn_get_param(cls_conn, param, buf); } return len; } static void iscsi_sw_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; stats->custom_length = 3; strcpy(stats->custom[0].desc, "tx_sendpage_failures"); stats->custom[0].value = tcp_sw_conn->sendpage_failures_cnt; strcpy(stats->custom[1].desc, "rx_discontiguous_hdr"); stats->custom[1].value = tcp_sw_conn->discontiguous_hdr_cnt; strcpy(stats->custom[2].desc, "eh_abort_cnt"); stats->custom[2].value = conn->eh_abort_cnt; iscsi_tcp_conn_get_stats(cls_conn, stats); } static struct iscsi_cls_session * iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, uint16_t qdepth, uint32_t initial_cmdsn, uint32_t *hostno) { struct iscsi_cls_session *cls_session; struct iscsi_session *session; struct Scsi_Host *shost; if (ep) { printk(KERN_ERR "iscsi_tcp: invalid ep %p.\n", ep); return NULL; } shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, 0, qdepth); if (!shost) return NULL; shost->transportt = iscsi_sw_tcp_scsi_transport; shost->max_lun = iscsi_max_lun; shost->max_id = 0; shost->max_channel = 0; shost->max_cmd_len = SCSI_MAX_VARLEN_CDB_SIZE; if (iscsi_host_add(shost, NULL)) goto free_host; *hostno = shost->host_no; cls_session = iscsi_session_setup(&iscsi_sw_tcp_transport, shost, cmds_max, sizeof(struct iscsi_tcp_task) + sizeof(struct iscsi_sw_tcp_hdrbuf), initial_cmdsn, 0); if (!cls_session) goto remove_host; session = cls_session->dd_data; shost->can_queue = session->scsi_cmds_max; if (iscsi_tcp_r2tpool_alloc(session)) goto remove_session; return cls_session; remove_session: iscsi_session_teardown(cls_session); remove_host: iscsi_host_remove(shost); free_host: iscsi_host_free(shost); return NULL; } static void iscsi_sw_tcp_session_destroy(struct iscsi_cls_session *cls_session) { struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); iscsi_tcp_r2tpool_free(cls_session->dd_data); iscsi_session_teardown(cls_session); iscsi_host_remove(shost); iscsi_host_free(shost); } static int iscsi_sw_tcp_slave_configure(struct scsi_device *sdev) { blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY); blk_queue_dma_alignment(sdev->request_queue, 0); return 0; }
/** * iscsi_tcp_data_recv - TCP receive in sendfile fashion * @rd_desc: read descriptor * @skb: socket buffer * @offset: offset in skb * @len: skb->len - offset **/ static int iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb, unsigned int offset, size_t len) { int rc; struct iscsi_conn *conn = rd_desc->arg.data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; int processed; char pad[ISCSI_PAD_LEN]; struct scatterlist sg; /* * Save current SKB and its offset in the corresponding * connection context. */ tcp_conn->in.copy = skb->len - offset; tcp_conn->in.offset = offset; tcp_conn->in.skb = skb; tcp_conn->in.len = tcp_conn->in.copy; BUG_ON(tcp_conn->in.copy <= 0); debug_tcp("in %d bytes\n", tcp_conn->in.copy); more: tcp_conn->in.copied = 0; rc = 0; if (unlikely(conn->suspend_rx)) { debug_tcp("conn %d Rx suspended!\n", conn->id); return 0; } if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER || tcp_conn->in_progress == IN_PROGRESS_HEADER_GATHER) { rc = iscsi_hdr_extract(tcp_conn); if (rc) { if (rc == -EAGAIN) goto nomore; else { iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); return 0; } } /* * Verify and process incoming PDU header. */ rc = tcp_conn->ops->hdr_recv(conn); if (!rc && tcp_conn->in.datalen) { if (conn->datadgst_en) { BUG_ON(!tcp_conn->data_rx_tfm); crypto_digest_init(tcp_conn->data_rx_tfm); } tcp_conn->in_progress = IN_PROGRESS_DATA_RECV; } else if (rc) { iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); return 0; } } if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV) { uint32_t recv_digest; debug_tcp("extra data_recv offset %d copy %d\n", tcp_conn->in.offset, tcp_conn->in.copy); skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset, &recv_digest, 4); tcp_conn->in.offset += 4; tcp_conn->in.copy -= 4; if (recv_digest != tcp_conn->in.datadgst) { debug_tcp("iscsi_tcp: data digest error!" "0x%x != 0x%x\n", recv_digest, tcp_conn->in.datadgst); iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST); return 0; } else { debug_tcp("iscsi_tcp: data digest match!" "0x%x == 0x%x\n", recv_digest, tcp_conn->in.datadgst); tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; } } if (unlikely(conn->suspend_rx)) { debug_tcp("conn %d Rx suspended!\n", conn->id); goto nomore; } if (tcp_conn->in_progress == IN_PROGRESS_DATA_RECV && tcp_conn->in.copy) { debug_tcp("data_recv offset %d copy %d\n", tcp_conn->in.offset, tcp_conn->in.copy); rc = tcp_conn->ops->data_recv(conn); if (rc) { if (rc == -EAGAIN) goto again; iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); return 0; } tcp_conn->in.copy -= tcp_conn->in.padding; tcp_conn->in.offset += tcp_conn->in.padding; if (conn->datadgst_en) { if (tcp_conn->in.padding) { debug_tcp("padding -> %d\n", tcp_conn->in.padding); memset(pad, 0, tcp_conn->in.padding); sg_init_one(&sg, pad, tcp_conn->in.padding); crypto_digest_update(tcp_conn->data_rx_tfm, &sg, 1); } crypto_digest_final(tcp_conn->data_rx_tfm, (u8 *) & tcp_conn->in.datadgst); debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst); tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV; } else tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; } debug_tcp("f, processed %d from out of %d padding %d\n", tcp_conn->in.offset - offset, (int)len, tcp_conn->in.padding); BUG_ON(tcp_conn->in.offset - offset > len); if (tcp_conn->in.offset - offset != len) { debug_tcp("continue to process %d bytes\n", (int)len - (tcp_conn->in.offset - offset)); goto more; } nomore: processed = tcp_conn->in.offset - offset; BUG_ON(processed == 0); return processed; again: processed = tcp_conn->in.offset - offset; debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n", processed, (int)len, (int)rd_desc->count); BUG_ON(processed == 0); BUG_ON(processed > len); conn->rxdata_octets += processed; return processed; }
static inline int iscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn) { struct sk_buff *skb = tcp_conn->in.skb; tcp_conn->in.zero_copy_hdr = 0; if (tcp_conn->in.copy >= tcp_conn->hdr_size && tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) { /* * Zero-copy PDU Header: using connection context * to store header pointer. */ if (skb_shinfo(skb)->frag_list == NULL && !skb_shinfo(skb)->nr_frags) { tcp_conn->in.hdr = (struct iscsi_hdr *) ((char*)skb->data + tcp_conn->in.offset); tcp_conn->in.zero_copy_hdr = 1; } else { /* ignoring return code since we checked * in.copy before */ skb_copy_bits(skb, tcp_conn->in.offset, &tcp_conn->hdr, tcp_conn->hdr_size); tcp_conn->in.hdr = &tcp_conn->hdr; } tcp_conn->in.offset += tcp_conn->hdr_size; tcp_conn->in.copy -= tcp_conn->hdr_size; } else { int hdr_remains; int copylen; /* * PDU header scattered across SKB's, * copying it... This'll happen quite rarely. */ if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) tcp_conn->in.hdr_offset = 0; hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset; BUG_ON(hdr_remains <= 0); copylen = min(tcp_conn->in.copy, hdr_remains); skb_copy_bits(skb, tcp_conn->in.offset, (char*)&tcp_conn->hdr + tcp_conn->in.hdr_offset, copylen); debug_tcp("PDU gather offset %d bytes %d in.offset %d " "in.copy %d\n", tcp_conn->in.hdr_offset, copylen, tcp_conn->in.offset, tcp_conn->in.copy); tcp_conn->in.offset += copylen; tcp_conn->in.copy -= copylen; if (copylen < hdr_remains) { tcp_conn->in_progress = IN_PROGRESS_HEADER_GATHER; tcp_conn->in.hdr_offset += copylen; return -EAGAIN; } tcp_conn->in.hdr = &tcp_conn->hdr; tcp_conn->discontiguous_hdr_cnt++; tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; } return 0; }