Index: kernel/2.6.16-18_compat.patch =================================================================== --- kernel/2.6.16-18_compat.patch (revision 0) +++ kernel/2.6.16-18_compat.patch (revision 779) @@ -0,0 +1,136 @@ +diff -Nurp a/iscsi_2.6.20_compat.h b/iscsi_2.6.20_compat.h +--- a/iscsi_2.6.20_compat.h 1970-01-01 00:00:00.000000000 +0200 ++++ b/iscsi_2.6.20_compat.h 2007-02-22 18:21:22.000000000 +0200 +@@ -0,0 +1,47 @@ ++ ++#define CRYPTO_ALG_ASYNC 0x00000080 ++struct hash_desc ++{ ++ struct crypto_tfm *tfm; ++ u32 flags; ++}; ++ ++static inline int crypto_hash_init(struct hash_desc *desc) ++{ ++ crypto_digest_init(desc->tfm); ++ return 0; ++} ++ ++static inline int crypto_hash_digest(struct hash_desc *desc, ++ struct scatterlist *sg, ++ unsigned int nbytes, u8 *out) ++{ ++ crypto_digest_digest(desc->tfm, sg, (nbytes+(PAGE_SIZE-1)) / PAGE_SIZE, out); ++ return nbytes; ++} ++ ++static inline int crypto_hash_update(struct hash_desc *desc, ++ struct scatterlist *sg, ++ unsigned int nbytes) ++{ ++ crypto_digest_update(desc->tfm, sg, (nbytes+(PAGE_SIZE-1)) / PAGE_SIZE); ++ return nbytes; ++} ++ ++static inline int crypto_hash_final(struct hash_desc *desc, u8 *out) ++{ ++ crypto_digest_final(desc->tfm, out); ++ return 0; ++} ++ ++static inline struct crypto_tfm *crypto_alloc_hash(const char *alg_name, ++ u32 type, u32 mask) ++{ ++ struct crypto_tfm *ret = crypto_alloc_tfm(alg_name ,type); ++ return ret ? ret : ERR_PTR(-ENOMEM); ++} ++ ++static inline void crypto_free_hash(struct crypto_tfm *tfm) ++{ ++ crypto_free_tfm(tfm); ++} +diff -Nurp a/iscsi_tcp.h b/iscsi_tcp.h +--- a/iscsi_tcp.h 2007-01-10 12:38:25.000000000 +0200 ++++ b/iscsi_tcp.h 2007-01-10 12:38:25.000000000 +0200 +@@ -49,6 +49,7 @@ + #define ISCSI_SG_TABLESIZE SG_ALL + #define ISCSI_TCP_MAX_CMD_LEN 16 + ++#include "iscsi_2.6.20_compat.h" + struct crypto_hash; + struct socket; + +diff -Nurp a/libiscsi.c b/libiscsi.c +--- a/libiscsi.c 2007-02-21 11:57:49.000000000 +0200 ++++ b/libiscsi.c 2007-02-21 11:57:49.000000000 +0200 +@@ -716,10 +716,9 @@ again: + return rc; + } + +-static void iscsi_xmitworker(struct work_struct *work) ++static void iscsi_xmitworker(void *data) + { +- struct iscsi_conn *conn = +- container_of(work, struct iscsi_conn, xmitwork); ++ struct iscsi_conn *conn = data; + int rc; + /* + * serialize Xmit worker on a per-connection basis. +@@ -1509,7 +1508,7 @@ iscsi_conn_setup(struct iscsi_cls_sessio + if (conn->mgmtqueue == ERR_PTR(-ENOMEM)) + goto mgmtqueue_alloc_fail; + +- INIT_WORK(&conn->xmitwork, iscsi_xmitworker); ++ INIT_WORK(&conn->xmitwork, iscsi_xmitworker, conn); + + /* allocate login_mtask used for the login/text sequences */ + spin_lock_bh(&session->lock); +diff -Nurp a/libiscsi.h b/libiscsi.h +--- a/libiscsi.h 2007-01-10 12:38:25.000000000 +0200 ++++ b/libiscsi.h 2007-01-10 12:38:25.000000000 +0200 +@@ -25,8 +25,6 @@ + + #include + #include +-#include +-#include + #include "iscsi_proto.h" + #include "iscsi_if.h" + +diff -Nurp a/scsi_transport_iscsi.c b/scsi_transport_iscsi.c +--- a/scsi_transport_iscsi.c 2007-01-10 12:38:25.000000000 +0200 ++++ b/scsi_transport_iscsi.c 2007-01-10 12:38:25.000000000 +0200 +@@ -234,11 +234,9 @@ static int iscsi_user_scan(struct Scsi_H + return 0; + } + +-static void session_recovery_timedout(struct work_struct *work) ++static void session_recovery_timedout(void *data) + { +- struct iscsi_cls_session *session = +- container_of(work, struct iscsi_cls_session, +- recovery_work.work); ++ struct iscsi_cls_session *session = data; + + dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed " + "out after %d secs\n", session->recovery_tmo); +@@ -278,7 +276,7 @@ iscsi_alloc_session(struct Scsi_Host *sh + + session->transport = transport; + session->recovery_tmo = 120; +- INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout); ++ INIT_WORK(&session->recovery_work, session_recovery_timedout, session); + INIT_LIST_HEAD(&session->host_list); + INIT_LIST_HEAD(&session->sess_list); + +diff -Nurp a/scsi_transport_iscsi.h b/scsi_transport_iscsi.h +--- a/scsi_transport_iscsi.h 2007-01-10 12:38:26.000000000 +0200 ++++ b/scsi_transport_iscsi.h 2007-01-10 12:38:25.000000000 +0200 +@@ -176,7 +176,7 @@ struct iscsi_cls_session { + + /* recovery fields */ + int recovery_tmo; +- struct delayed_work recovery_work; ++ struct work_struct recovery_work; + + int target_id; Index: kernel/iscsi_tcp.c =================================================================== --- kernel/iscsi_tcp.c (revision 0) +++ kernel/iscsi_tcp.c (revision 779) @@ -0,0 +1,2235 @@ +/* + * iSCSI Initiator over TCP/IP Data-Path + * + * Copyright (C) 2004 Dmitry Yusupov + * Copyright (C) 2004 Alex Aizman + * Copyright (C) 2005 - 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * See the file COPYING included with this distribution for more details. + * + * Credits: + * Christoph Hellwig + * FUJITA Tomonori + * Arne Redlich + * Zhenyu Wang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scsi_transport_iscsi.h" + +#include "iscsi_tcp.h" + +MODULE_AUTHOR("Dmitry Yusupov , " + "Alex Aizman "); +MODULE_DESCRIPTION("iSCSI/TCP data-path"); +MODULE_LICENSE("GPL"); +/* #define DEBUG_TCP */ +#define DEBUG_ASSERT + +#ifdef DEBUG_TCP +#define debug_tcp(fmt...) printk(KERN_INFO "tcp: " fmt) +#else +#define debug_tcp(fmt...) +#endif + +#ifndef DEBUG_ASSERT +#ifdef BUG_ON +#undef BUG_ON +#endif +#define BUG_ON(expr) +#endif + +static unsigned int iscsi_max_lun = 512; +module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO); + +static inline void +iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size) +{ + ibuf->sg.page = virt_to_page(vbuf); + ibuf->sg.offset = offset_in_page(vbuf); + ibuf->sg.length = size; + ibuf->sent = 0; + ibuf->use_sendmsg = 1; +} + +static inline void +iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg) +{ + ibuf->sg.page = sg->page; + ibuf->sg.offset = sg->offset; + ibuf->sg.length = sg->length; + /* + * Fastpath: sg element fits into single page + */ + if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page)) + ibuf->use_sendmsg = 0; + else + ibuf->use_sendmsg = 1; + ibuf->sent = 0; +} + +static inline int +iscsi_buf_left(struct iscsi_buf *ibuf) +{ + int rc; + + rc = ibuf->sg.length - ibuf->sent; + BUG_ON(rc < 0); + return rc; +} + +static inline void +iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf, + u8* crc) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + + crypto_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc); + buf->sg.length = tcp_conn->hdr_size; +} + +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; +} + +/* + * must be called with session lock + */ +static void +iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_r2t_info *r2t; + struct scsi_cmnd *sc; + + /* flush ctask's r2t queues */ + while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) { + __kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n"); + } + + sc = ctask->sc; + if (unlikely(!sc)) + return; + + tcp_ctask->xmstate = XMSTATE_IDLE; + tcp_ctask->r2t = NULL; +} + +/** + * iscsi_data_rsp - SCSI Data-In Response processing + * @conn: iscsi connection + * @ctask: scsi command task + **/ +static int +iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + int rc; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr; + struct iscsi_session *session = conn->session; + int datasn = be32_to_cpu(rhdr->datasn); + + rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr); + if (rc) + return rc; + /* + * setup Data-In byte counter (gets decremented..) + */ + ctask->data_count = tcp_conn->in.datalen; + + if (tcp_conn->in.datalen == 0) + return 0; + + if (ctask->datasn != datasn) + return ISCSI_ERR_DATASN; + + ctask->datasn++; + + tcp_ctask->data_offset = be32_to_cpu(rhdr->offset); + if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length) + return ISCSI_ERR_DATA_OFFSET; + + if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) { + struct scsi_cmnd *sc = ctask->sc; + + conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1; + if (rhdr->flags & ISCSI_FLAG_DATA_UNDERFLOW) { + int res_count = be32_to_cpu(rhdr->residual_count); + + if (res_count > 0 && + res_count <= sc->request_bufflen) { + sc->resid = res_count; + sc->result = (DID_OK << 16) | rhdr->cmd_status; + } else + sc->result = (DID_BAD_TARGET << 16) | + rhdr->cmd_status; + } else if (rhdr->flags & ISCSI_FLAG_DATA_OVERFLOW) { + sc->resid = be32_to_cpu(rhdr->residual_count); + sc->result = (DID_OK << 16) | rhdr->cmd_status; + } else + sc->result = (DID_OK << 16) | rhdr->cmd_status; + } + + conn->datain_pdus_cnt++; + return 0; +} + +/** + * iscsi_solicit_data_init - initialize first Data-Out + * @conn: iscsi connection + * @ctask: scsi command task + * @r2t: R2T info + * + * Notes: + * Initialize first Data-Out within this R2T sequence and finds + * proper data_offset within this SCSI command. + * + * This function is called with connection lock taken. + **/ +static void +iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, + struct iscsi_r2t_info *r2t) +{ + struct iscsi_data *hdr; + struct scsi_cmnd *sc = ctask->sc; + + hdr = &r2t->dtask.hdr; + memset(hdr, 0, sizeof(struct iscsi_data)); + hdr->ttt = r2t->ttt; + hdr->datasn = cpu_to_be32(r2t->solicit_datasn); + r2t->solicit_datasn++; + hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; + memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun)); + hdr->itt = ctask->hdr->itt; + hdr->exp_statsn = r2t->exp_statsn; + hdr->offset = cpu_to_be32(r2t->data_offset); + if (r2t->data_length > conn->max_xmit_dlength) { + hton24(hdr->dlength, conn->max_xmit_dlength); + r2t->data_count = conn->max_xmit_dlength; + hdr->flags = 0; + } else { + hton24(hdr->dlength, r2t->data_length); + r2t->data_count = r2t->data_length; + hdr->flags = ISCSI_FLAG_CMD_FINAL; + } + conn->dataout_pdus_cnt++; + + r2t->sent = 0; + + iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr, + sizeof(struct iscsi_hdr)); + + if (sc->use_sg) { + int i, sg_count = 0; + struct scatterlist *sg = sc->request_buffer; + + r2t->sg = NULL; + for (i = 0; i < sc->use_sg; i++, sg += 1) { + /* FIXME: prefetch ? */ + if (sg_count + sg->length > r2t->data_offset) { + int page_offset; + + /* sg page found! */ + + /* offset within this page */ + page_offset = r2t->data_offset - sg_count; + + /* fill in this buffer */ + iscsi_buf_init_sg(&r2t->sendbuf, sg); + r2t->sendbuf.sg.offset += page_offset; + r2t->sendbuf.sg.length -= page_offset; + + /* xmit logic will continue with next one */ + r2t->sg = sg + 1; + break; + } + sg_count += sg->length; + } + BUG_ON(r2t->sg == NULL); + } else { + iscsi_buf_init_iov(&r2t->sendbuf, + (char*)sc->request_buffer + r2t->data_offset, + r2t->data_count); + r2t->sg = NULL; + } +} + +/** + * iscsi_r2t_rsp - iSCSI R2T Response processing + * @conn: iscsi connection + * @ctask: scsi command task + **/ +static int +iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_r2t_info *r2t; + struct iscsi_session *session = conn->session; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr; + int r2tsn = be32_to_cpu(rhdr->r2tsn); + int rc; + + if (tcp_conn->in.datalen) { + printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n", + tcp_conn->in.datalen); + return ISCSI_ERR_DATALEN; + } + + if (tcp_ctask->exp_r2tsn && tcp_ctask->exp_r2tsn != r2tsn) + return ISCSI_ERR_R2TSN; + + rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr); + if (rc) + return rc; + + /* FIXME: use R2TSN to detect missing R2T */ + + /* fill-in new R2T associated with the task */ + spin_lock(&session->lock); + if (!ctask->sc || ctask->mtask || + session->state != ISCSI_STATE_LOGGED_IN) { + printk(KERN_INFO "iscsi_tcp: dropping R2T itt %d in " + "recovery...\n", ctask->itt); + spin_unlock(&session->lock); + return 0; + } + + rc = __kfifo_get(tcp_ctask->r2tpool.queue, (void*)&r2t, sizeof(void*)); + BUG_ON(!rc); + + r2t->exp_statsn = rhdr->statsn; + r2t->data_length = be32_to_cpu(rhdr->data_length); + if (r2t->data_length == 0) { + printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n"); + spin_unlock(&session->lock); + return ISCSI_ERR_DATALEN; + } + + if (r2t->data_length > session->max_burst) + debug_scsi("invalid R2T with data len %u and max burst %u." + "Attempting to execute request.\n", + r2t->data_length, session->max_burst); + + r2t->data_offset = be32_to_cpu(rhdr->data_offset); + if (r2t->data_offset + r2t->data_length > ctask->total_length) { + spin_unlock(&session->lock); + printk(KERN_ERR "iscsi_tcp: invalid R2T with data len %u at " + "offset %u and total length %d\n", r2t->data_length, + r2t->data_offset, ctask->total_length); + return ISCSI_ERR_DATALEN; + } + + r2t->ttt = rhdr->ttt; /* no flip */ + r2t->solicit_datasn = 0; + + iscsi_solicit_data_init(conn, ctask, r2t); + + tcp_ctask->exp_r2tsn = r2tsn + 1; + __kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*)); + tcp_ctask->xmstate |= XMSTATE_SOL_HDR; + list_move_tail(&ctask->running, &conn->xmitqueue); + + scsi_queue_work(session->host, &conn->xmitwork); + conn->r2t_pdus_cnt++; + spin_unlock(&session->lock); + + return 0; +} + +static int +iscsi_tcp_hdr_recv(struct iscsi_conn *conn) +{ + int rc = 0, opcode, ahslen; + struct iscsi_hdr *hdr; + struct iscsi_session *session = conn->session; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + uint32_t cdgst, rdgst = 0, itt; + + hdr = tcp_conn->in.hdr; + + /* verify PDU length */ + tcp_conn->in.datalen = ntoh24(hdr->dlength); + if (tcp_conn->in.datalen > conn->max_recv_dlength) { + printk(KERN_ERR "iscsi_tcp: datalen %d > %d\n", + tcp_conn->in.datalen, conn->max_recv_dlength); + return ISCSI_ERR_DATALEN; + } + tcp_conn->data_copied = 0; + + /* read AHS */ + ahslen = hdr->hlength << 2; + tcp_conn->in.offset += ahslen; + tcp_conn->in.copy -= ahslen; + if (tcp_conn->in.copy < 0) { + printk(KERN_ERR "iscsi_tcp: can't handle AHS with length " + "%d bytes\n", ahslen); + return ISCSI_ERR_AHSLEN; + } + + /* calculate read padding */ + tcp_conn->in.padding = tcp_conn->in.datalen & (ISCSI_PAD_LEN-1); + if (tcp_conn->in.padding) { + tcp_conn->in.padding = ISCSI_PAD_LEN - tcp_conn->in.padding; + debug_scsi("read padding %d bytes\n", tcp_conn->in.padding); + } + + if (conn->hdrdgst_en) { + struct scatterlist sg; + + sg_init_one(&sg, (u8 *)hdr, + sizeof(struct iscsi_hdr) + ahslen); + crypto_hash_digest(&tcp_conn->rx_hash, &sg, sg.length, + (u8 *)&cdgst); + rdgst = *(uint32_t*)((char*)hdr + sizeof(struct iscsi_hdr) + + ahslen); + if (cdgst != rdgst) { + printk(KERN_ERR "iscsi_tcp: hdrdgst error " + "recv 0x%x calc 0x%x\n", rdgst, cdgst); + return ISCSI_ERR_HDR_DGST; + } + } + + opcode = hdr->opcode & ISCSI_OPCODE_MASK; + /* verify itt (itt encoding: age+cid+itt) */ + rc = iscsi_verify_itt(conn, hdr, &itt); + if (rc == ISCSI_ERR_NO_SCSI_CMD) { + tcp_conn->in.datalen = 0; /* force drop */ + return 0; + } else if (rc) + return rc; + + debug_tcp("opcode 0x%x offset %d copy %d ahslen %d datalen %d\n", + opcode, tcp_conn->in.offset, tcp_conn->in.copy, + ahslen, tcp_conn->in.datalen); + + switch(opcode) { + case ISCSI_OP_SCSI_DATA_IN: + tcp_conn->in.ctask = session->cmds[itt]; + rc = iscsi_data_rsp(conn, tcp_conn->in.ctask); + if (rc) + return rc; + /* fall through */ + case ISCSI_OP_SCSI_CMD_RSP: + tcp_conn->in.ctask = session->cmds[itt]; + if (tcp_conn->in.datalen) + goto copy_hdr; + + spin_lock(&session->lock); + rc = __iscsi_complete_pdu(conn, hdr, NULL, 0); + spin_unlock(&session->lock); + break; + case ISCSI_OP_R2T: + tcp_conn->in.ctask = session->cmds[itt]; + if (ahslen) + rc = ISCSI_ERR_AHSLEN; + else if (tcp_conn->in.ctask->sc->sc_data_direction == + DMA_TO_DEVICE) + rc = iscsi_r2t_rsp(conn, tcp_conn->in.ctask); + else + rc = ISCSI_ERR_PROTO; + break; + case ISCSI_OP_LOGIN_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_REJECT: + case ISCSI_OP_ASYNC_EVENT: + /* + * It is possible that we could get a PDU with a buffer larger + * than 8K, but there are no targets that currently do this. + * For now we fail until we find a vendor that needs it + */ + if (ISCSI_DEF_MAX_RECV_SEG_LEN < + tcp_conn->in.datalen) { + printk(KERN_ERR "iscsi_tcp: received buffer of len %u " + "but conn buffer is only %u (opcode %0x)\n", + tcp_conn->in.datalen, + ISCSI_DEF_MAX_RECV_SEG_LEN, opcode); + rc = ISCSI_ERR_PROTO; + break; + } + + if (tcp_conn->in.datalen) + goto copy_hdr; + /* fall through */ + case ISCSI_OP_LOGOUT_RSP: + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_SCSI_TMFUNC_RSP: + rc = iscsi_complete_pdu(conn, hdr, NULL, 0); + break; + default: + rc = ISCSI_ERR_BAD_OPCODE; + break; + } + + return rc; + +copy_hdr: + /* + * if we did zero copy for the header but we will need multiple + * skbs to complete the command then we have to copy the header + * for later use + */ + if (tcp_conn->in.zero_copy_hdr && tcp_conn->in.copy <= + (tcp_conn->in.datalen + tcp_conn->in.padding + + (conn->datadgst_en ? 4 : 0))) { + debug_tcp("Copying header for later use. in.copy %d in.datalen" + " %d\n", tcp_conn->in.copy, tcp_conn->in.datalen); + memcpy(&tcp_conn->hdr, tcp_conn->in.hdr, + sizeof(struct iscsi_hdr)); + tcp_conn->in.hdr = &tcp_conn->hdr; + tcp_conn->in.zero_copy_hdr = 0; + } + return 0; +} + +/** + * 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_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. + **/ +static inline int +iscsi_tcp_copy(struct iscsi_conn *conn, int buf_size) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + 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*)conn->data + 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; +} + +static inline void +partial_sg_digest_update(struct hash_desc *desc, struct scatterlist *sg, + int offset, int length) +{ + struct scatterlist temp; + + memcpy(&temp, sg, sizeof(struct scatterlist)); + temp.offset = offset; + temp.length = length; + crypto_hash_update(desc, &temp, length); +} + +static void +iscsi_recv_digest_update(struct iscsi_tcp_conn *tcp_conn, char* buf, int len) +{ + struct scatterlist tmp; + + sg_init_one(&tmp, buf, len); + crypto_hash_update(&tcp_conn->rx_hash, &tmp, len); +} + +static int iscsi_scsi_data_in(struct iscsi_conn *conn) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_cmd_task *ctask = tcp_conn->in.ctask; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct scsi_cmnd *sc = ctask->sc; + struct scatterlist *sg; + int i, offset, rc = 0; + + BUG_ON((void*)ctask != sc->SCp.ptr); + + /* + * copying Data-In into the Scsi_Cmnd + */ + if (!sc->use_sg) { + i = ctask->data_count; + rc = iscsi_ctask_copy(tcp_conn, ctask, sc->request_buffer, + sc->request_bufflen, + tcp_ctask->data_offset); + if (rc == -EAGAIN) + return rc; + if (conn->datadgst_en) + iscsi_recv_digest_update(tcp_conn, sc->request_buffer, + i); + rc = 0; + goto done; + } + + offset = tcp_ctask->data_offset; + sg = sc->request_buffer; + + if (tcp_ctask->data_offset) + for (i = 0; i < tcp_ctask->sg_count; i++) + offset -= sg[i].length; + /* we've passed through partial sg*/ + if (offset < 0) + offset = 0; + + for (i = tcp_ctask->sg_count; i < sc->use_sg; i++) { + char *dest; + + dest = kmap_atomic(sg[i].page, KM_SOFTIRQ0); + rc = iscsi_ctask_copy(tcp_conn, ctask, dest + sg[i].offset, + sg[i].length, offset); + kunmap_atomic(dest, KM_SOFTIRQ0); + if (rc == -EAGAIN) + /* continue with the next SKB/PDU */ + return rc; + if (!rc) { + if (conn->datadgst_en) { + if (!offset) + crypto_hash_update( + &tcp_conn->rx_hash, + &sg[i], sg[i].length); + else + partial_sg_digest_update( + &tcp_conn->rx_hash, + &sg[i], + sg[i].offset + offset, + sg[i].length - offset); + } + offset = 0; + tcp_ctask->sg_count++; + } + + if (!ctask->data_count) { + if (rc && conn->datadgst_en) + /* + * data-in is complete, but buffer not... + */ + partial_sg_digest_update(&tcp_conn->rx_hash, + &sg[i], + sg[i].offset, + sg[i].length-rc); + rc = 0; + break; + } + + if (!tcp_conn->in.copy) + return -EAGAIN; + } + BUG_ON(ctask->data_count); + +done: + /* check for non-exceptional status */ + if (tcp_conn->in.hdr->flags & ISCSI_FLAG_DATA_STATUS) { + debug_scsi("done [sc %lx res %d itt 0x%x flags 0x%x]\n", + (long)sc, sc->result, ctask->itt, + tcp_conn->in.hdr->flags); + spin_lock(&conn->session->lock); + __iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0); + spin_unlock(&conn->session->lock); + } + + return rc; +} + +static int +iscsi_data_recv(struct iscsi_conn *conn) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + int rc = 0, opcode; + + opcode = tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK; + switch (opcode) { + case ISCSI_OP_SCSI_DATA_IN: + rc = iscsi_scsi_data_in(conn); + break; + case ISCSI_OP_SCSI_CMD_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_LOGIN_RSP: + case ISCSI_OP_ASYNC_EVENT: + case ISCSI_OP_REJECT: + /* + * Collect data segment to the connection's data + * placeholder + */ + if (iscsi_tcp_copy(conn, tcp_conn->in.datalen)) { + rc = -EAGAIN; + goto exit; + } + + rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, conn->data, + tcp_conn->in.datalen); + if (!rc && conn->datadgst_en && opcode != ISCSI_OP_LOGIN_RSP) + iscsi_recv_digest_update(tcp_conn, conn->data, + tcp_conn->in.datalen); + break; + default: + BUG_ON(1); + } +exit: + return rc; +} + +/** + * 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 = iscsi_tcp_hdr_recv(conn); + if (!rc && tcp_conn->in.datalen) { + if (conn->datadgst_en) + crypto_hash_init(&tcp_conn->rx_hash); + tcp_conn->in_progress = IN_PROGRESS_DATA_RECV; + } else if (rc) { + iscsi_conn_failure(conn, rc); + 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); + rc = iscsi_tcp_copy(conn, sizeof(uint32_t)); + if (rc) { + if (rc == -EAGAIN) + goto again; + iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); + return 0; + } + + memcpy(&recv_digest, conn->data, sizeof(uint32_t)); + 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 (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 = iscsi_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_hash_update(&tcp_conn->rx_hash, + &sg, sg.length); + } + crypto_hash_final(&tcp_conn->rx_hash, + (u8 *) &tcp_conn->in.datadgst); + debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst); + tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV; + tcp_conn->data_copied = 0; + } 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 void +iscsi_tcp_data_ready(struct sock *sk, int flag) +{ + struct iscsi_conn *conn = sk->sk_user_data; + read_descriptor_t rd_desc; + + read_lock(&sk->sk_callback_lock); + + /* + * Use rd_desc to pass 'conn' to iscsi_tcp_data_recv. + * We set count to 1 because we want the network layer to + * hand us all the skbs that are available. iscsi_tcp_data_recv + * handled pdus that cross buffers or pdus that still need data. + */ + rd_desc.arg.data = conn; + rd_desc.count = 1; + tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv); + + read_unlock(&sk->sk_callback_lock); +} + +static void +iscsi_tcp_state_change(struct sock *sk) +{ + struct iscsi_tcp_conn *tcp_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; + old_state_change = tcp_conn->old_state_change; + + read_unlock(&sk->sk_callback_lock); + + old_state_change(sk); +} + +/** + * iscsi_write_space - Called when more output buffer space is available + * @sk: socket space is available for + **/ +static void +iscsi_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; + + tcp_conn->old_write_space(sk); + debug_tcp("iscsi_write_space: cid %d\n", conn->id); + scsi_queue_work(conn->session->host, &conn->xmitwork); +} + +static void +iscsi_conn_set_callbacks(struct iscsi_conn *conn) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct sock *sk = tcp_conn->sock->sk; + + /* assign new callbacks */ + write_lock_bh(&sk->sk_callback_lock); + sk->sk_user_data = conn; + tcp_conn->old_data_ready = sk->sk_data_ready; + tcp_conn->old_state_change = sk->sk_state_change; + tcp_conn->old_write_space = sk->sk_write_space; + sk->sk_data_ready = iscsi_tcp_data_ready; + sk->sk_state_change = iscsi_tcp_state_change; + sk->sk_write_space = iscsi_write_space; + write_unlock_bh(&sk->sk_callback_lock); +} + +static void +iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn) +{ + struct sock *sk = tcp_conn->sock->sk; + + /* restore socket callbacks, see also: iscsi_conn_set_callbacks() */ + write_lock_bh(&sk->sk_callback_lock); + sk->sk_user_data = NULL; + sk->sk_data_ready = tcp_conn->old_data_ready; + sk->sk_state_change = tcp_conn->old_state_change; + sk->sk_write_space = tcp_conn->old_write_space; + sk->sk_no_check = 0; + write_unlock_bh(&sk->sk_callback_lock); +} + +/** + * iscsi_send - generic send routine + * @sk: kernel's socket + * @buf: buffer to write from + * @size: actual size to write + * @flags: socket's flags + */ +static inline int +iscsi_send(struct iscsi_conn *conn, struct iscsi_buf *buf, int size, int flags) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct socket *sk = tcp_conn->sock; + int offset = buf->sg.offset + buf->sent, res; + + /* + * if we got use_sg=0 or are sending something we kmallocd + * then we did not have to do kmap (kmap returns page_address) + * + * if we got use_sg > 0, but had to drop down, we do not + * set clustering so this should only happen for that + * slab case. + */ + if (buf->use_sendmsg) + res = sock_no_sendpage(sk, buf->sg.page, offset, size, flags); + else + res = tcp_conn->sendpage(sk, buf->sg.page, offset, size, flags); + + if (res >= 0) { + conn->txdata_octets += res; + buf->sent += res; + return res; + } + + tcp_conn->sendpage_failures_cnt++; + if (res == -EAGAIN) + res = -ENOBUFS; + else + iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); + return res; +} + +/** + * 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) + **/ +static 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_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) + **/ +static 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 inline void +iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_tcp_cmd_task *tcp_ctask) +{ + crypto_hash_init(&tcp_conn->tx_hash); + tcp_ctask->digest_count = 4; +} + +/** + * iscsi_solicit_data_cont - initialize next Data-Out + * @conn: iscsi connection + * @ctask: scsi command task + * @r2t: R2T info + * @left: bytes left to transfer + * + * Notes: + * Initialize next Data-Out within this R2T sequence and continue + * to process next Scatter-Gather element(if any) of this SCSI command. + * + * Called under connection lock. + **/ +static void +iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, + struct iscsi_r2t_info *r2t, int left) +{ + struct iscsi_data *hdr; + struct scsi_cmnd *sc = ctask->sc; + int new_offset; + + hdr = &r2t->dtask.hdr; + memset(hdr, 0, sizeof(struct iscsi_data)); + hdr->ttt = r2t->ttt; + hdr->datasn = cpu_to_be32(r2t->solicit_datasn); + r2t->solicit_datasn++; + hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; + memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun)); + hdr->itt = ctask->hdr->itt; + hdr->exp_statsn = r2t->exp_statsn; + new_offset = r2t->data_offset + r2t->sent; + hdr->offset = cpu_to_be32(new_offset); + if (left > conn->max_xmit_dlength) { + hton24(hdr->dlength, conn->max_xmit_dlength); + r2t->data_count = conn->max_xmit_dlength; + } else { + hton24(hdr->dlength, left); + r2t->data_count = left; + hdr->flags = ISCSI_FLAG_CMD_FINAL; + } + conn->dataout_pdus_cnt++; + + iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr, + sizeof(struct iscsi_hdr)); + + if (iscsi_buf_left(&r2t->sendbuf)) + return; + + if (sc->use_sg) { + iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg); + r2t->sg += 1; + } else { + iscsi_buf_init_iov(&r2t->sendbuf, + (char*)sc->request_buffer + new_offset, + r2t->data_count); + r2t->sg = NULL; + } +} + +static void iscsi_set_padding(struct iscsi_tcp_cmd_task *tcp_ctask, + unsigned long len) +{ + tcp_ctask->pad_count = len & (ISCSI_PAD_LEN - 1); + if (!tcp_ctask->pad_count) + return; + + tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count; + debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count); + tcp_ctask->xmstate |= XMSTATE_W_PAD; +} + +/** + * iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands + * @conn: iscsi connection + * @ctask: scsi command task + * @sc: scsi command + **/ +static void +iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask) +{ + struct scsi_cmnd *sc = ctask->sc; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + + BUG_ON(__kfifo_len(tcp_ctask->r2tqueue)); + + tcp_ctask->sent = 0; + tcp_ctask->sg_count = 0; + + if (sc->sc_data_direction == DMA_TO_DEVICE) { + tcp_ctask->xmstate = XMSTATE_W_HDR; + tcp_ctask->exp_r2tsn = 0; + BUG_ON(ctask->total_length == 0); + + if (sc->use_sg) { + struct scatterlist *sg = sc->request_buffer; + + iscsi_buf_init_sg(&tcp_ctask->sendbuf, sg); + tcp_ctask->sg = sg + 1; + tcp_ctask->bad_sg = sg + sc->use_sg; + } else { + iscsi_buf_init_iov(&tcp_ctask->sendbuf, + sc->request_buffer, + sc->request_bufflen); + tcp_ctask->sg = NULL; + tcp_ctask->bad_sg = NULL; + } + debug_scsi("cmd [itt 0x%x total %d imm_data %d " + "unsol count %d, unsol offset %d]\n", + ctask->itt, ctask->total_length, ctask->imm_count, + ctask->unsol_count, ctask->unsol_offset); + } else + tcp_ctask->xmstate = XMSTATE_R_HDR; + + iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr, + sizeof(struct iscsi_hdr)); +} + +/** + * iscsi_tcp_mtask_xmit - xmit management(immediate) task + * @conn: iscsi connection + * @mtask: task management task + * + * Notes: + * The function can return -EAGAIN in which case caller must + * call it again later, or recover. '0' return code means successful + * xmit. + * + * Management xmit state machine consists of two states: + * IN_PROGRESS_IMM_HEAD - PDU Header xmit in progress + * IN_PROGRESS_IMM_DATA - PDU Data xmit in progress + **/ +static int +iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask) +{ + struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data; + int rc; + + debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n", + conn->id, tcp_mtask->xmstate, mtask->itt); + + if (tcp_mtask->xmstate & XMSTATE_IMM_HDR) { + tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR; + if (mtask->data_count) + tcp_mtask->xmstate |= XMSTATE_IMM_DATA; + if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE && + conn->stop_stage != STOP_CONN_RECOVER && + conn->hdrdgst_en) + iscsi_hdr_digest(conn, &tcp_mtask->headbuf, + (u8*)tcp_mtask->hdrext); + rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf, + mtask->data_count); + if (rc) { + tcp_mtask->xmstate |= XMSTATE_IMM_HDR; + if (mtask->data_count) + tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA; + return rc; + } + } + + if (tcp_mtask->xmstate & XMSTATE_IMM_DATA) { + BUG_ON(!mtask->data_count); + tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA; + /* FIXME: implement. + * Virtual buffer could be spreaded across multiple pages... + */ + do { + int rc; + + rc = iscsi_sendpage(conn, &tcp_mtask->sendbuf, + &mtask->data_count, &tcp_mtask->sent); + if (rc) { + tcp_mtask->xmstate |= XMSTATE_IMM_DATA; + return rc; + } + } while (mtask->data_count); + } + + BUG_ON(tcp_mtask->xmstate != XMSTATE_IDLE); + if (mtask->hdr->itt == RESERVED_ITT) { + struct iscsi_session *session = conn->session; + + spin_lock_bh(&session->lock); + list_del(&conn->mtask->running); + __kfifo_put(session->mgmtpool.queue, (void*)&conn->mtask, + sizeof(void*)); + spin_unlock_bh(&session->lock); + } + return 0; +} + +static inline int +iscsi_send_read_hdr(struct iscsi_conn *conn, + struct iscsi_tcp_cmd_task *tcp_ctask) +{ + int rc; + + tcp_ctask->xmstate &= ~XMSTATE_R_HDR; + if (conn->hdrdgst_en) + iscsi_hdr_digest(conn, &tcp_ctask->headbuf, + (u8*)tcp_ctask->hdrext); + rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, 0); + if (!rc) { + BUG_ON(tcp_ctask->xmstate != XMSTATE_IDLE); + return 0; /* wait for Data-In */ + } + tcp_ctask->xmstate |= XMSTATE_R_HDR; + return rc; +} + +static inline int +iscsi_send_write_hdr(struct iscsi_conn *conn, + struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + int rc; + + tcp_ctask->xmstate &= ~XMSTATE_W_HDR; + if (conn->hdrdgst_en) + iscsi_hdr_digest(conn, &tcp_ctask->headbuf, + (u8*)tcp_ctask->hdrext); + rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count); + if (rc) { + tcp_ctask->xmstate |= XMSTATE_W_HDR; + return rc; + } + + if (ctask->imm_count) { + tcp_ctask->xmstate |= XMSTATE_IMM_DATA; + iscsi_set_padding(tcp_ctask, ctask->imm_count); + + if (ctask->conn->datadgst_en) { + iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask); + tcp_ctask->immdigest = 0; + } + } + + if (ctask->unsol_count) + tcp_ctask->xmstate |= XMSTATE_UNS_HDR | XMSTATE_UNS_INIT; + return 0; +} + +static int +iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + int sent = 0, rc; + + if (tcp_ctask->xmstate & XMSTATE_W_PAD) { + iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad, + tcp_ctask->pad_count); + if (conn->datadgst_en) + crypto_hash_update(&tcp_conn->tx_hash, + &tcp_ctask->sendbuf.sg, + tcp_ctask->sendbuf.sg.length); + } else if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_PAD)) + return 0; + + tcp_ctask->xmstate &= ~XMSTATE_W_PAD; + tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_PAD; + debug_scsi("sending %d pad bytes for itt 0x%x\n", + tcp_ctask->pad_count, ctask->itt); + rc = iscsi_sendpage(conn, &tcp_ctask->sendbuf, &tcp_ctask->pad_count, + &sent); + if (rc) { + debug_scsi("padding send failed %d\n", rc); + tcp_ctask->xmstate |= XMSTATE_W_RESEND_PAD; + } + return rc; +} + +static int +iscsi_send_digest(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, + struct iscsi_buf *buf, uint32_t *digest) +{ + struct iscsi_tcp_cmd_task *tcp_ctask; + struct iscsi_tcp_conn *tcp_conn; + int rc, sent = 0; + + if (!conn->datadgst_en) + return 0; + + tcp_ctask = ctask->dd_data; + tcp_conn = conn->dd_data; + + if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_DATA_DIGEST)) { + crypto_hash_final(&tcp_conn->tx_hash, (u8*)digest); + iscsi_buf_init_iov(buf, (char*)digest, 4); + } + tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_DATA_DIGEST; + + rc = iscsi_sendpage(conn, buf, &tcp_ctask->digest_count, &sent); + if (!rc) + debug_scsi("sent digest 0x%x for itt 0x%x\n", *digest, + ctask->itt); + else { + debug_scsi("sending digest 0x%x failed for itt 0x%x!\n", + *digest, ctask->itt); + tcp_ctask->xmstate |= XMSTATE_W_RESEND_DATA_DIGEST; + } + return rc; +} + +static int +iscsi_send_data(struct iscsi_cmd_task *ctask, struct iscsi_buf *sendbuf, + struct scatterlist **sg, int *sent, int *count, + struct iscsi_buf *digestbuf, uint32_t *digest) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_conn *conn = ctask->conn; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + int rc, buf_sent, offset; + + while (*count) { + buf_sent = 0; + offset = sendbuf->sent; + + rc = iscsi_sendpage(conn, sendbuf, count, &buf_sent); + *sent = *sent + buf_sent; + if (buf_sent && conn->datadgst_en) + partial_sg_digest_update(&tcp_conn->tx_hash, + &sendbuf->sg, sendbuf->sg.offset + offset, + buf_sent); + if (!iscsi_buf_left(sendbuf) && *sg != tcp_ctask->bad_sg) { + iscsi_buf_init_sg(sendbuf, *sg); + *sg = *sg + 1; + } + + if (rc) + return rc; + } + + rc = iscsi_send_padding(conn, ctask); + if (rc) + return rc; + + return iscsi_send_digest(conn, ctask, digestbuf, digest); +} + +static int +iscsi_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_data_task *dtask; + int rc; + + tcp_ctask->xmstate |= XMSTATE_UNS_DATA; + if (tcp_ctask->xmstate & XMSTATE_UNS_INIT) { + dtask = &tcp_ctask->unsol_dtask; + + iscsi_prep_unsolicit_data_pdu(ctask, &dtask->hdr); + iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)&dtask->hdr, + sizeof(struct iscsi_hdr)); + if (conn->hdrdgst_en) + iscsi_hdr_digest(conn, &tcp_ctask->headbuf, + (u8*)dtask->hdrext); + + tcp_ctask->xmstate &= ~XMSTATE_UNS_INIT; + iscsi_set_padding(tcp_ctask, ctask->data_count); + } + + rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count); + if (rc) { + tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA; + tcp_ctask->xmstate |= XMSTATE_UNS_HDR; + return rc; + } + + if (conn->datadgst_en) { + dtask = &tcp_ctask->unsol_dtask; + iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask); + dtask->digest = 0; + } + + debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n", + ctask->itt, ctask->unsol_count, tcp_ctask->sent); + return 0; +} + +static int +iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + int rc; + + if (tcp_ctask->xmstate & XMSTATE_UNS_HDR) { + BUG_ON(!ctask->unsol_count); + tcp_ctask->xmstate &= ~XMSTATE_UNS_HDR; +send_hdr: + rc = iscsi_send_unsol_hdr(conn, ctask); + if (rc) + return rc; + } + + if (tcp_ctask->xmstate & XMSTATE_UNS_DATA) { + struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask; + int start = tcp_ctask->sent; + + rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg, + &tcp_ctask->sent, &ctask->data_count, + &dtask->digestbuf, &dtask->digest); + ctask->unsol_count -= tcp_ctask->sent - start; + if (rc) + return rc; + tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA; + /* + * Done with the Data-Out. Next, check if we need + * to send another unsolicited Data-Out. + */ + if (ctask->unsol_count) { + debug_scsi("sending more uns\n"); + tcp_ctask->xmstate |= XMSTATE_UNS_INIT; + goto send_hdr; + } + } + return 0; +} + +static int iscsi_send_sol_pdu(struct iscsi_conn *conn, + struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + struct iscsi_session *session = conn->session; + struct iscsi_r2t_info *r2t; + struct iscsi_data_task *dtask; + int left, rc; + + if (tcp_ctask->xmstate & XMSTATE_SOL_HDR) { + tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR; + tcp_ctask->xmstate |= XMSTATE_SOL_DATA; + if (!tcp_ctask->r2t) { + spin_lock_bh(&session->lock); + __kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t, + sizeof(void*)); + spin_unlock_bh(&session->lock); + } +send_hdr: + r2t = tcp_ctask->r2t; + dtask = &r2t->dtask; + + if (conn->hdrdgst_en) + iscsi_hdr_digest(conn, &r2t->headbuf, + (u8*)dtask->hdrext); + rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count); + if (rc) { + tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA; + tcp_ctask->xmstate |= XMSTATE_SOL_HDR; + return rc; + } + + if (conn->datadgst_en) { + iscsi_data_digest_init(conn->dd_data, tcp_ctask); + dtask->digest = 0; + } + + iscsi_set_padding(tcp_ctask, r2t->data_count); + debug_scsi("sol dout [dsn %d itt 0x%x dlen %d sent %d]\n", + r2t->solicit_datasn - 1, ctask->itt, r2t->data_count, + r2t->sent); + } + + if (tcp_ctask->xmstate & XMSTATE_SOL_DATA) { + r2t = tcp_ctask->r2t; + dtask = &r2t->dtask; + + rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg, + &r2t->sent, &r2t->data_count, + &dtask->digestbuf, &dtask->digest); + if (rc) + return rc; + tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA; + + /* + * Done with this Data-Out. Next, check if we have + * to send another Data-Out for this R2T. + */ + BUG_ON(r2t->data_length - r2t->sent < 0); + left = r2t->data_length - r2t->sent; + if (left) { + iscsi_solicit_data_cont(conn, ctask, r2t, left); + tcp_ctask->xmstate |= XMSTATE_SOL_DATA; + tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR; + goto send_hdr; + } + + /* + * Done with this R2T. Check if there are more + * outstanding R2Ts ready to be processed. + */ + spin_lock_bh(&session->lock); + tcp_ctask->r2t = NULL; + __kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + if (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, + sizeof(void*))) { + tcp_ctask->r2t = r2t; + tcp_ctask->xmstate |= XMSTATE_SOL_DATA; + tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR; + spin_unlock_bh(&session->lock); + goto send_hdr; + } + spin_unlock_bh(&session->lock); + } + return 0; +} + +static int +iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) +{ + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + int rc = 0; + + debug_scsi("ctask deq [cid %d xmstate %x itt 0x%x]\n", + conn->id, tcp_ctask->xmstate, ctask->itt); + + /* + * serialize with TMF AbortTask + */ + if (ctask->mtask) + return rc; + + if (tcp_ctask->xmstate & XMSTATE_R_HDR) + return iscsi_send_read_hdr(conn, tcp_ctask); + + if (tcp_ctask->xmstate & XMSTATE_W_HDR) { + rc = iscsi_send_write_hdr(conn, ctask); + if (rc) + return rc; + } + + if (tcp_ctask->xmstate & XMSTATE_IMM_DATA) { + rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg, + &tcp_ctask->sent, &ctask->imm_count, + &tcp_ctask->immbuf, &tcp_ctask->immdigest); + if (rc) + return rc; + tcp_ctask->xmstate &= ~XMSTATE_IMM_DATA; + } + + rc = iscsi_send_unsol_pdu(conn, ctask); + if (rc) + return rc; + + rc = iscsi_send_sol_pdu(conn, ctask); + if (rc) + return rc; + + return rc; +} + +static struct iscsi_cls_conn * +iscsi_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; + + cls_conn = iscsi_conn_setup(cls_session, conn_idx); + if (!cls_conn) + return NULL; + conn = cls_conn->dd_data; + /* + * due to strange issues with iser these are not set + * in iscsi_conn_setup + */ + conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; + + tcp_conn = kzalloc(sizeof(*tcp_conn), GFP_KERNEL); + if (!tcp_conn) + goto tcp_conn_alloc_fail; + + conn->dd_data = tcp_conn; + tcp_conn->iscsi_conn = conn; + tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; + /* initial operational parameters */ + tcp_conn->hdr_size = sizeof(struct iscsi_hdr); + + tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0, + CRYPTO_ALG_ASYNC); + tcp_conn->tx_hash.flags = 0; + if (IS_ERR(tcp_conn->tx_hash.tfm)) { + printk(KERN_ERR "Could not create connection due to crc32c " + "loading error %ld. Make sure the crc32c module is " + "built as a module or into the kernel\n", + PTR_ERR(tcp_conn->tx_hash.tfm)); + goto free_tcp_conn; + } + + tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0, + CRYPTO_ALG_ASYNC); + tcp_conn->rx_hash.flags = 0; + if (IS_ERR(tcp_conn->rx_hash.tfm)) { + printk(KERN_ERR "Could not create connection due to crc32c " + "loading error %ld. Make sure the crc32c module is " + "built as a module or into the kernel\n", + PTR_ERR(tcp_conn->rx_hash.tfm)); + goto free_tx_tfm; + } + + return cls_conn; + +free_tx_tfm: + crypto_free_hash(tcp_conn->tx_hash.tfm); +free_tcp_conn: + kfree(tcp_conn); +tcp_conn_alloc_fail: + iscsi_conn_teardown(cls_conn); + return NULL; +} + +static void +iscsi_tcp_release_conn(struct iscsi_conn *conn) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + + if (!tcp_conn->sock) + return; + + sock_hold(tcp_conn->sock->sk); + iscsi_conn_restore_callbacks(tcp_conn); + sock_put(tcp_conn->sock->sk); + + sock_release(tcp_conn->sock); + tcp_conn->sock = NULL; + conn->recv_lock = NULL; +} + +static void +iscsi_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; + + iscsi_tcp_release_conn(conn); + iscsi_conn_teardown(cls_conn); + + if (tcp_conn->tx_hash.tfm) + crypto_free_hash(tcp_conn->tx_hash.tfm); + if (tcp_conn->rx_hash.tfm) + crypto_free_hash(tcp_conn->rx_hash.tfm); + + kfree(tcp_conn); +} + +static void +iscsi_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; + + iscsi_conn_stop(cls_conn, flag); + iscsi_tcp_release_conn(conn); + tcp_conn->hdr_size = sizeof(struct iscsi_hdr); +} + +static int +iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session, + struct iscsi_cls_conn *cls_conn, uint64_t transport_eph, + int is_leading) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct sock *sk; + struct socket *sock; + int err; + + /* lookup for existing socket */ + sock = sockfd_lookup((int)transport_eph, &err); + if (!sock) { + printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err); + return -EEXIST; + } + + err = iscsi_conn_bind(cls_session, cls_conn, is_leading); + if (err) + return err; + + /* bind iSCSI connection and socket */ + tcp_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; + + /* FIXME: disable Nagle's algorithm */ + + /* + * Intercept TCP callbacks for sendfile like receive + * processing. + */ + conn->recv_lock = &sk->sk_callback_lock; + iscsi_conn_set_callbacks(conn); + tcp_conn->sendpage = tcp_conn->sock->ops->sendpage; + /* + * set receive state machine into initial state + */ + tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; + + return 0; +} + +/* called with host lock */ +static void +iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask, + char *data, uint32_t data_size) +{ + struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data; + + iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr, + sizeof(struct iscsi_hdr)); + tcp_mtask->xmstate = XMSTATE_IMM_HDR; + tcp_mtask->sent = 0; + + if (mtask->data_count) + iscsi_buf_init_iov(&tcp_mtask->sendbuf, (char*)mtask->data, + mtask->data_count); +} + +static int +iscsi_r2tpool_alloc(struct iscsi_session *session) +{ + int i; + int cmd_i; + + /* + * initialize per-task: R2T pool and xmit queue + */ + for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { + struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + + /* + * pre-allocated x4 as much r2ts to handle race when + * target acks DataOut faster than we data_xmit() queues + * could replenish r2tqueue. + */ + + /* R2T pool */ + if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4, + (void***)&tcp_ctask->r2ts, + sizeof(struct iscsi_r2t_info))) { + goto r2t_alloc_fail; + } + + /* R2T xmit queue */ + tcp_ctask->r2tqueue = kfifo_alloc( + session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL); + if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) { + iscsi_pool_free(&tcp_ctask->r2tpool, + (void**)tcp_ctask->r2ts); + goto r2t_alloc_fail; + } + } + + return 0; + +r2t_alloc_fail: + for (i = 0; i < cmd_i; i++) { + struct iscsi_cmd_task *ctask = session->cmds[i]; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + + kfifo_free(tcp_ctask->r2tqueue); + iscsi_pool_free(&tcp_ctask->r2tpool, + (void**)tcp_ctask->r2ts); + } + return -ENOMEM; +} + +static void +iscsi_r2tpool_free(struct iscsi_session *session) +{ + int i; + + for (i = 0; i < session->cmds_max; i++) { + struct iscsi_cmd_task *ctask = session->cmds[i]; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + + kfifo_free(tcp_ctask->r2tqueue); + iscsi_pool_free(&tcp_ctask->r2tpool, + (void**)tcp_ctask->r2ts); + } +} + +static int +iscsi_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; + int value; + + switch(param) { + case ISCSI_PARAM_HDRDGST_EN: + iscsi_set_param(cls_conn, param, buf, buflen); + tcp_conn->hdr_size = sizeof(struct iscsi_hdr); + if (conn->hdrdgst_en) + tcp_conn->hdr_size += sizeof(__u32); + break; + case ISCSI_PARAM_DATADGST_EN: + iscsi_set_param(cls_conn, param, buf, buflen); + tcp_conn->sendpage = conn->datadgst_en ? + sock_no_sendpage : tcp_conn->sock->ops->sendpage; + break; + case ISCSI_PARAM_MAX_R2T: + sscanf(buf, "%d", &value); + if (session->max_r2t == roundup_pow_of_two(value)) + break; + iscsi_r2tpool_free(session); + iscsi_set_param(cls_conn, param, buf, buflen); + if (session->max_r2t & (session->max_r2t - 1)) + session->max_r2t = roundup_pow_of_two(session->max_r2t); + if (iscsi_r2tpool_alloc(session)) + return -ENOMEM; + break; + default: + return iscsi_set_param(cls_conn, param, buf, buflen); + } + + return 0; +} + +static int +iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param param, char *buf) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct inet_sock *inet; + struct ipv6_pinfo *np; + struct sock *sk; + int len; + + switch(param) { + case ISCSI_PARAM_CONN_PORT: + mutex_lock(&conn->xmitmutex); + if (!tcp_conn->sock) { + mutex_unlock(&conn->xmitmutex); + return -EINVAL; + } + + inet = inet_sk(tcp_conn->sock->sk); + len = sprintf(buf, "%hu\n", be16_to_cpu(inet->dport)); + mutex_unlock(&conn->xmitmutex); + break; + case ISCSI_PARAM_CONN_ADDRESS: + mutex_lock(&conn->xmitmutex); + if (!tcp_conn->sock) { + mutex_unlock(&conn->xmitmutex); + return -EINVAL; + } + + sk = tcp_conn->sock->sk; + if (sk->sk_family == PF_INET) { + inet = inet_sk(sk); + len = sprintf(buf, NIPQUAD_FMT "\n", + NIPQUAD(inet->daddr)); + } else { + np = inet6_sk(sk); + len = sprintf(buf, NIP6_FMT "\n", NIP6(np->daddr)); + } + mutex_unlock(&conn->xmitmutex); + break; + default: + return iscsi_conn_get_param(cls_conn, param, buf); + } + + return len; +} + +static void +iscsi_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; + + stats->txdata_octets = conn->txdata_octets; + stats->rxdata_octets = conn->rxdata_octets; + stats->scsicmd_pdus = conn->scsicmd_pdus_cnt; + stats->dataout_pdus = conn->dataout_pdus_cnt; + stats->scsirsp_pdus = conn->scsirsp_pdus_cnt; + stats->datain_pdus = conn->datain_pdus_cnt; + stats->r2t_pdus = conn->r2t_pdus_cnt; + stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt; + stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt; + stats->custom_length = 3; + strcpy(stats->custom[0].desc, "tx_sendpage_failures"); + stats->custom[0].value = tcp_conn->sendpage_failures_cnt; + strcpy(stats->custom[1].desc, "rx_discontiguous_hdr"); + stats->custom[1].value = tcp_conn->discontiguous_hdr_cnt; + strcpy(stats->custom[2].desc, "eh_abort_cnt"); + stats->custom[2].value = conn->eh_abort_cnt; +} + +static struct iscsi_cls_session * +iscsi_tcp_session_create(struct iscsi_transport *iscsit, + struct scsi_transport_template *scsit, + uint32_t initial_cmdsn, uint32_t *hostno) +{ + struct iscsi_cls_session *cls_session; + struct iscsi_session *session; + uint32_t hn; + int cmd_i; + + cls_session = iscsi_session_setup(iscsit, scsit, + sizeof(struct iscsi_tcp_cmd_task), + sizeof(struct iscsi_tcp_mgmt_task), + initial_cmdsn, &hn); + if (!cls_session) + return NULL; + *hostno = hn; + + session = class_to_transport_session(cls_session); + for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { + struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; + struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; + + ctask->hdr = &tcp_ctask->hdr; + } + + for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) { + struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i]; + struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data; + + mtask->hdr = &tcp_mtask->hdr; + } + + if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session))) + goto r2tpool_alloc_fail; + + return cls_session; + +r2tpool_alloc_fail: + iscsi_session_teardown(cls_session); + return NULL; +} + +static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session) +{ + iscsi_r2tpool_free(class_to_transport_session(cls_session)); + iscsi_session_teardown(cls_session); +} + +static struct scsi_host_template iscsi_sht = { + .name = "iSCSI Initiator over TCP/IP", + .queuecommand = iscsi_queuecommand, + .change_queue_depth = iscsi_change_queue_depth, + .can_queue = ISCSI_XMIT_CMDS_MAX - 1, + .sg_tablesize = ISCSI_SG_TABLESIZE, + .max_sectors = 0xFFFF, + .cmd_per_lun = ISCSI_DEF_CMD_PER_LUN, + .eh_abort_handler = iscsi_eh_abort, + .eh_host_reset_handler = iscsi_eh_host_reset, + .use_clustering = DISABLE_CLUSTERING, + .proc_name = "iscsi_tcp", + .this_id = -1, +}; + +static struct iscsi_transport iscsi_tcp_transport = { + .owner = THIS_MODULE, + .name = "tcp", + .caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST + | CAP_DATADGST, + .param_mask = ISCSI_MAX_RECV_DLENGTH | + ISCSI_MAX_XMIT_DLENGTH | + ISCSI_HDRDGST_EN | + ISCSI_DATADGST_EN | + ISCSI_INITIAL_R2T_EN | + ISCSI_MAX_R2T | + ISCSI_IMM_DATA_EN | + ISCSI_FIRST_BURST | + ISCSI_MAX_BURST | + ISCSI_PDU_INORDER_EN | + ISCSI_DATASEQ_INORDER_EN | + ISCSI_ERL | + ISCSI_CONN_PORT | + ISCSI_CONN_ADDRESS | + ISCSI_EXP_STATSN | + ISCSI_PERSISTENT_PORT | + ISCSI_PERSISTENT_ADDRESS | + ISCSI_TARGET_NAME | + ISCSI_TPGT, + .host_template = &iscsi_sht, + .conndata_size = sizeof(struct iscsi_conn), + .max_conn = 1, + .max_cmd_len = ISCSI_TCP_MAX_CMD_LEN, + /* session management */ + .create_session = iscsi_tcp_session_create, + .destroy_session = iscsi_tcp_session_destroy, + /* connection management */ + .create_conn = iscsi_tcp_conn_create, + .bind_conn = iscsi_tcp_conn_bind, + .destroy_conn = iscsi_tcp_conn_destroy, + .set_param = iscsi_conn_set_param, + .get_conn_param = iscsi_tcp_conn_get_param, + .get_session_param = iscsi_session_get_param, + .start_conn = iscsi_conn_start, + .stop_conn = iscsi_tcp_conn_stop, + /* IO */ + .send_pdu = iscsi_conn_send_pdu, + .get_stats = iscsi_conn_get_stats, + .init_cmd_task = iscsi_tcp_cmd_init, + .init_mgmt_task = iscsi_tcp_mgmt_init, + .xmit_cmd_task = iscsi_tcp_ctask_xmit, + .xmit_mgmt_task = iscsi_tcp_mtask_xmit, + .cleanup_cmd_task = iscsi_tcp_cleanup_ctask, + /* recovery */ + .session_recovery_timedout = iscsi_session_recovery_timedout, +}; + +static int __init +iscsi_tcp_init(void) +{ + if (iscsi_max_lun < 1) { + printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n", + iscsi_max_lun); + return -EINVAL; + } + iscsi_tcp_transport.max_lun = iscsi_max_lun; + + if (!iscsi_register_transport(&iscsi_tcp_transport)) + return -ENODEV; + + return 0; +} + +static void __exit +iscsi_tcp_exit(void) +{ + iscsi_unregister_transport(&iscsi_tcp_transport); +} + +module_init(iscsi_tcp_init); +module_exit(iscsi_tcp_exit); Index: kernel/libiscsi.c =================================================================== --- kernel/libiscsi.c (revision 0) +++ kernel/libiscsi.c (revision 779) @@ -0,0 +1,1995 @@ +/* + * iSCSI lib functions + * + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004 - 2006 Mike Christie + * Copyright (C) 2004 - 2005 Dmitry Yusupov + * Copyright (C) 2004 - 2005 Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi_proto.h" +#include "scsi_transport_iscsi.h" +#include "libiscsi.h" + +struct iscsi_session * +class_to_transport_session(struct iscsi_cls_session *cls_session) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); + return iscsi_hostdata(shost->hostdata); +} +EXPORT_SYMBOL_GPL(class_to_transport_session); + +#define INVALID_SN_DELTA 0xffff + +int +iscsi_check_assign_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr) +{ + uint32_t max_cmdsn = be32_to_cpu(hdr->max_cmdsn); + uint32_t exp_cmdsn = be32_to_cpu(hdr->exp_cmdsn); + + if (max_cmdsn < exp_cmdsn -1 && + max_cmdsn > exp_cmdsn - INVALID_SN_DELTA) + return ISCSI_ERR_MAX_CMDSN; + if (max_cmdsn > session->max_cmdsn || + max_cmdsn < session->max_cmdsn - INVALID_SN_DELTA) + session->max_cmdsn = max_cmdsn; + if (exp_cmdsn > session->exp_cmdsn || + exp_cmdsn < session->exp_cmdsn - INVALID_SN_DELTA) + session->exp_cmdsn = exp_cmdsn; + + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_check_assign_cmdsn); + +void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask, + struct iscsi_data *hdr) +{ + struct iscsi_conn *conn = ctask->conn; + + memset(hdr, 0, sizeof(struct iscsi_data)); + hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + hdr->datasn = cpu_to_be32(ctask->unsol_datasn); + ctask->unsol_datasn++; + hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; + memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun)); + + hdr->itt = ctask->hdr->itt; + hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); + hdr->offset = cpu_to_be32(ctask->unsol_offset); + + if (ctask->unsol_count > conn->max_xmit_dlength) { + hton24(hdr->dlength, conn->max_xmit_dlength); + ctask->data_count = conn->max_xmit_dlength; + ctask->unsol_offset += ctask->data_count; + hdr->flags = 0; + } else { + hton24(hdr->dlength, ctask->unsol_count); + ctask->data_count = ctask->unsol_count; + hdr->flags = ISCSI_FLAG_CMD_FINAL; + } +} +EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu); + +/** + * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu + * @ctask: iscsi cmd task + * + * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set + * fields like dlength or final based on how much data it sends + */ +static void iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask) +{ + struct iscsi_conn *conn = ctask->conn; + struct iscsi_session *session = conn->session; + struct iscsi_cmd *hdr = ctask->hdr; + struct scsi_cmnd *sc = ctask->sc; + + hdr->opcode = ISCSI_OP_SCSI_CMD; + hdr->flags = ISCSI_ATTR_SIMPLE; + int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun); + hdr->itt = build_itt(ctask->itt, conn->id, session->age); + hdr->data_length = cpu_to_be32(sc->request_bufflen); + hdr->cmdsn = cpu_to_be32(session->cmdsn); + session->cmdsn++; + hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); + memcpy(hdr->cdb, sc->cmnd, sc->cmd_len); + memset(&hdr->cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len); + + ctask->data_count = 0; + if (sc->sc_data_direction == DMA_TO_DEVICE) { + hdr->flags |= ISCSI_FLAG_CMD_WRITE; + /* + * Write counters: + * + * imm_count bytes to be sent right after + * SCSI PDU Header + * + * unsol_count bytes(as Data-Out) to be sent + * without R2T ack right after + * immediate data + * + * r2t_data_count bytes to be sent via R2T ack's + * + * pad_count bytes to be sent as zero-padding + */ + ctask->imm_count = 0; + ctask->unsol_count = 0; + ctask->unsol_offset = 0; + ctask->unsol_datasn = 0; + + if (session->imm_data_en) { + if (ctask->total_length >= session->first_burst) + ctask->imm_count = min(session->first_burst, + conn->max_xmit_dlength); + else + ctask->imm_count = min(ctask->total_length, + conn->max_xmit_dlength); + hton24(ctask->hdr->dlength, ctask->imm_count); + } else + zero_data(ctask->hdr->dlength); + + if (!session->initial_r2t_en) { + ctask->unsol_count = min(session->first_burst, + ctask->total_length) - ctask->imm_count; + ctask->unsol_offset = ctask->imm_count; + } + + if (!ctask->unsol_count) + /* No unsolicit Data-Out's */ + ctask->hdr->flags |= ISCSI_FLAG_CMD_FINAL; + } else { + ctask->datasn = 0; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + zero_data(hdr->dlength); + + if (sc->sc_data_direction == DMA_FROM_DEVICE) + hdr->flags |= ISCSI_FLAG_CMD_READ; + } + + conn->scsicmd_pdus_cnt++; +} +EXPORT_SYMBOL_GPL(iscsi_prep_scsi_cmd_pdu); + +/** + * iscsi_complete_command - return command back to scsi-ml + * @ctask: iscsi cmd task + * + * Must be called with session lock. + * This function returns the scsi command to scsi-ml and returns + * the cmd task to the pool of available cmd tasks. + */ +static void iscsi_complete_command(struct iscsi_cmd_task *ctask) +{ + struct iscsi_session *session = ctask->conn->session; + struct scsi_cmnd *sc = ctask->sc; + + ctask->state = ISCSI_TASK_COMPLETED; + ctask->sc = NULL; + /* SCSI eh reuses commands to verify us */ + sc->SCp.ptr = NULL; + list_del_init(&ctask->running); + __kfifo_put(session->cmdpool.queue, (void*)&ctask, sizeof(void*)); + sc->scsi_done(sc); +} + +static void __iscsi_get_ctask(struct iscsi_cmd_task *ctask) +{ + atomic_inc(&ctask->refcount); +} + +static void iscsi_get_ctask(struct iscsi_cmd_task *ctask) +{ + spin_lock_bh(&ctask->conn->session->lock); + __iscsi_get_ctask(ctask); + spin_unlock_bh(&ctask->conn->session->lock); +} + +static void __iscsi_put_ctask(struct iscsi_cmd_task *ctask) +{ + if (atomic_dec_and_test(&ctask->refcount)) + iscsi_complete_command(ctask); +} + +static void iscsi_put_ctask(struct iscsi_cmd_task *ctask) +{ + spin_lock_bh(&ctask->conn->session->lock); + __iscsi_put_ctask(ctask); + spin_unlock_bh(&ctask->conn->session->lock); +} + +/** + * iscsi_cmd_rsp - SCSI Command Response processing + * @conn: iscsi connection + * @hdr: iscsi header + * @ctask: scsi command task + * @data: cmd data buffer + * @datalen: len of buffer + * + * iscsi_cmd_rsp sets up the scsi_cmnd fields based on the PDU and + * then completes the command and task. + **/ +static int iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + struct iscsi_cmd_task *ctask, char *data, + int datalen) +{ + int rc; + struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr; + struct iscsi_session *session = conn->session; + struct scsi_cmnd *sc = ctask->sc; + + rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr); + if (rc) { + sc->result = DID_ERROR << 16; + goto out; + } + + conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1; + + sc->result = (DID_OK << 16) | rhdr->cmd_status; + + if (rhdr->response != ISCSI_STATUS_CMD_COMPLETED) { + sc->result = DID_ERROR << 16; + goto out; + } + + if (rhdr->cmd_status == SAM_STAT_CHECK_CONDITION) { + uint16_t senselen; + + if (datalen < 2) { +invalid_datalen: + printk(KERN_ERR "iscsi: Got CHECK_CONDITION but " + "invalid data buffer size of %d\n", datalen); + sc->result = DID_BAD_TARGET << 16; + goto out; + } + + senselen = be16_to_cpu(get_unaligned((__be16 *) data)); + if (datalen < senselen) + goto invalid_datalen; + + memcpy(sc->sense_buffer, data + 2, + min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE)); + debug_scsi("copied %d bytes of sense\n", + min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE)); + } + + if (sc->sc_data_direction == DMA_TO_DEVICE) + goto out; + + if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) { + int res_count = be32_to_cpu(rhdr->residual_count); + + if (res_count > 0 && res_count <= sc->request_bufflen) + sc->resid = res_count; + else + sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status; + } else if (rhdr->flags & ISCSI_FLAG_CMD_BIDI_UNDERFLOW) + sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status; + else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW) + sc->resid = be32_to_cpu(rhdr->residual_count); + +out: + debug_scsi("done [sc %lx res %d itt 0x%x]\n", + (long)sc, sc->result, ctask->itt); + conn->scsirsp_pdus_cnt++; + + __iscsi_put_ctask(ctask); + return rc; +} + +static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr) +{ + struct iscsi_tm_rsp *tmf = (struct iscsi_tm_rsp *)hdr; + + conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; + conn->tmfrsp_pdus_cnt++; + + if (conn->tmabort_state != TMABORT_INITIAL) + return; + + if (tmf->response == ISCSI_TMF_RSP_COMPLETE) + conn->tmabort_state = TMABORT_SUCCESS; + else if (tmf->response == ISCSI_TMF_RSP_NO_TASK) + conn->tmabort_state = TMABORT_NOT_FOUND; + else + conn->tmabort_state = TMABORT_FAILED; + wake_up(&conn->ehwait); +} + +static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + char *data, int datalen) +{ + struct iscsi_reject *reject = (struct iscsi_reject *)hdr; + struct iscsi_hdr rejected_pdu; + uint32_t itt; + + conn->exp_statsn = be32_to_cpu(reject->statsn) + 1; + + if (reject->reason == ISCSI_REASON_DATA_DIGEST_ERROR) { + if (ntoh24(reject->dlength) > datalen) + return ISCSI_ERR_PROTO; + + if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) { + memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr)); + itt = get_itt(rejected_pdu.itt); + printk(KERN_ERR "itt 0x%x had pdu (op 0x%x) rejected " + "due to DataDigest error.\n", itt, + rejected_pdu.opcode); + } + } + return 0; +} + +/** + * __iscsi_complete_pdu - complete pdu + * @conn: iscsi conn + * @hdr: iscsi header + * @data: data buffer + * @datalen: len of data buffer + * + * Completes pdu processing by freeing any resources allocated at + * queuecommand or send generic. session lock must be held and verify + * itt must have been called. + */ +int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + char *data, int datalen) +{ + struct iscsi_session *session = conn->session; + int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0; + struct iscsi_cmd_task *ctask; + struct iscsi_mgmt_task *mtask; + uint32_t itt; + + if (hdr->itt != RESERVED_ITT) + itt = get_itt(hdr->itt); + else + itt = ~0U; + + if (itt < session->cmds_max) { + ctask = session->cmds[itt]; + + debug_scsi("cmdrsp [op 0x%x cid %d itt 0x%x len %d]\n", + opcode, conn->id, ctask->itt, datalen); + + switch(opcode) { + case ISCSI_OP_SCSI_CMD_RSP: + BUG_ON((void*)ctask != ctask->sc->SCp.ptr); + rc = iscsi_scsi_cmd_rsp(conn, hdr, ctask, data, + datalen); + break; + case ISCSI_OP_SCSI_DATA_IN: + BUG_ON((void*)ctask != ctask->sc->SCp.ptr); + if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { + conn->scsirsp_pdus_cnt++; + __iscsi_put_ctask(ctask); + } + break; + case ISCSI_OP_R2T: + /* LLD handles this for now */ + break; + default: + rc = ISCSI_ERR_BAD_OPCODE; + break; + } + } else if (itt >= ISCSI_MGMT_ITT_OFFSET && + itt < ISCSI_MGMT_ITT_OFFSET + session->mgmtpool_max) { + mtask = session->mgmt_cmds[itt - ISCSI_MGMT_ITT_OFFSET]; + + debug_scsi("immrsp [op 0x%x cid %d itt 0x%x len %d]\n", + opcode, conn->id, mtask->itt, datalen); + + rc = iscsi_check_assign_cmdsn(session, + (struct iscsi_nopin*)hdr); + if (rc) + goto done; + + switch(opcode) { + case ISCSI_OP_LOGOUT_RSP: + if (datalen) { + rc = ISCSI_ERR_PROTO; + break; + } + conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; + /* fall through */ + case ISCSI_OP_LOGIN_RSP: + case ISCSI_OP_TEXT_RSP: + /* + * login related PDU's exp_statsn is handled in + * userspace + */ + if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) + rc = ISCSI_ERR_CONN_FAILED; + list_del(&mtask->running); + if (conn->login_mtask != mtask) + __kfifo_put(session->mgmtpool.queue, + (void*)&mtask, sizeof(void*)); + break; + case ISCSI_OP_SCSI_TMFUNC_RSP: + if (datalen) { + rc = ISCSI_ERR_PROTO; + break; + } + + iscsi_tmf_rsp(conn, hdr); + break; + case ISCSI_OP_NOOP_IN: + if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) { + rc = ISCSI_ERR_PROTO; + break; + } + conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; + + if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) + rc = ISCSI_ERR_CONN_FAILED; + list_del(&mtask->running); + if (conn->login_mtask != mtask) + __kfifo_put(session->mgmtpool.queue, + (void*)&mtask, sizeof(void*)); + break; + default: + rc = ISCSI_ERR_BAD_OPCODE; + break; + } + } else if (itt == ~0U) { + rc = iscsi_check_assign_cmdsn(session, + (struct iscsi_nopin*)hdr); + if (rc) + goto done; + + switch(opcode) { + case ISCSI_OP_NOOP_IN: + if (datalen) { + rc = ISCSI_ERR_PROTO; + break; + } + + if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) + break; + + if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0)) + rc = ISCSI_ERR_CONN_FAILED; + break; + case ISCSI_OP_REJECT: + rc = iscsi_handle_reject(conn, hdr, data, datalen); + break; + case ISCSI_OP_ASYNC_EVENT: + conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; + if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) + rc = ISCSI_ERR_CONN_FAILED; + break; + default: + rc = ISCSI_ERR_BAD_OPCODE; + break; + } + } else + rc = ISCSI_ERR_BAD_ITT; + +done: + return rc; +} +EXPORT_SYMBOL_GPL(__iscsi_complete_pdu); + +int iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + char *data, int datalen) +{ + int rc; + + spin_lock(&conn->session->lock); + rc = __iscsi_complete_pdu(conn, hdr, data, datalen); + spin_unlock(&conn->session->lock); + return rc; +} +EXPORT_SYMBOL_GPL(iscsi_complete_pdu); + +/* verify itt (itt encoding: age+cid+itt) */ +int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + uint32_t *ret_itt) +{ + struct iscsi_session *session = conn->session; + struct iscsi_cmd_task *ctask; + uint32_t itt; + + if (hdr->itt != RESERVED_ITT) { + if (((__force u32)hdr->itt & ISCSI_AGE_MASK) != + (session->age << ISCSI_AGE_SHIFT)) { + printk(KERN_ERR "iscsi: received itt %x expected " + "session age (%x)\n", (__force u32)hdr->itt, + session->age & ISCSI_AGE_MASK); + return ISCSI_ERR_BAD_ITT; + } + + if (((__force u32)hdr->itt & ISCSI_CID_MASK) != + (conn->id << ISCSI_CID_SHIFT)) { + printk(KERN_ERR "iscsi: received itt %x, expected " + "CID (%x)\n", (__force u32)hdr->itt, conn->id); + return ISCSI_ERR_BAD_ITT; + } + itt = get_itt(hdr->itt); + } else + itt = ~0U; + + if (itt < session->cmds_max) { + ctask = session->cmds[itt]; + + if (!ctask->sc) { + printk(KERN_INFO "iscsi: dropping ctask with " + "itt 0x%x\n", ctask->itt); + /* force drop */ + return ISCSI_ERR_NO_SCSI_CMD; + } + + if (ctask->sc->SCp.phase != session->age) { + printk(KERN_ERR "iscsi: ctask's session age %d, " + "expected %d\n", ctask->sc->SCp.phase, + session->age); + return ISCSI_ERR_SESSION_FAILED; + } + } + + *ret_itt = itt; + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_verify_itt); + +void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err) +{ + struct iscsi_session *session = conn->session; + unsigned long flags; + + spin_lock_irqsave(&session->lock, flags); + if (session->state == ISCSI_STATE_FAILED) { + spin_unlock_irqrestore(&session->lock, flags); + return; + } + + if (conn->stop_stage == 0) + session->state = ISCSI_STATE_FAILED; + spin_unlock_irqrestore(&session->lock, flags); + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); + iscsi_conn_error(conn->cls_conn, err); +} +EXPORT_SYMBOL_GPL(iscsi_conn_failure); + +static int iscsi_xmit_mtask(struct iscsi_conn *conn) +{ + struct iscsi_hdr *hdr = conn->mtask->hdr; + int rc, was_logout = 0; + + if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT) { + conn->session->state = ISCSI_STATE_IN_RECOVERY; + iscsi_block_session(session_to_cls(conn->session)); + was_logout = 1; + } + rc = conn->session->tt->xmit_mgmt_task(conn, conn->mtask); + if (rc) + return rc; + + /* done with this in-progress mtask */ + conn->mtask = NULL; + + if (was_logout) { + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); + return -ENODATA; + } + return 0; +} + +/** + * iscsi_data_xmit - xmit any command into the scheduled connection + * @conn: iscsi connection + * + * Notes: + * The function can return -EAGAIN in which case the caller must + * re-schedule it again later or recover. '0' return code means + * successful xmit. + **/ +static int iscsi_data_xmit(struct iscsi_conn *conn) +{ + struct iscsi_transport *tt; + int rc = 0; + + if (unlikely(conn->suspend_tx)) { + debug_scsi("conn %d Tx suspended!\n", conn->id); + return -ENODATA; + } + tt = conn->session->tt; + + /* + * Transmit in the following order: + * + * 1) un-finished xmit (ctask or mtask) + * 2) immediate control PDUs + * 3) write data + * 4) SCSI commands + * 5) non-immediate control PDUs + * + * No need to lock around __kfifo_get as long as + * there's one producer and one consumer. + */ + + BUG_ON(conn->ctask && conn->mtask); + + if (conn->ctask) { + iscsi_get_ctask(conn->ctask); + rc = tt->xmit_cmd_task(conn, conn->ctask); + iscsi_put_ctask(conn->ctask); + if (rc) + goto again; + /* done with this in-progress ctask */ + conn->ctask = NULL; + } + if (conn->mtask) { + rc = iscsi_xmit_mtask(conn); + if (rc) + goto again; + } + + /* process immediate first */ + if (unlikely(__kfifo_len(conn->immqueue))) { + while (__kfifo_get(conn->immqueue, (void*)&conn->mtask, + sizeof(void*))) { + spin_lock_bh(&conn->session->lock); + list_add_tail(&conn->mtask->running, + &conn->mgmt_run_list); + spin_unlock_bh(&conn->session->lock); + rc = iscsi_xmit_mtask(conn); + if (rc) + goto again; + } + } + + /* process command queue */ + spin_lock_bh(&conn->session->lock); + while (!list_empty(&conn->xmitqueue)) { + /* + * iscsi tcp may readd the task to the xmitqueue to send + * write data + */ + conn->ctask = list_entry(conn->xmitqueue.next, + struct iscsi_cmd_task, running); + conn->ctask->state = ISCSI_TASK_RUNNING; + list_move_tail(conn->xmitqueue.next, &conn->run_list); + __iscsi_get_ctask(conn->ctask); + spin_unlock_bh(&conn->session->lock); + + rc = tt->xmit_cmd_task(conn, conn->ctask); + + spin_lock_bh(&conn->session->lock); + __iscsi_put_ctask(conn->ctask); + if (rc) { + spin_unlock_bh(&conn->session->lock); + goto again; + } + } + spin_unlock_bh(&conn->session->lock); + /* done with this ctask */ + conn->ctask = NULL; + + /* process the rest control plane PDUs, if any */ + if (unlikely(__kfifo_len(conn->mgmtqueue))) { + while (__kfifo_get(conn->mgmtqueue, (void*)&conn->mtask, + sizeof(void*))) { + spin_lock_bh(&conn->session->lock); + list_add_tail(&conn->mtask->running, + &conn->mgmt_run_list); + spin_unlock_bh(&conn->session->lock); + rc = iscsi_xmit_mtask(conn); + if (rc) + goto again; + } + } + + return -ENODATA; + +again: + if (unlikely(conn->suspend_tx)) + return -ENODATA; + + return rc; +} + +static void iscsi_xmitworker(struct work_struct *work) +{ + struct iscsi_conn *conn = + container_of(work, struct iscsi_conn, xmitwork); + int rc; + /* + * serialize Xmit worker on a per-connection basis. + */ + mutex_lock(&conn->xmitmutex); + do { + rc = iscsi_data_xmit(conn); + } while (rc >= 0 || rc == -EAGAIN); + mutex_unlock(&conn->xmitmutex); +} + +enum { + FAILURE_BAD_HOST = 1, + FAILURE_SESSION_FAILED, + FAILURE_SESSION_FREED, + FAILURE_WINDOW_CLOSED, + FAILURE_OOM, + FAILURE_SESSION_TERMINATE, + FAILURE_SESSION_IN_RECOVERY, + FAILURE_SESSION_RECOVERY_TIMEOUT, +}; + +int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *)) +{ + struct Scsi_Host *host; + int reason = 0; + struct iscsi_session *session; + struct iscsi_conn *conn; + struct iscsi_cmd_task *ctask = NULL; + + sc->scsi_done = done; + sc->result = 0; + sc->SCp.ptr = NULL; + + host = sc->device->host; + session = iscsi_hostdata(host->hostdata); + + spin_lock(&session->lock); + + /* + * ISCSI_STATE_FAILED is a temp. state. The recovery + * code will decide what is best to do with command queued + * during this time + */ + if (session->state != ISCSI_STATE_LOGGED_IN && + session->state != ISCSI_STATE_FAILED) { + /* + * to handle the race between when we set the recovery state + * and block the session we requeue here (commands could + * be entering our queuecommand while a block is starting + * up because the block code is not locked) + */ + if (session->state == ISCSI_STATE_IN_RECOVERY) { + reason = FAILURE_SESSION_IN_RECOVERY; + goto reject; + } + + if (session->state == ISCSI_STATE_RECOVERY_FAILED) + reason = FAILURE_SESSION_RECOVERY_TIMEOUT; + else if (session->state == ISCSI_STATE_TERMINATE) + reason = FAILURE_SESSION_TERMINATE; + else + reason = FAILURE_SESSION_FREED; + goto fault; + } + + /* + * Check for iSCSI window and take care of CmdSN wrap-around + */ + if ((int)(session->max_cmdsn - session->cmdsn) < 0) { + reason = FAILURE_WINDOW_CLOSED; + goto reject; + } + + conn = session->leadconn; + if (!conn) { + reason = FAILURE_SESSION_FREED; + goto fault; + } + + if (!__kfifo_get(session->cmdpool.queue, (void*)&ctask, + sizeof(void*))) { + reason = FAILURE_OOM; + goto reject; + } + sc->SCp.phase = session->age; + sc->SCp.ptr = (char *)ctask; + + atomic_set(&ctask->refcount, 1); + ctask->state = ISCSI_TASK_PENDING; + ctask->mtask = NULL; + ctask->conn = conn; + ctask->sc = sc; + INIT_LIST_HEAD(&ctask->running); + ctask->total_length = sc->request_bufflen; + iscsi_prep_scsi_cmd_pdu(ctask); + + session->tt->init_cmd_task(ctask); + + list_add_tail(&ctask->running, &conn->xmitqueue); + debug_scsi( + "ctask enq [%s cid %d sc %p cdb 0x%x itt 0x%x len %d cmdsn %d " + "win %d]\n", + sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read", + conn->id, sc, sc->cmnd[0], ctask->itt, sc->request_bufflen, + session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1); + spin_unlock(&session->lock); + + scsi_queue_work(host, &conn->xmitwork); + return 0; + +reject: + spin_unlock(&session->lock); + debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason); + return SCSI_MLQUEUE_HOST_BUSY; + +fault: + spin_unlock(&session->lock); + printk(KERN_ERR "iscsi: cmd 0x%x is not queued (%d)\n", + sc->cmnd[0], reason); + sc->result = (DID_NO_CONNECT << 16); + sc->resid = sc->request_bufflen; + sc->scsi_done(sc); + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_queuecommand); + +int iscsi_change_queue_depth(struct scsi_device *sdev, int depth) +{ + if (depth > ISCSI_MAX_CMD_PER_LUN) + depth = ISCSI_MAX_CMD_PER_LUN; + scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth); + return sdev->queue_depth; +} +EXPORT_SYMBOL_GPL(iscsi_change_queue_depth); + +static int +iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size) +{ + struct iscsi_session *session = conn->session; + struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr; + struct iscsi_mgmt_task *mtask; + + spin_lock_bh(&session->lock); + if (session->state == ISCSI_STATE_TERMINATE) { + spin_unlock_bh(&session->lock); + return -EPERM; + } + if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) || + hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE)) + /* + * Login and Text are sent serially, in + * request-followed-by-response sequence. + * Same mtask can be used. Same ITT must be used. + * Note that login_mtask is preallocated at conn_create(). + */ + mtask = conn->login_mtask; + else { + BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE); + BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED); + + nop->exp_statsn = cpu_to_be32(conn->exp_statsn); + if (!__kfifo_get(session->mgmtpool.queue, + (void*)&mtask, sizeof(void*))) { + spin_unlock_bh(&session->lock); + return -ENOSPC; + } + } + + /* + * pre-format CmdSN for outgoing PDU. + */ + if (hdr->itt != RESERVED_ITT) { + hdr->itt = build_itt(mtask->itt, conn->id, session->age); + nop->cmdsn = cpu_to_be32(session->cmdsn); + if (conn->c_stage == ISCSI_CONN_STARTED && + !(hdr->opcode & ISCSI_OP_IMMEDIATE)) + session->cmdsn++; + } else + /* do not advance CmdSN */ + nop->cmdsn = cpu_to_be32(session->cmdsn); + + if (data_size) { + memcpy(mtask->data, data, data_size); + mtask->data_count = data_size; + } else + mtask->data_count = 0; + + INIT_LIST_HEAD(&mtask->running); + memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr)); + if (session->tt->init_mgmt_task) + session->tt->init_mgmt_task(conn, mtask, data, data_size); + spin_unlock_bh(&session->lock); + + debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n", + hdr->opcode, hdr->itt, data_size); + + /* + * since send_pdu() could be called at least from two contexts, + * we need to serialize __kfifo_put, so we don't have to take + * additional lock on fast data-path + */ + if (hdr->opcode & ISCSI_OP_IMMEDIATE) + __kfifo_put(conn->immqueue, (void*)&mtask, sizeof(void*)); + else + __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*)); + + scsi_queue_work(session->host, &conn->xmitwork); + return 0; +} + +int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + int rc; + + mutex_lock(&conn->xmitmutex); + rc = iscsi_conn_send_generic(conn, hdr, data, data_size); + mutex_unlock(&conn->xmitmutex); + + return rc; +} +EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu); + +void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session) +{ + struct iscsi_session *session = class_to_transport_session(cls_session); + struct iscsi_conn *conn = session->leadconn; + + spin_lock_bh(&session->lock); + if (session->state != ISCSI_STATE_LOGGED_IN) { + session->state = ISCSI_STATE_RECOVERY_FAILED; + if (conn) + wake_up(&conn->ehwait); + } + spin_unlock_bh(&session->lock); +} +EXPORT_SYMBOL_GPL(iscsi_session_recovery_timedout); + +int iscsi_eh_host_reset(struct scsi_cmnd *sc) +{ + struct Scsi_Host *host = sc->device->host; + struct iscsi_session *session = iscsi_hostdata(host->hostdata); + struct iscsi_conn *conn = session->leadconn; + int fail_session = 0; + + spin_lock_bh(&session->lock); + if (session->state == ISCSI_STATE_TERMINATE) { +failed: + debug_scsi("failing host reset: session terminated " + "[CID %d age %d]\n", conn->id, session->age); + spin_unlock_bh(&session->lock); + return FAILED; + } + + if (sc->SCp.phase == session->age) { + debug_scsi("failing connection CID %d due to SCSI host reset\n", + conn->id); + fail_session = 1; + } + spin_unlock_bh(&session->lock); + + /* + * we drop the lock here but the leadconn cannot be destoyed while + * we are in the scsi eh + */ + if (fail_session) + iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); + + debug_scsi("iscsi_eh_host_reset wait for relogin\n"); + wait_event_interruptible(conn->ehwait, + session->state == ISCSI_STATE_TERMINATE || + session->state == ISCSI_STATE_LOGGED_IN || + session->state == ISCSI_STATE_RECOVERY_FAILED); + if (signal_pending(current)) + flush_signals(current); + + spin_lock_bh(&session->lock); + if (session->state == ISCSI_STATE_LOGGED_IN) + printk(KERN_INFO "iscsi: host reset succeeded\n"); + else + goto failed; + spin_unlock_bh(&session->lock); + + return SUCCESS; +} +EXPORT_SYMBOL_GPL(iscsi_eh_host_reset); + +static void iscsi_tmabort_timedout(unsigned long data) +{ + struct iscsi_cmd_task *ctask = (struct iscsi_cmd_task *)data; + struct iscsi_conn *conn = ctask->conn; + struct iscsi_session *session = conn->session; + + spin_lock(&session->lock); + if (conn->tmabort_state == TMABORT_INITIAL) { + conn->tmabort_state = TMABORT_TIMEDOUT; + debug_scsi("tmabort timedout [sc %p itt 0x%x]\n", + ctask->sc, ctask->itt); + /* unblock eh_abort() */ + wake_up(&conn->ehwait); + } + spin_unlock(&session->lock); +} + +/* must be called with the mutex lock */ +static int iscsi_exec_abort_task(struct scsi_cmnd *sc, + struct iscsi_cmd_task *ctask) +{ + struct iscsi_conn *conn = ctask->conn; + struct iscsi_session *session = conn->session; + struct iscsi_tm *hdr = &conn->tmhdr; + int rc; + + /* + * ctask timed out but session is OK requests must be serialized. + */ + memset(hdr, 0, sizeof(struct iscsi_tm)); + hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE; + hdr->flags = ISCSI_TM_FUNC_ABORT_TASK; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun)); + hdr->rtt = ctask->hdr->itt; + hdr->refcmdsn = ctask->hdr->cmdsn; + + rc = iscsi_conn_send_generic(conn, (struct iscsi_hdr *)hdr, + NULL, 0); + if (rc) { + iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); + debug_scsi("abort sent failure [itt 0x%x] %d\n", ctask->itt, + rc); + return rc; + } + + debug_scsi("abort sent [itt 0x%x]\n", ctask->itt); + + spin_lock_bh(&session->lock); + ctask->mtask = (struct iscsi_mgmt_task *) + session->mgmt_cmds[get_itt(hdr->itt) - + ISCSI_MGMT_ITT_OFFSET]; + + if (conn->tmabort_state == TMABORT_INITIAL) { + conn->tmfcmd_pdus_cnt++; + conn->tmabort_timer.expires = 10*HZ + jiffies; + conn->tmabort_timer.function = iscsi_tmabort_timedout; + conn->tmabort_timer.data = (unsigned long)ctask; + add_timer(&conn->tmabort_timer); + debug_scsi("abort set timeout [itt 0x%x]\n", ctask->itt); + } + spin_unlock_bh(&session->lock); + mutex_unlock(&conn->xmitmutex); + + /* + * block eh thread until: + * + * 1) abort response + * 2) abort timeout + * 3) session is terminated or restarted or userspace has + * given up on recovery + */ + wait_event_interruptible(conn->ehwait, + sc->SCp.phase != session->age || + session->state != ISCSI_STATE_LOGGED_IN || + conn->tmabort_state != TMABORT_INITIAL); + if (signal_pending(current)) + flush_signals(current); + del_timer_sync(&conn->tmabort_timer); + + mutex_lock(&conn->xmitmutex); + return 0; +} + +/* + * xmit mutex and session lock must be held + */ +static struct iscsi_mgmt_task * +iscsi_remove_mgmt_task(struct kfifo *fifo, uint32_t itt) +{ + int i, nr_tasks = __kfifo_len(fifo) / sizeof(void*); + struct iscsi_mgmt_task *task; + + debug_scsi("searching %d tasks\n", nr_tasks); + + for (i = 0; i < nr_tasks; i++) { + __kfifo_get(fifo, (void*)&task, sizeof(void*)); + debug_scsi("check task %u\n", task->itt); + + if (task->itt == itt) { + debug_scsi("matched task\n"); + return task; + } + + __kfifo_put(fifo, (void*)&task, sizeof(void*)); + } + return NULL; +} + +static int iscsi_ctask_mtask_cleanup(struct iscsi_cmd_task *ctask) +{ + struct iscsi_conn *conn = ctask->conn; + struct iscsi_session *session = conn->session; + + if (!ctask->mtask) + return -EINVAL; + + if (!iscsi_remove_mgmt_task(conn->immqueue, ctask->mtask->itt)) + list_del(&ctask->mtask->running); + __kfifo_put(session->mgmtpool.queue, (void*)&ctask->mtask, + sizeof(void*)); + ctask->mtask = NULL; + return 0; +} + +/* + * session lock and xmitmutex must be held + */ +static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, + int err) +{ + struct scsi_cmnd *sc; + + sc = ctask->sc; + if (!sc) + return; + + conn->session->tt->cleanup_cmd_task(conn, ctask); + iscsi_ctask_mtask_cleanup(ctask); + + sc->result = err; + sc->resid = sc->request_bufflen; + /* release ref from queuecommand */ + __iscsi_put_ctask(ctask); +} + +int iscsi_eh_abort(struct scsi_cmnd *sc) +{ + struct iscsi_cmd_task *ctask; + struct iscsi_conn *conn; + struct iscsi_session *session; + int rc; + + /* + * if session was ISCSI_STATE_IN_RECOVERY then we may not have + * got the command. + */ + if (!sc->SCp.ptr) { + debug_scsi("sc never reached iscsi layer or it completed.\n"); + return SUCCESS; + } + + ctask = (struct iscsi_cmd_task *)sc->SCp.ptr; + conn = ctask->conn; + session = conn->session; + + conn->eh_abort_cnt++; + debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt); + + mutex_lock(&conn->xmitmutex); + spin_lock_bh(&session->lock); + + /* + * If we are not logged in or we have started a new session + * then let the host reset code handle this + */ + if (session->state != ISCSI_STATE_LOGGED_IN || + sc->SCp.phase != session->age) + goto failed; + + /* ctask completed before time out */ + if (!ctask->sc) { + spin_unlock_bh(&session->lock); + debug_scsi("sc completed while abort in progress\n"); + goto success_rel_mutex; + } + + /* what should we do here ? */ + if (conn->ctask == ctask) { + printk(KERN_INFO "iscsi: sc %p itt 0x%x partially sent. " + "Failing abort\n", sc, ctask->itt); + goto failed; + } + + if (ctask->state == ISCSI_TASK_PENDING) + goto success_cleanup; + + conn->tmabort_state = TMABORT_INITIAL; + + spin_unlock_bh(&session->lock); + rc = iscsi_exec_abort_task(sc, ctask); + spin_lock_bh(&session->lock); + + if (rc || sc->SCp.phase != session->age || + session->state != ISCSI_STATE_LOGGED_IN) + goto failed; + iscsi_ctask_mtask_cleanup(ctask); + + switch (conn->tmabort_state) { + case TMABORT_SUCCESS: + goto success_cleanup; + case TMABORT_NOT_FOUND: + if (!ctask->sc) { + /* ctask completed before tmf abort response */ + spin_unlock_bh(&session->lock); + debug_scsi("sc completed while abort in progress\n"); + goto success_rel_mutex; + } + /* fall through */ + default: + /* timedout or failed */ + spin_unlock_bh(&session->lock); + iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); + spin_lock_bh(&session->lock); + goto failed; + } + +success_cleanup: + debug_scsi("abort success [sc %lx itt 0x%x]\n", (long)sc, ctask->itt); + spin_unlock_bh(&session->lock); + + /* + * clean up task if aborted. we have the xmitmutex so grab + * the recv lock as a writer + */ + write_lock_bh(conn->recv_lock); + spin_lock(&session->lock); + fail_command(conn, ctask, DID_ABORT << 16); + spin_unlock(&session->lock); + write_unlock_bh(conn->recv_lock); + +success_rel_mutex: + mutex_unlock(&conn->xmitmutex); + return SUCCESS; + +failed: + spin_unlock_bh(&session->lock); + mutex_unlock(&conn->xmitmutex); + + debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt); + return FAILED; +} +EXPORT_SYMBOL_GPL(iscsi_eh_abort); + +int +iscsi_pool_init(struct iscsi_queue *q, int max, void ***items, int item_size) +{ + int i; + + *items = kmalloc(max * sizeof(void*), GFP_KERNEL); + if (*items == NULL) + return -ENOMEM; + + q->max = max; + q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL); + if (q->pool == NULL) { + kfree(*items); + return -ENOMEM; + } + + q->queue = kfifo_init((void*)q->pool, max * sizeof(void*), + GFP_KERNEL, NULL); + if (q->queue == ERR_PTR(-ENOMEM)) { + kfree(q->pool); + kfree(*items); + return -ENOMEM; + } + + for (i = 0; i < max; i++) { + q->pool[i] = kmalloc(item_size, GFP_KERNEL); + if (q->pool[i] == NULL) { + int j; + + for (j = 0; j < i; j++) + kfree(q->pool[j]); + + kfifo_free(q->queue); + kfree(q->pool); + kfree(*items); + return -ENOMEM; + } + memset(q->pool[i], 0, item_size); + (*items)[i] = q->pool[i]; + __kfifo_put(q->queue, (void*)&q->pool[i], sizeof(void*)); + } + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_pool_init); + +void iscsi_pool_free(struct iscsi_queue *q, void **items) +{ + int i; + + for (i = 0; i < q->max; i++) + kfree(items[i]); + kfree(q->pool); + kfree(items); +} +EXPORT_SYMBOL_GPL(iscsi_pool_free); + +/* + * iSCSI Session's hostdata organization: + * + * *------------------* <== hostdata_session(host->hostdata) + * | ptr to class sess| + * |------------------| <== iscsi_hostdata(host->hostdata) + * | iscsi_session | + * *------------------* + */ + +#define hostdata_privsize(_sz) (sizeof(unsigned long) + _sz + \ + _sz % sizeof(unsigned long)) + +#define hostdata_session(_hostdata) (iscsi_ptr(*(unsigned long *)_hostdata)) + +/** + * iscsi_session_setup - create iscsi cls session and host and session + * @scsit: scsi transport template + * @iscsit: iscsi transport template + * @initial_cmdsn: initial CmdSN + * @hostno: host no allocated + * + * This can be used by software iscsi_transports that allocate + * a session per scsi host. + **/ +struct iscsi_cls_session * +iscsi_session_setup(struct iscsi_transport *iscsit, + struct scsi_transport_template *scsit, + int cmd_task_size, int mgmt_task_size, + uint32_t initial_cmdsn, uint32_t *hostno) +{ + struct Scsi_Host *shost; + struct iscsi_session *session; + struct iscsi_cls_session *cls_session; + int cmd_i; + + shost = scsi_host_alloc(iscsit->host_template, + hostdata_privsize(sizeof(*session))); + if (!shost) + return NULL; + + shost->max_id = 1; + shost->max_channel = 0; + shost->max_lun = iscsit->max_lun; + shost->max_cmd_len = iscsit->max_cmd_len; + shost->transportt = scsit; + shost->transportt->create_work_queue = 1; + *hostno = shost->host_no; + + session = iscsi_hostdata(shost->hostdata); + memset(session, 0, sizeof(struct iscsi_session)); + session->host = shost; + session->state = ISCSI_STATE_FREE; + session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX; + session->cmds_max = ISCSI_XMIT_CMDS_MAX; + session->cmdsn = initial_cmdsn; + session->exp_cmdsn = initial_cmdsn + 1; + session->max_cmdsn = initial_cmdsn + 1; + session->max_r2t = 1; + session->tt = iscsit; + + /* initialize SCSI PDU commands pool */ + if (iscsi_pool_init(&session->cmdpool, session->cmds_max, + (void***)&session->cmds, + cmd_task_size + sizeof(struct iscsi_cmd_task))) + goto cmdpool_alloc_fail; + + /* pre-format cmds pool with ITT */ + for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { + struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; + + if (cmd_task_size) + ctask->dd_data = &ctask[1]; + ctask->itt = cmd_i; + INIT_LIST_HEAD(&ctask->running); + } + + spin_lock_init(&session->lock); + + /* initialize immediate command pool */ + if (iscsi_pool_init(&session->mgmtpool, session->mgmtpool_max, + (void***)&session->mgmt_cmds, + mgmt_task_size + sizeof(struct iscsi_mgmt_task))) + goto mgmtpool_alloc_fail; + + + /* pre-format immediate cmds pool with ITT */ + for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) { + struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i]; + + if (mgmt_task_size) + mtask->dd_data = &mtask[1]; + mtask->itt = ISCSI_MGMT_ITT_OFFSET + cmd_i; + INIT_LIST_HEAD(&mtask->running); + } + + if (scsi_add_host(shost, NULL)) + goto add_host_fail; + + if (!try_module_get(iscsit->owner)) + goto cls_session_fail; + + cls_session = iscsi_create_session(shost, iscsit, 0); + if (!cls_session) + goto module_put; + *(unsigned long*)shost->hostdata = (unsigned long)cls_session; + + return cls_session; + +module_put: + module_put(iscsit->owner); +cls_session_fail: + scsi_remove_host(shost); +add_host_fail: + iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds); +mgmtpool_alloc_fail: + iscsi_pool_free(&session->cmdpool, (void**)session->cmds); +cmdpool_alloc_fail: + scsi_host_put(shost); + return NULL; +} +EXPORT_SYMBOL_GPL(iscsi_session_setup); + +/** + * iscsi_session_teardown - destroy session, host, and cls_session + * shost: scsi host + * + * This can be used by software iscsi_transports that allocate + * a session per scsi host. + **/ +void iscsi_session_teardown(struct iscsi_cls_session *cls_session) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); + struct iscsi_session *session = iscsi_hostdata(shost->hostdata); + struct module *owner = cls_session->transport->owner; + + scsi_remove_host(shost); + + iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds); + iscsi_pool_free(&session->cmdpool, (void**)session->cmds); + + kfree(session->targetname); + + iscsi_destroy_session(cls_session); + scsi_host_put(shost); + module_put(owner); +} +EXPORT_SYMBOL_GPL(iscsi_session_teardown); + +/** + * iscsi_conn_setup - create iscsi_cls_conn and iscsi_conn + * @cls_session: iscsi_cls_session + * @conn_idx: cid + **/ +struct iscsi_cls_conn * +iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx) +{ + struct iscsi_session *session = class_to_transport_session(cls_session); + struct iscsi_conn *conn; + struct iscsi_cls_conn *cls_conn; + char *data; + + cls_conn = iscsi_create_conn(cls_session, conn_idx); + if (!cls_conn) + return NULL; + conn = cls_conn->dd_data; + memset(conn, 0, sizeof(*conn)); + + conn->session = session; + conn->cls_conn = cls_conn; + conn->c_stage = ISCSI_CONN_INITIAL_STAGE; + conn->id = conn_idx; + conn->exp_statsn = 0; + conn->tmabort_state = TMABORT_INITIAL; + INIT_LIST_HEAD(&conn->run_list); + INIT_LIST_HEAD(&conn->mgmt_run_list); + INIT_LIST_HEAD(&conn->xmitqueue); + + /* initialize general immediate & non-immediate PDU commands queue */ + conn->immqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*), + GFP_KERNEL, NULL); + if (conn->immqueue == ERR_PTR(-ENOMEM)) + goto immqueue_alloc_fail; + + conn->mgmtqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*), + GFP_KERNEL, NULL); + if (conn->mgmtqueue == ERR_PTR(-ENOMEM)) + goto mgmtqueue_alloc_fail; + + INIT_WORK(&conn->xmitwork, iscsi_xmitworker); + + /* allocate login_mtask used for the login/text sequences */ + spin_lock_bh(&session->lock); + if (!__kfifo_get(session->mgmtpool.queue, + (void*)&conn->login_mtask, + sizeof(void*))) { + spin_unlock_bh(&session->lock); + goto login_mtask_alloc_fail; + } + spin_unlock_bh(&session->lock); + + data = kmalloc(ISCSI_DEF_MAX_RECV_SEG_LEN, GFP_KERNEL); + if (!data) + goto login_mtask_data_alloc_fail; + conn->login_mtask->data = conn->data = data; + + init_timer(&conn->tmabort_timer); + mutex_init(&conn->xmitmutex); + init_waitqueue_head(&conn->ehwait); + + return cls_conn; + +login_mtask_data_alloc_fail: + __kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask, + sizeof(void*)); +login_mtask_alloc_fail: + kfifo_free(conn->mgmtqueue); +mgmtqueue_alloc_fail: + kfifo_free(conn->immqueue); +immqueue_alloc_fail: + iscsi_destroy_conn(cls_conn); + return NULL; +} +EXPORT_SYMBOL_GPL(iscsi_conn_setup); + +/** + * iscsi_conn_teardown - teardown iscsi connection + * cls_conn: iscsi class connection + * + * TODO: we may need to make this into a two step process + * like scsi-mls remove + put host + */ +void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_session *session = conn->session; + unsigned long flags; + + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); + mutex_lock(&conn->xmitmutex); + + spin_lock_bh(&session->lock); + conn->c_stage = ISCSI_CONN_CLEANUP_WAIT; + if (session->leadconn == conn) { + /* + * leading connection? then give up on recovery. + */ + session->state = ISCSI_STATE_TERMINATE; + wake_up(&conn->ehwait); + } + spin_unlock_bh(&session->lock); + + mutex_unlock(&conn->xmitmutex); + + /* + * Block until all in-progress commands for this connection + * time out or fail. + */ + for (;;) { + spin_lock_irqsave(session->host->host_lock, flags); + if (!session->host->host_busy) { /* OK for ERL == 0 */ + spin_unlock_irqrestore(session->host->host_lock, flags); + break; + } + spin_unlock_irqrestore(session->host->host_lock, flags); + msleep_interruptible(500); + printk(KERN_INFO "iscsi: scsi conn_destroy(): host_busy %d " + "host_failed %d\n", session->host->host_busy, + session->host->host_failed); + /* + * force eh_abort() to unblock + */ + wake_up(&conn->ehwait); + } + + /* flush queued up work because we free the connection below */ + scsi_flush_work(session->host); + + spin_lock_bh(&session->lock); + kfree(conn->data); + kfree(conn->persistent_address); + __kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask, + sizeof(void*)); + if (session->leadconn == conn) { + session->leadconn = NULL; + /* no connections exits.. reset sequencing */ + session->cmdsn = session->max_cmdsn = session->exp_cmdsn = 1; + } + spin_unlock_bh(&session->lock); + + kfifo_free(conn->immqueue); + kfifo_free(conn->mgmtqueue); + + iscsi_destroy_conn(cls_conn); +} +EXPORT_SYMBOL_GPL(iscsi_conn_teardown); + +int iscsi_conn_start(struct iscsi_cls_conn *cls_conn) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_session *session = conn->session; + + if (!session) { + printk(KERN_ERR "iscsi: can't start unbound connection\n"); + return -EPERM; + } + + if ((session->imm_data_en || !session->initial_r2t_en) && + session->first_burst > session->max_burst) { + printk("iscsi: invalid burst lengths: " + "first_burst %d max_burst %d\n", + session->first_burst, session->max_burst); + return -EINVAL; + } + + spin_lock_bh(&session->lock); + conn->c_stage = ISCSI_CONN_STARTED; + session->state = ISCSI_STATE_LOGGED_IN; + + switch(conn->stop_stage) { + case STOP_CONN_RECOVER: + /* + * unblock eh_abort() if it is blocked. re-try all + * commands after successful recovery + */ + conn->stop_stage = 0; + conn->tmabort_state = TMABORT_INITIAL; + session->age++; + spin_unlock_bh(&session->lock); + + iscsi_unblock_session(session_to_cls(session)); + wake_up(&conn->ehwait); + return 0; + case STOP_CONN_TERM: + conn->stop_stage = 0; + break; + default: + break; + } + spin_unlock_bh(&session->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_conn_start); + +static void +flush_control_queues(struct iscsi_session *session, struct iscsi_conn *conn) +{ + struct iscsi_mgmt_task *mtask, *tmp; + + /* handle pending */ + while (__kfifo_get(conn->immqueue, (void*)&mtask, sizeof(void*)) || + __kfifo_get(conn->mgmtqueue, (void*)&mtask, sizeof(void*))) { + if (mtask == conn->login_mtask) + continue; + debug_scsi("flushing pending mgmt task itt 0x%x\n", mtask->itt); + __kfifo_put(session->mgmtpool.queue, (void*)&mtask, + sizeof(void*)); + } + + /* handle running */ + list_for_each_entry_safe(mtask, tmp, &conn->mgmt_run_list, running) { + debug_scsi("flushing running mgmt task itt 0x%x\n", mtask->itt); + list_del(&mtask->running); + + if (mtask == conn->login_mtask) + continue; + __kfifo_put(session->mgmtpool.queue, (void*)&mtask, + sizeof(void*)); + } + + conn->mtask = NULL; +} + +/* Fail commands. Mutex and session lock held and recv side suspended */ +static void fail_all_commands(struct iscsi_conn *conn) +{ + struct iscsi_cmd_task *ctask, *tmp; + + /* flush pending */ + list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) { + debug_scsi("failing pending sc %p itt 0x%x\n", ctask->sc, + ctask->itt); + fail_command(conn, ctask, DID_BUS_BUSY << 16); + } + + /* fail all other running */ + list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) { + debug_scsi("failing in progress sc %p itt 0x%x\n", + ctask->sc, ctask->itt); + fail_command(conn, ctask, DID_BUS_BUSY << 16); + } + + conn->ctask = NULL; +} + +static void iscsi_start_session_recovery(struct iscsi_session *session, + struct iscsi_conn *conn, int flag) +{ + int old_stop_stage; + + spin_lock_bh(&session->lock); + if (conn->stop_stage == STOP_CONN_TERM) { + spin_unlock_bh(&session->lock); + return; + } + + /* + * When this is called for the in_login state, we only want to clean + * up the login task and connection. We do not need to block and set + * the recovery state again + */ + if (flag == STOP_CONN_TERM) + session->state = ISCSI_STATE_TERMINATE; + else if (conn->stop_stage != STOP_CONN_RECOVER) + session->state = ISCSI_STATE_IN_RECOVERY; + + old_stop_stage = conn->stop_stage; + conn->stop_stage = flag; + conn->c_stage = ISCSI_CONN_STOPPED; + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); + spin_unlock_bh(&session->lock); + + write_lock_bh(conn->recv_lock); + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); + write_unlock_bh(conn->recv_lock); + + mutex_lock(&conn->xmitmutex); + /* + * for connection level recovery we should not calculate + * header digest. conn->hdr_size used for optimization + * in hdr_extract() and will be re-negotiated at + * set_param() time. + */ + if (flag == STOP_CONN_RECOVER) { + conn->hdrdgst_en = 0; + conn->datadgst_en = 0; + if (session->state == ISCSI_STATE_IN_RECOVERY && + old_stop_stage != STOP_CONN_RECOVER) { + debug_scsi("blocking session\n"); + iscsi_block_session(session_to_cls(session)); + } + } + + /* + * flush queues. + */ + spin_lock_bh(&session->lock); + fail_all_commands(conn); + flush_control_queues(session, conn); + spin_unlock_bh(&session->lock); + + mutex_unlock(&conn->xmitmutex); +} + +void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_session *session = conn->session; + + switch (flag) { + case STOP_CONN_RECOVER: + case STOP_CONN_TERM: + iscsi_start_session_recovery(session, conn, flag); + break; + default: + printk(KERN_ERR "iscsi: invalid stop flag %d\n", flag); + } +} +EXPORT_SYMBOL_GPL(iscsi_conn_stop); + +int iscsi_conn_bind(struct iscsi_cls_session *cls_session, + struct iscsi_cls_conn *cls_conn, int is_leading) +{ + struct iscsi_session *session = class_to_transport_session(cls_session); + struct iscsi_conn *conn = cls_conn->dd_data; + + spin_lock_bh(&session->lock); + if (is_leading) + session->leadconn = conn; + spin_unlock_bh(&session->lock); + + /* + * Unblock xmitworker(), Login Phase will pass through. + */ + clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); + clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_conn_bind); + + +int iscsi_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; + uint32_t value; + + switch(param) { + case ISCSI_PARAM_MAX_RECV_DLENGTH: + sscanf(buf, "%d", &conn->max_recv_dlength); + break; + case ISCSI_PARAM_MAX_XMIT_DLENGTH: + sscanf(buf, "%d", &conn->max_xmit_dlength); + break; + case ISCSI_PARAM_HDRDGST_EN: + sscanf(buf, "%d", &conn->hdrdgst_en); + break; + case ISCSI_PARAM_DATADGST_EN: + sscanf(buf, "%d", &conn->datadgst_en); + break; + case ISCSI_PARAM_INITIAL_R2T_EN: + sscanf(buf, "%d", &session->initial_r2t_en); + break; + case ISCSI_PARAM_MAX_R2T: + sscanf(buf, "%d", &session->max_r2t); + break; + case ISCSI_PARAM_IMM_DATA_EN: + sscanf(buf, "%d", &session->imm_data_en); + break; + case ISCSI_PARAM_FIRST_BURST: + sscanf(buf, "%d", &session->first_burst); + break; + case ISCSI_PARAM_MAX_BURST: + sscanf(buf, "%d", &session->max_burst); + break; + case ISCSI_PARAM_PDU_INORDER_EN: + sscanf(buf, "%d", &session->pdu_inorder_en); + break; + case ISCSI_PARAM_DATASEQ_INORDER_EN: + sscanf(buf, "%d", &session->dataseq_inorder_en); + break; + case ISCSI_PARAM_ERL: + sscanf(buf, "%d", &session->erl); + break; + case ISCSI_PARAM_IFMARKER_EN: + sscanf(buf, "%d", &value); + BUG_ON(value); + break; + case ISCSI_PARAM_OFMARKER_EN: + sscanf(buf, "%d", &value); + BUG_ON(value); + break; + case ISCSI_PARAM_EXP_STATSN: + sscanf(buf, "%u", &conn->exp_statsn); + break; + case ISCSI_PARAM_TARGET_NAME: + /* this should not change between logins */ + if (session->targetname) + break; + + session->targetname = kstrdup(buf, GFP_KERNEL); + if (!session->targetname) + return -ENOMEM; + break; + case ISCSI_PARAM_TPGT: + sscanf(buf, "%d", &session->tpgt); + break; + case ISCSI_PARAM_PERSISTENT_PORT: + sscanf(buf, "%d", &conn->persistent_port); + break; + case ISCSI_PARAM_PERSISTENT_ADDRESS: + /* + * this is the address returned in discovery so it should + * not change between logins. + */ + if (conn->persistent_address) + break; + + conn->persistent_address = kstrdup(buf, GFP_KERNEL); + if (!conn->persistent_address) + return -ENOMEM; + break; + default: + return -ENOSYS; + } + + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_set_param); + +int iscsi_session_get_param(struct iscsi_cls_session *cls_session, + enum iscsi_param param, char *buf) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); + struct iscsi_session *session = iscsi_hostdata(shost->hostdata); + int len; + + switch(param) { + case ISCSI_PARAM_INITIAL_R2T_EN: + len = sprintf(buf, "%d\n", session->initial_r2t_en); + break; + case ISCSI_PARAM_MAX_R2T: + len = sprintf(buf, "%hu\n", session->max_r2t); + break; + case ISCSI_PARAM_IMM_DATA_EN: + len = sprintf(buf, "%d\n", session->imm_data_en); + break; + case ISCSI_PARAM_FIRST_BURST: + len = sprintf(buf, "%u\n", session->first_burst); + break; + case ISCSI_PARAM_MAX_BURST: + len = sprintf(buf, "%u\n", session->max_burst); + break; + case ISCSI_PARAM_PDU_INORDER_EN: + len = sprintf(buf, "%d\n", session->pdu_inorder_en); + break; + case ISCSI_PARAM_DATASEQ_INORDER_EN: + len = sprintf(buf, "%d\n", session->dataseq_inorder_en); + break; + case ISCSI_PARAM_ERL: + len = sprintf(buf, "%d\n", session->erl); + break; + case ISCSI_PARAM_TARGET_NAME: + len = sprintf(buf, "%s\n", session->targetname); + break; + case ISCSI_PARAM_TPGT: + len = sprintf(buf, "%d\n", session->tpgt); + break; + default: + return -ENOSYS; + } + + return len; +} +EXPORT_SYMBOL_GPL(iscsi_session_get_param); + +int iscsi_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_MAX_RECV_DLENGTH: + len = sprintf(buf, "%u\n", conn->max_recv_dlength); + break; + case ISCSI_PARAM_MAX_XMIT_DLENGTH: + len = sprintf(buf, "%u\n", conn->max_xmit_dlength); + break; + case ISCSI_PARAM_HDRDGST_EN: + len = sprintf(buf, "%d\n", conn->hdrdgst_en); + break; + case ISCSI_PARAM_DATADGST_EN: + len = sprintf(buf, "%d\n", conn->datadgst_en); + break; + case ISCSI_PARAM_IFMARKER_EN: + len = sprintf(buf, "%d\n", conn->ifmarker_en); + break; + case ISCSI_PARAM_OFMARKER_EN: + len = sprintf(buf, "%d\n", conn->ofmarker_en); + break; + case ISCSI_PARAM_EXP_STATSN: + len = sprintf(buf, "%u\n", conn->exp_statsn); + break; + case ISCSI_PARAM_PERSISTENT_PORT: + len = sprintf(buf, "%d\n", conn->persistent_port); + break; + case ISCSI_PARAM_PERSISTENT_ADDRESS: + len = sprintf(buf, "%s\n", conn->persistent_address); + break; + default: + return -ENOSYS; + } + + return len; +} +EXPORT_SYMBOL_GPL(iscsi_conn_get_param); + +MODULE_AUTHOR("Mike Christie"); +MODULE_DESCRIPTION("iSCSI library functions"); +MODULE_LICENSE("GPL"); Index: kernel/iscsi_tcp.h =================================================================== --- kernel/iscsi_tcp.h (revision 0) +++ kernel/iscsi_tcp.h (revision 779) @@ -0,0 +1,167 @@ +/* + * iSCSI Initiator TCP Transport + * Copyright (C) 2004 Dmitry Yusupov + * Copyright (C) 2004 Alex Aizman + * Copyright (C) 2005 - 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSI_TCP_H +#define ISCSI_TCP_H + +#include "libiscsi.h" + +/* Socket's Receive state machine */ +#define IN_PROGRESS_WAIT_HEADER 0x0 +#define IN_PROGRESS_HEADER_GATHER 0x1 +#define IN_PROGRESS_DATA_RECV 0x2 +#define IN_PROGRESS_DDIGEST_RECV 0x3 + +/* xmit state machine */ +#define XMSTATE_IDLE 0x0 +#define XMSTATE_R_HDR 0x1 +#define XMSTATE_W_HDR 0x2 +#define XMSTATE_IMM_HDR 0x4 +#define XMSTATE_IMM_DATA 0x8 +#define XMSTATE_UNS_INIT 0x10 +#define XMSTATE_UNS_HDR 0x20 +#define XMSTATE_UNS_DATA 0x40 +#define XMSTATE_SOL_HDR 0x80 +#define XMSTATE_SOL_DATA 0x100 +#define XMSTATE_W_PAD 0x200 +#define XMSTATE_W_RESEND_PAD 0x400 +#define XMSTATE_W_RESEND_DATA_DIGEST 0x800 + +#define ISCSI_PAD_LEN 4 +#define ISCSI_SG_TABLESIZE SG_ALL +#define ISCSI_TCP_MAX_CMD_LEN 16 + +struct crypto_hash; +struct socket; + +/* Socket connection recieve helper */ +struct iscsi_tcp_recv { + struct iscsi_hdr *hdr; + struct sk_buff *skb; + int offset; + int len; + int hdr_offset; + int copy; + int copied; + int padding; + struct iscsi_cmd_task *ctask; /* current cmd in progress */ + + /* copied and flipped values */ + int datalen; + int datadgst; + char zero_copy_hdr; +}; + +struct iscsi_tcp_conn { + struct iscsi_conn *iscsi_conn; + struct socket *sock; + struct iscsi_hdr hdr; /* header placeholder */ + char hdrext[4*sizeof(__u16) + + sizeof(__u32)]; + int data_copied; + int stop_stage; /* conn_stop() flag: * + * stop to recover, * + * stop to terminate */ + /* iSCSI connection-wide sequencing */ + int hdr_size; /* PDU header size */ + + /* control data */ + struct iscsi_tcp_recv in; /* TCP receive context */ + int in_progress; /* connection state machine */ + + /* old values for socket callbacks */ + void (*old_data_ready)(struct sock *, int); + void (*old_state_change)(struct sock *); + void (*old_write_space)(struct sock *); + + /* data and header digests */ + struct hash_desc tx_hash; /* CRC32C (Tx) */ + struct hash_desc rx_hash; /* CRC32C (Rx) */ + + /* MIB custom statistics */ + uint32_t sendpage_failures_cnt; + uint32_t discontiguous_hdr_cnt; + + ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int); +}; + +struct iscsi_buf { + struct scatterlist sg; + unsigned int sent; + char use_sendmsg; +}; + +struct iscsi_data_task { + struct iscsi_data hdr; /* PDU */ + char hdrext[sizeof(__u32)]; /* Header-Digest */ + struct iscsi_buf digestbuf; /* digest buffer */ + uint32_t digest; /* data digest */ +}; + +struct iscsi_tcp_mgmt_task { + struct iscsi_hdr hdr; + char hdrext[sizeof(__u32)]; /* Header-Digest */ + int xmstate; /* mgmt xmit progress */ + struct iscsi_buf headbuf; /* header buffer */ + struct iscsi_buf sendbuf; /* in progress buffer */ + int sent; +}; + +struct iscsi_r2t_info { + __be32 ttt; /* copied from R2T */ + __be32 exp_statsn; /* copied from R2T */ + uint32_t data_length; /* copied from R2T */ + uint32_t data_offset; /* copied from R2T */ + struct iscsi_buf headbuf; /* Data-Out Header Buffer */ + struct iscsi_buf sendbuf; /* Data-Out in progress buffer*/ + int sent; /* R2T sequence progress */ + int data_count; /* DATA-Out payload progress */ + struct scatterlist *sg; /* per-R2T SG list */ + int solicit_datasn; + struct iscsi_data_task dtask; /* which data task */ +}; + +struct iscsi_tcp_cmd_task { + struct iscsi_cmd hdr; + char hdrext[4*sizeof(__u16)+ /* AHS */ + sizeof(__u32)]; /* HeaderDigest */ + char pad[ISCSI_PAD_LEN]; + int pad_count; /* padded bytes */ + struct iscsi_buf headbuf; /* header buf (xmit) */ + struct iscsi_buf sendbuf; /* in progress buffer*/ + int xmstate; /* xmit xtate machine */ + int sent; + struct scatterlist *sg; /* per-cmd SG list */ + struct scatterlist *bad_sg; /* assert statement */ + int sg_count; /* SG's to process */ + uint32_t exp_r2tsn; + int data_offset; + struct iscsi_r2t_info *r2t; /* in progress R2T */ + struct iscsi_queue r2tpool; + struct kfifo *r2tqueue; + struct iscsi_r2t_info **r2ts; + int digest_count; + uint32_t immdigest; /* for imm data */ + struct iscsi_buf immbuf; /* for imm data digest */ + struct iscsi_data_task unsol_dtask; /* unsol data task */ +}; + +#endif /* ISCSI_H */ Index: kernel/libiscsi.h =================================================================== --- kernel/libiscsi.h (revision 0) +++ kernel/libiscsi.h (revision 779) @@ -0,0 +1,311 @@ +/* + * iSCSI lib definitions + * + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004 - 2006 Mike Christie + * Copyright (C) 2004 - 2005 Dmitry Yusupov + * Copyright (C) 2004 - 2005 Alex Aizman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef LIBISCSI_H +#define LIBISCSI_H + +#include +#include +#include +#include +#include "iscsi_proto.h" +#include "iscsi_if.h" + +struct scsi_transport_template; +struct scsi_device; +struct Scsi_Host; +struct scsi_cmnd; +struct socket; +struct iscsi_transport; +struct iscsi_cls_session; +struct iscsi_cls_conn; +struct iscsi_session; +struct iscsi_nopin; + +/* #define DEBUG_SCSI */ +#ifdef DEBUG_SCSI +#define debug_scsi(fmt...) printk(KERN_INFO "iscsi: " fmt) +#else +#define debug_scsi(fmt...) +#endif + +#define ISCSI_XMIT_CMDS_MAX 128 /* must be power of 2 */ +#define ISCSI_MGMT_CMDS_MAX 32 /* must be power of 2 */ +#define ISCSI_CONN_MAX 1 + +#define ISCSI_MGMT_ITT_OFFSET 0xa00 + +#define ISCSI_DEF_CMD_PER_LUN 32 +#define ISCSI_MAX_CMD_PER_LUN 128 + +/* Task Mgmt states */ +#define TMABORT_INITIAL 0x0 +#define TMABORT_SUCCESS 0x1 +#define TMABORT_FAILED 0x2 +#define TMABORT_TIMEDOUT 0x3 +#define TMABORT_NOT_FOUND 0x4 + +/* Connection suspend "bit" */ +#define ISCSI_SUSPEND_BIT 1 + +#define ISCSI_ITT_MASK (0xfff) +#define ISCSI_CID_SHIFT 12 +#define ISCSI_CID_MASK (0xffff << ISCSI_CID_SHIFT) +#define ISCSI_AGE_SHIFT 28 +#define ISCSI_AGE_MASK (0xf << ISCSI_AGE_SHIFT) + +struct iscsi_mgmt_task { + /* + * Becuae LLDs allocate their hdr differently, this is a pointer to + * that storage. It must be setup at session creation time. + */ + struct iscsi_hdr *hdr; + char *data; /* mgmt payload */ + int data_count; /* counts data to be sent */ + uint32_t itt; /* this ITT */ + void *dd_data; /* driver/transport data */ + struct list_head running; +}; + +enum { + ISCSI_TASK_COMPLETED, + ISCSI_TASK_PENDING, + ISCSI_TASK_RUNNING, +}; + +struct iscsi_cmd_task { + /* + * Becuae LLDs allocate their hdr differently, this is a pointer to + * that storage. It must be setup at session creation time. + */ + struct iscsi_cmd *hdr; + int itt; /* this ITT */ + int datasn; /* DataSN */ + + uint32_t unsol_datasn; + int imm_count; /* imm-data (bytes) */ + int unsol_count; /* unsolicited (bytes)*/ + /* offset in unsolicited stream (bytes); */ + int unsol_offset; + int data_count; /* remaining Data-Out */ + struct scsi_cmnd *sc; /* associated SCSI cmd*/ + int total_length; + struct iscsi_conn *conn; /* used connection */ + struct iscsi_mgmt_task *mtask; /* tmf mtask in progr */ + + /* state set/tested under session->lock */ + int state; + atomic_t refcount; + struct list_head running; /* running cmd list */ + void *dd_data; /* driver/transport data */ +}; + +struct iscsi_conn { + struct iscsi_cls_conn *cls_conn; /* ptr to class connection */ + void *dd_data; /* iscsi_transport data */ + struct iscsi_session *session; /* parent session */ + /* + * LLDs should set this lock. It protects the transport recv + * code + */ + rwlock_t *recv_lock; + /* + * conn_stop() flag: stop to recover, stop to terminate + */ + int stop_stage; + + /* iSCSI connection-wide sequencing */ + uint32_t exp_statsn; + + /* control data */ + int id; /* CID */ + int c_stage; /* connection state */ + /* + * Preallocated buffer for pdus that have data but do not + * originate from scsi-ml. We never have two pdus using the + * buffer at the same time. It is only allocated to + * the default max recv size because the pdus we support + * should always fit in this buffer + */ + char *data; + struct iscsi_mgmt_task *login_mtask; /* mtask used for login/text */ + struct iscsi_mgmt_task *mtask; /* xmit mtask in progress */ + struct iscsi_cmd_task *ctask; /* xmit ctask in progress */ + + /* xmit */ + struct kfifo *immqueue; /* immediate xmit queue */ + struct kfifo *mgmtqueue; /* mgmt (control) xmit queue */ + struct list_head mgmt_run_list; /* list of control tasks */ + struct list_head xmitqueue; /* data-path cmd queue */ + struct list_head run_list; /* list of cmds in progress */ + struct work_struct xmitwork; /* per-conn. xmit workqueue */ + /* + * serializes connection xmit, access to kfifos: + * xmitqueue, immqueue, mgmtqueue + */ + struct mutex xmitmutex; + + unsigned long suspend_tx; /* suspend Tx */ + unsigned long suspend_rx; /* suspend Rx */ + + /* abort */ + wait_queue_head_t ehwait; /* used in eh_abort() */ + struct iscsi_tm tmhdr; + struct timer_list tmabort_timer; + int tmabort_state; /* see TMABORT_INITIAL, etc.*/ + + /* negotiated params */ + int max_recv_dlength; /* initiator_max_recv_dsl*/ + int max_xmit_dlength; /* target_max_recv_dsl */ + int hdrdgst_en; + int datadgst_en; + int ifmarker_en; + int ofmarker_en; + /* values userspace uses to id a conn */ + int persistent_port; + char *persistent_address; + + /* MIB-statistics */ + uint64_t txdata_octets; + uint64_t rxdata_octets; + uint32_t scsicmd_pdus_cnt; + uint32_t dataout_pdus_cnt; + uint32_t scsirsp_pdus_cnt; + uint32_t datain_pdus_cnt; + uint32_t r2t_pdus_cnt; + uint32_t tmfcmd_pdus_cnt; + int32_t tmfrsp_pdus_cnt; + + /* custom statistics */ + uint32_t eh_abort_cnt; +}; + +struct iscsi_queue { + struct kfifo *queue; /* FIFO Queue */ + void **pool; /* Pool of elements */ + int max; /* Max number of elements */ +}; + +struct iscsi_session { + /* iSCSI session-wide sequencing */ + uint32_t cmdsn; + uint32_t exp_cmdsn; + uint32_t max_cmdsn; + + /* configuration */ + int initial_r2t_en; + int max_r2t; + int imm_data_en; + int first_burst; + int max_burst; + int time2wait; + int time2retain; + int pdu_inorder_en; + int dataseq_inorder_en; + int erl; + int tpgt; + char *targetname; + + /* control data */ + struct iscsi_transport *tt; + struct Scsi_Host *host; + struct iscsi_conn *leadconn; /* leading connection */ + spinlock_t lock; /* protects session state, * + * sequence numbers, * + * session resources: * + * - cmdpool, * + * - mgmtpool, * + * - r2tpool */ + int state; /* session state */ + int age; /* counts session re-opens */ + + int cmds_max; /* size of cmds array */ + struct iscsi_cmd_task **cmds; /* Original Cmds arr */ + struct iscsi_queue cmdpool; /* PDU's pool */ + int mgmtpool_max; /* size of mgmt array */ + struct iscsi_mgmt_task **mgmt_cmds; /* Original mgmt arr */ + struct iscsi_queue mgmtpool; /* Mgmt PDU's pool */ +}; + +/* + * scsi host template + */ +extern int iscsi_change_queue_depth(struct scsi_device *sdev, int depth); +extern int iscsi_eh_abort(struct scsi_cmnd *sc); +extern int iscsi_eh_host_reset(struct scsi_cmnd *sc); +extern int iscsi_queuecommand(struct scsi_cmnd *sc, + void (*done)(struct scsi_cmnd *)); + +/* + * session management + */ +extern struct iscsi_cls_session * +iscsi_session_setup(struct iscsi_transport *, struct scsi_transport_template *, + int, int, uint32_t, uint32_t *); +extern void iscsi_session_teardown(struct iscsi_cls_session *); +extern struct iscsi_session *class_to_transport_session(struct iscsi_cls_session *); +extern void iscsi_session_recovery_timedout(struct iscsi_cls_session *); +extern int iscsi_set_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param param, char *buf, int buflen); +extern int iscsi_session_get_param(struct iscsi_cls_session *cls_session, + enum iscsi_param param, char *buf); + +#define session_to_cls(_sess) \ + hostdata_session(_sess->host->hostdata) + +/* + * connection management + */ +extern struct iscsi_cls_conn *iscsi_conn_setup(struct iscsi_cls_session *, + uint32_t); +extern void iscsi_conn_teardown(struct iscsi_cls_conn *); +extern int iscsi_conn_start(struct iscsi_cls_conn *); +extern void iscsi_conn_stop(struct iscsi_cls_conn *, int); +extern int iscsi_conn_bind(struct iscsi_cls_session *, struct iscsi_cls_conn *, + int); +extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err); +extern int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param param, char *buf); + +/* + * pdu and task processing + */ +extern int iscsi_check_assign_cmdsn(struct iscsi_session *, + struct iscsi_nopin *); +extern void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *, + struct iscsi_data *hdr); +extern int iscsi_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *, + char *, uint32_t); +extern int iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *, + char *, int); +extern int __iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *, + char *, int); +extern int iscsi_verify_itt(struct iscsi_conn *, struct iscsi_hdr *, + uint32_t *); + +/* + * generic helpers + */ +extern void iscsi_pool_free(struct iscsi_queue *, void **); +extern int iscsi_pool_init(struct iscsi_queue *, int, void ***, int); + +#endif Index: kernel/scsi_transport_iscsi.c =================================================================== --- kernel/scsi_transport_iscsi.c (revision 0) +++ kernel/scsi_transport_iscsi.c (revision 779) @@ -0,0 +1,1475 @@ +/* + * iSCSI transport class definitions + * + * Copyright (C) IBM Corporation, 2004 + * Copyright (C) Mike Christie, 2004 - 2005 + * Copyright (C) Dmitry Yusupov, 2004 - 2005 + * Copyright (C) Alex Aizman, 2004 - 2005 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include "scsi_transport_iscsi.h" +#include "iscsi_if.h" + +#define ISCSI_SESSION_ATTRS 11 +#define ISCSI_CONN_ATTRS 11 +#define ISCSI_HOST_ATTRS 0 +#define ISCSI_TRANSPORT_VERSION "2.0-724" + +struct iscsi_internal { + int daemon_pid; + struct scsi_transport_template t; + struct iscsi_transport *iscsi_transport; + struct list_head list; + struct class_device cdev; + + struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1]; + struct transport_container conn_cont; + struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1]; + struct transport_container session_cont; + struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1]; +}; + +static atomic_t iscsi_session_nr; /* sysfs session id for next new session */ + +/* + * list of registered transports and lock that must + * be held while accessing list. The iscsi_transport_lock must + * be acquired after the rx_queue_mutex. + */ +static LIST_HEAD(iscsi_transports); +static DEFINE_SPINLOCK(iscsi_transport_lock); + +#define to_iscsi_internal(tmpl) \ + container_of(tmpl, struct iscsi_internal, t) + +#define cdev_to_iscsi_internal(_cdev) \ + container_of(_cdev, struct iscsi_internal, cdev) + +static void iscsi_transport_release(struct class_device *cdev) +{ + struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev); + kfree(priv); +} + +/* + * iscsi_transport_class represents the iscsi_transports that are + * registered. + */ +static struct class iscsi_transport_class = { + .name = "iscsi_transport", + .release = iscsi_transport_release, +}; + +static ssize_t +show_transport_handle(struct class_device *cdev, char *buf) +{ + struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev); + return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport)); +} +static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL); + +#define show_transport_attr(name, format) \ +static ssize_t \ +show_transport_##name(struct class_device *cdev, char *buf) \ +{ \ + struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev); \ + return sprintf(buf, format"\n", priv->iscsi_transport->name); \ +} \ +static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL); + +show_transport_attr(caps, "0x%x"); +show_transport_attr(max_lun, "%d"); +show_transport_attr(max_conn, "%d"); +show_transport_attr(max_cmd_len, "%d"); + +static struct attribute *iscsi_transport_attrs[] = { + &class_device_attr_handle.attr, + &class_device_attr_caps.attr, + &class_device_attr_max_lun.attr, + &class_device_attr_max_conn.attr, + &class_device_attr_max_cmd_len.attr, + NULL, +}; + +static struct attribute_group iscsi_transport_group = { + .attrs = iscsi_transport_attrs, +}; + +static int iscsi_setup_host(struct transport_container *tc, struct device *dev, + struct class_device *cdev) +{ + struct Scsi_Host *shost = dev_to_shost(dev); + struct iscsi_host *ihost = shost->shost_data; + + memset(ihost, 0, sizeof(*ihost)); + INIT_LIST_HEAD(&ihost->sessions); + mutex_init(&ihost->mutex); + return 0; +} + +static DECLARE_TRANSPORT_CLASS(iscsi_host_class, + "iscsi_host", + iscsi_setup_host, + NULL, + NULL); + +static DECLARE_TRANSPORT_CLASS(iscsi_session_class, + "iscsi_session", + NULL, + NULL, + NULL); + +static DECLARE_TRANSPORT_CLASS(iscsi_connection_class, + "iscsi_connection", + NULL, + NULL, + NULL); + +static struct sock *nls; +static DEFINE_MUTEX(rx_queue_mutex); + +static LIST_HEAD(sesslist); +static DEFINE_SPINLOCK(sesslock); +static LIST_HEAD(connlist); +static DEFINE_SPINLOCK(connlock); + +static uint32_t iscsi_conn_get_sid(struct iscsi_cls_conn *conn) +{ + struct iscsi_cls_session *sess = iscsi_dev_to_session(conn->dev.parent); + return sess->sid; +} + +/* + * Returns the matching session to a given sid + */ +static struct iscsi_cls_session *iscsi_session_lookup(uint32_t sid) +{ + unsigned long flags; + struct iscsi_cls_session *sess; + + spin_lock_irqsave(&sesslock, flags); + list_for_each_entry(sess, &sesslist, sess_list) { + if (sess->sid == sid) { + spin_unlock_irqrestore(&sesslock, flags); + return sess; + } + } + spin_unlock_irqrestore(&sesslock, flags); + return NULL; +} + +/* + * Returns the matching connection to a given sid / cid tuple + */ +static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid) +{ + unsigned long flags; + struct iscsi_cls_conn *conn; + + spin_lock_irqsave(&connlock, flags); + list_for_each_entry(conn, &connlist, conn_list) { + if ((conn->cid == cid) && (iscsi_conn_get_sid(conn) == sid)) { + spin_unlock_irqrestore(&connlock, flags); + return conn; + } + } + spin_unlock_irqrestore(&connlock, flags); + return NULL; +} + +/* + * The following functions can be used by LLDs that allocate + * their own scsi_hosts or by software iscsi LLDs + */ +static void iscsi_session_release(struct device *dev) +{ + struct iscsi_cls_session *session = iscsi_dev_to_session(dev); + struct Scsi_Host *shost; + + shost = iscsi_session_to_shost(session); + scsi_host_put(shost); + kfree(session); +} + +static int iscsi_is_session_dev(const struct device *dev) +{ + return dev->release == iscsi_session_release; +} + +static int iscsi_user_scan(struct Scsi_Host *shost, uint channel, + uint id, uint lun) +{ + struct iscsi_host *ihost = shost->shost_data; + struct iscsi_cls_session *session; + + mutex_lock(&ihost->mutex); + list_for_each_entry(session, &ihost->sessions, host_list) { + if ((channel == SCAN_WILD_CARD || channel == 0) && + (id == SCAN_WILD_CARD || id == session->target_id)) + scsi_scan_target(&session->dev, 0, + session->target_id, lun, 1); + } + mutex_unlock(&ihost->mutex); + + return 0; +} + +static void session_recovery_timedout(struct work_struct *work) +{ + struct iscsi_cls_session *session = + container_of(work, struct iscsi_cls_session, + recovery_work.work); + + dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed " + "out after %d secs\n", session->recovery_tmo); + + if (session->transport->session_recovery_timedout) + session->transport->session_recovery_timedout(session); + + scsi_target_unblock(&session->dev); +} + +void iscsi_unblock_session(struct iscsi_cls_session *session) +{ + if (!cancel_delayed_work(&session->recovery_work)) + flush_scheduled_work(); + scsi_target_unblock(&session->dev); +} +EXPORT_SYMBOL_GPL(iscsi_unblock_session); + +void iscsi_block_session(struct iscsi_cls_session *session) +{ + scsi_target_block(&session->dev); + schedule_delayed_work(&session->recovery_work, + session->recovery_tmo * HZ); +} +EXPORT_SYMBOL_GPL(iscsi_block_session); + +struct iscsi_cls_session * +iscsi_alloc_session(struct Scsi_Host *shost, + struct iscsi_transport *transport) +{ + struct iscsi_cls_session *session; + + session = kzalloc(sizeof(*session) + transport->sessiondata_size, + GFP_KERNEL); + if (!session) + return NULL; + + session->transport = transport; + session->recovery_tmo = 120; + INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout); + INIT_LIST_HEAD(&session->host_list); + INIT_LIST_HEAD(&session->sess_list); + + /* this is released in the dev's release function */ + scsi_host_get(shost); + session->dev.parent = &shost->shost_gendev; + session->dev.release = iscsi_session_release; + device_initialize(&session->dev); + if (transport->sessiondata_size) + session->dd_data = &session[1]; + return session; +} +EXPORT_SYMBOL_GPL(iscsi_alloc_session); + +int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(session); + struct iscsi_host *ihost; + int err; + + ihost = shost->shost_data; + session->sid = atomic_add_return(1, &iscsi_session_nr); + session->target_id = target_id; + + snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u", + session->sid); + err = device_add(&session->dev); + if (err) { + dev_printk(KERN_ERR, &session->dev, "iscsi: could not " + "register session's dev\n"); + goto release_host; + } + transport_register_device(&session->dev); + + mutex_lock(&ihost->mutex); + list_add(&session->host_list, &ihost->sessions); + mutex_unlock(&ihost->mutex); + return 0; + +release_host: + scsi_host_put(shost); + return err; +} +EXPORT_SYMBOL_GPL(iscsi_add_session); + +/** + * iscsi_create_session - create iscsi class session + * @shost: scsi host + * @transport: iscsi transport + * + * This can be called from a LLD or iscsi_transport. + **/ +struct iscsi_cls_session * +iscsi_create_session(struct Scsi_Host *shost, + struct iscsi_transport *transport, + unsigned int target_id) +{ + struct iscsi_cls_session *session; + + session = iscsi_alloc_session(shost, transport); + if (!session) + return NULL; + + if (iscsi_add_session(session, target_id)) { + iscsi_free_session(session); + return NULL; + } + return session; +} +EXPORT_SYMBOL_GPL(iscsi_create_session); + +void iscsi_remove_session(struct iscsi_cls_session *session) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(session); + struct iscsi_host *ihost = shost->shost_data; + + if (!cancel_delayed_work(&session->recovery_work)) + flush_scheduled_work(); + + mutex_lock(&ihost->mutex); + list_del(&session->host_list); + mutex_unlock(&ihost->mutex); + + scsi_remove_target(&session->dev); + + transport_unregister_device(&session->dev); + device_del(&session->dev); +} +EXPORT_SYMBOL_GPL(iscsi_remove_session); + +void iscsi_free_session(struct iscsi_cls_session *session) +{ + put_device(&session->dev); +} + +EXPORT_SYMBOL_GPL(iscsi_free_session); + +/** + * iscsi_destroy_session - destroy iscsi session + * @session: iscsi_session + * + * Can be called by a LLD or iscsi_transport. There must not be + * any running connections. + **/ +int iscsi_destroy_session(struct iscsi_cls_session *session) +{ + iscsi_remove_session(session); + iscsi_free_session(session); + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_destroy_session); + +static void iscsi_conn_release(struct device *dev) +{ + struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev); + struct device *parent = conn->dev.parent; + + kfree(conn); + put_device(parent); +} + +static int iscsi_is_conn_dev(const struct device *dev) +{ + return dev->release == iscsi_conn_release; +} + +/** + * iscsi_create_conn - create iscsi class connection + * @session: iscsi cls session + * @cid: connection id + * + * This can be called from a LLD or iscsi_transport. The connection + * is child of the session so cid must be unique for all connections + * on the session. + * + * Since we do not support MCS, cid will normally be zero. In some cases + * for software iscsi we could be trying to preallocate a connection struct + * in which case there could be two connection structs and cid would be + * non-zero. + **/ +struct iscsi_cls_conn * +iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid) +{ + struct iscsi_transport *transport = session->transport; + struct iscsi_cls_conn *conn; + int err; + + conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL); + if (!conn) + return NULL; + + if (transport->conndata_size) + conn->dd_data = &conn[1]; + + INIT_LIST_HEAD(&conn->conn_list); + conn->transport = transport; + conn->cid = cid; + + /* this is released in the dev's release function */ + if (!get_device(&session->dev)) + goto free_conn; + + snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u", + session->sid, cid); + conn->dev.parent = &session->dev; + conn->dev.release = iscsi_conn_release; + err = device_register(&conn->dev); + if (err) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register " + "connection's dev\n"); + goto release_parent_ref; + } + transport_register_device(&conn->dev); + return conn; + +release_parent_ref: + put_device(&session->dev); +free_conn: + kfree(conn); + return NULL; +} + +EXPORT_SYMBOL_GPL(iscsi_create_conn); + +/** + * iscsi_destroy_conn - destroy iscsi class connection + * @session: iscsi cls session + * + * This can be called from a LLD or iscsi_transport. + **/ +int iscsi_destroy_conn(struct iscsi_cls_conn *conn) +{ + transport_unregister_device(&conn->dev); + device_unregister(&conn->dev); + return 0; +} + +EXPORT_SYMBOL_GPL(iscsi_destroy_conn); + +/* + * iscsi interface functions + */ +static struct iscsi_internal * +iscsi_if_transport_lookup(struct iscsi_transport *tt) +{ + struct iscsi_internal *priv; + unsigned long flags; + + spin_lock_irqsave(&iscsi_transport_lock, flags); + list_for_each_entry(priv, &iscsi_transports, list) { + if (tt == priv->iscsi_transport) { + spin_unlock_irqrestore(&iscsi_transport_lock, flags); + return priv; + } + } + spin_unlock_irqrestore(&iscsi_transport_lock, flags); + return NULL; +} + +static int +iscsi_broadcast_skb(struct sk_buff *skb, gfp_t gfp) +{ + int rc; + + rc = netlink_broadcast(nls, skb, 0, 1, gfp); + if (rc < 0) { + printk(KERN_ERR "iscsi: can not broadcast skb (%d)\n", rc); + return rc; + } + + return 0; +} + +static int +iscsi_unicast_skb(struct sk_buff *skb, int pid) +{ + int rc; + + rc = netlink_unicast(nls, skb, pid, MSG_DONTWAIT); + if (rc < 0) { + printk(KERN_ERR "iscsi: can not unicast skb (%d)\n", rc); + return rc; + } + + return 0; +} + +int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size) +{ + struct nlmsghdr *nlh; + struct sk_buff *skb; + struct iscsi_uevent *ev; + char *pdu; + struct iscsi_internal *priv; + int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) + + data_size); + + priv = iscsi_if_transport_lookup(conn->transport); + if (!priv) + return -EINVAL; + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED); + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver " + "control PDU: OOM\n"); + return -ENOMEM; + } + + nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0); + ev = NLMSG_DATA(nlh); + memset(ev, 0, sizeof(*ev)); + ev->transport_handle = iscsi_handle(conn->transport); + ev->type = ISCSI_KEVENT_RECV_PDU; + ev->r.recv_req.cid = conn->cid; + ev->r.recv_req.sid = iscsi_conn_get_sid(conn); + pdu = (char*)ev + sizeof(*ev); + memcpy(pdu, hdr, sizeof(struct iscsi_hdr)); + memcpy(pdu + sizeof(struct iscsi_hdr), data, data_size); + + return iscsi_unicast_skb(skb, priv->daemon_pid); +} +EXPORT_SYMBOL_GPL(iscsi_recv_pdu); + +void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error) +{ + struct nlmsghdr *nlh; + struct sk_buff *skb; + struct iscsi_uevent *ev; + struct iscsi_internal *priv; + int len = NLMSG_SPACE(sizeof(*ev)); + + priv = iscsi_if_transport_lookup(conn->transport); + if (!priv) + return; + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored " + "conn error (%d)\n", error); + return; + } + + nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0); + ev = NLMSG_DATA(nlh); + ev->transport_handle = iscsi_handle(conn->transport); + ev->type = ISCSI_KEVENT_CONN_ERROR; + ev->r.connerror.error = error; + ev->r.connerror.cid = conn->cid; + ev->r.connerror.sid = iscsi_conn_get_sid(conn); + + iscsi_broadcast_skb(skb, GFP_ATOMIC); + + dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n", + error); +} +EXPORT_SYMBOL_GPL(iscsi_conn_error); + +static int +iscsi_if_send_reply(int pid, int seq, int type, int done, int multi, + void *payload, int size) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + int len = NLMSG_SPACE(size); + int flags = multi ? NLM_F_MULTI : 0; + int t = done ? NLMSG_DONE : type; + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + printk(KERN_ERR "Could not allocate skb to send reply.\n"); + return -ENOMEM; + } + + nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0); + nlh->nlmsg_flags = flags; + memcpy(NLMSG_DATA(nlh), payload, size); + return iscsi_unicast_skb(skb, pid); +} + +static int +iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh) +{ + struct iscsi_uevent *ev = NLMSG_DATA(nlh); + struct iscsi_stats *stats; + struct sk_buff *skbstat; + struct iscsi_cls_conn *conn; + struct nlmsghdr *nlhstat; + struct iscsi_uevent *evstat; + struct iscsi_internal *priv; + int len = NLMSG_SPACE(sizeof(*ev) + + sizeof(struct iscsi_stats) + + sizeof(struct iscsi_stats_custom) * + ISCSI_STATS_CUSTOM_MAX); + int err = 0; + + priv = iscsi_if_transport_lookup(transport); + if (!priv) + return -EINVAL; + + conn = iscsi_conn_lookup(ev->u.get_stats.sid, ev->u.get_stats.cid); + if (!conn) + return -EEXIST; + + do { + int actual_size; + + skbstat = alloc_skb(len, GFP_ATOMIC); + if (!skbstat) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not " + "deliver stats: OOM\n"); + return -ENOMEM; + } + + nlhstat = __nlmsg_put(skbstat, priv->daemon_pid, 0, 0, + (len - sizeof(*nlhstat)), 0); + evstat = NLMSG_DATA(nlhstat); + memset(evstat, 0, sizeof(*evstat)); + evstat->transport_handle = iscsi_handle(conn->transport); + evstat->type = nlh->nlmsg_type; + evstat->u.get_stats.cid = + ev->u.get_stats.cid; + evstat->u.get_stats.sid = + ev->u.get_stats.sid; + stats = (struct iscsi_stats *) + ((char*)evstat + sizeof(*evstat)); + memset(stats, 0, sizeof(*stats)); + + transport->get_stats(conn, stats); + actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) + + sizeof(struct iscsi_stats) + + sizeof(struct iscsi_stats_custom) * + stats->custom_length); + actual_size -= sizeof(*nlhstat); + actual_size = NLMSG_LENGTH(actual_size); + skb_trim(skbstat, NLMSG_ALIGN(actual_size)); + nlhstat->nlmsg_len = actual_size; + + err = iscsi_unicast_skb(skbstat, priv->daemon_pid); + } while (err < 0 && err != -ECONNREFUSED); + + return err; +} + +/** + * iscsi_if_destroy_session_done - send session destr. completion event + * @conn: last connection for session + * + * This is called by HW iscsi LLDs to notify userpsace that its HW has + * removed a session. + **/ +int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn) +{ + struct iscsi_internal *priv; + struct iscsi_cls_session *session; + struct Scsi_Host *shost; + struct iscsi_uevent *ev; + struct sk_buff *skb; + struct nlmsghdr *nlh; + unsigned long flags; + int rc, len = NLMSG_SPACE(sizeof(*ev)); + + priv = iscsi_if_transport_lookup(conn->transport); + if (!priv) + return -EINVAL; + + session = iscsi_dev_to_session(conn->dev.parent); + shost = iscsi_session_to_shost(session); + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of " + "session creation event\n"); + return -ENOMEM; + } + + nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0); + ev = NLMSG_DATA(nlh); + ev->transport_handle = iscsi_handle(conn->transport); + ev->type = ISCSI_KEVENT_DESTROY_SESSION; + ev->r.d_session.host_no = shost->host_no; + ev->r.d_session.sid = session->sid; + + /* + * this will occur if the daemon is not up, so we just warn + * the user and when the daemon is restarted it will handle it + */ + rc = iscsi_broadcast_skb(skb, GFP_KERNEL); + if (rc < 0) + dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of " + "session destruction event. Check iscsi daemon\n"); + + spin_lock_irqsave(&sesslock, flags); + list_del(&session->sess_list); + spin_unlock_irqrestore(&sesslock, flags); + + spin_lock_irqsave(&connlock, flags); + conn->active = 0; + list_del(&conn->conn_list); + spin_unlock_irqrestore(&connlock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(iscsi_if_destroy_session_done); + +/** + * iscsi_if_create_session_done - send session creation completion event + * @conn: leading connection for session + * + * This is called by HW iscsi LLDs to notify userpsace that its HW has + * created a session or a existing session is back in the logged in state. + **/ +int iscsi_if_create_session_done(struct iscsi_cls_conn *conn) +{ + struct iscsi_internal *priv; + struct iscsi_cls_session *session; + struct Scsi_Host *shost; + struct iscsi_uevent *ev; + struct sk_buff *skb; + struct nlmsghdr *nlh; + unsigned long flags; + int rc, len = NLMSG_SPACE(sizeof(*ev)); + + priv = iscsi_if_transport_lookup(conn->transport); + if (!priv) + return -EINVAL; + + session = iscsi_dev_to_session(conn->dev.parent); + shost = iscsi_session_to_shost(session); + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of " + "session creation event\n"); + return -ENOMEM; + } + + nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0); + ev = NLMSG_DATA(nlh); + ev->transport_handle = iscsi_handle(conn->transport); + ev->type = ISCSI_UEVENT_CREATE_SESSION; + ev->r.c_session_ret.host_no = shost->host_no; + ev->r.c_session_ret.sid = session->sid; + + /* + * this will occur if the daemon is not up, so we just warn + * the user and when the daemon is restarted it will handle it + */ + rc = iscsi_broadcast_skb(skb, GFP_KERNEL); + if (rc < 0) + dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of " + "session creation event. Check iscsi daemon\n"); + + spin_lock_irqsave(&sesslock, flags); + list_add(&session->sess_list, &sesslist); + spin_unlock_irqrestore(&sesslock, flags); + + spin_lock_irqsave(&connlock, flags); + list_add(&conn->conn_list, &connlist); + conn->active = 1; + spin_unlock_irqrestore(&connlock, flags); + return rc; +} +EXPORT_SYMBOL_GPL(iscsi_if_create_session_done); + +static int +iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev) +{ + struct iscsi_transport *transport = priv->iscsi_transport; + struct iscsi_cls_session *session; + unsigned long flags; + uint32_t hostno; + + session = transport->create_session(transport, &priv->t, + ev->u.c_session.initial_cmdsn, + &hostno); + if (!session) + return -ENOMEM; + + spin_lock_irqsave(&sesslock, flags); + list_add(&session->sess_list, &sesslist); + spin_unlock_irqrestore(&sesslock, flags); + + ev->r.c_session_ret.host_no = hostno; + ev->r.c_session_ret.sid = session->sid; + return 0; +} + +static int +iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev) +{ + struct iscsi_cls_conn *conn; + struct iscsi_cls_session *session; + unsigned long flags; + + session = iscsi_session_lookup(ev->u.c_conn.sid); + if (!session) { + printk(KERN_ERR "iscsi: invalid session %d\n", + ev->u.c_conn.sid); + return -EINVAL; + } + + conn = transport->create_conn(session, ev->u.c_conn.cid); + if (!conn) { + printk(KERN_ERR "iscsi: couldn't create a new " + "connection for session %d\n", + session->sid); + return -ENOMEM; + } + + ev->r.c_conn_ret.sid = session->sid; + ev->r.c_conn_ret.cid = conn->cid; + + spin_lock_irqsave(&connlock, flags); + list_add(&conn->conn_list, &connlist); + conn->active = 1; + spin_unlock_irqrestore(&connlock, flags); + + return 0; +} + +static int +iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev) +{ + unsigned long flags; + struct iscsi_cls_conn *conn; + + conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid); + if (!conn) + return -EINVAL; + spin_lock_irqsave(&connlock, flags); + conn->active = 0; + list_del(&conn->conn_list); + spin_unlock_irqrestore(&connlock, flags); + + if (transport->destroy_conn) + transport->destroy_conn(conn); + return 0; +} + +static int +iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev) +{ + char *data = (char*)ev + sizeof(*ev); + struct iscsi_cls_conn *conn; + struct iscsi_cls_session *session; + int err = 0, value = 0; + + session = iscsi_session_lookup(ev->u.set_param.sid); + conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid); + if (!conn || !session) + return -EINVAL; + + switch (ev->u.set_param.param) { + case ISCSI_PARAM_SESS_RECOVERY_TMO: + sscanf(data, "%d", &value); + if (value != 0) + session->recovery_tmo = value; + break; + default: + err = transport->set_param(conn, ev->u.set_param.param, + data, ev->u.set_param.len); + } + + return err; +} + +static int +iscsi_if_transport_ep(struct iscsi_transport *transport, + struct iscsi_uevent *ev, int msg_type) +{ + struct sockaddr *dst_addr; + int rc = 0; + + switch (msg_type) { + case ISCSI_UEVENT_TRANSPORT_EP_CONNECT: + if (!transport->ep_connect) + return -EINVAL; + + dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev)); + rc = transport->ep_connect(dst_addr, + ev->u.ep_connect.non_blocking, + &ev->r.ep_connect_ret.handle); + break; + case ISCSI_UEVENT_TRANSPORT_EP_POLL: + if (!transport->ep_poll) + return -EINVAL; + + ev->r.retcode = transport->ep_poll(ev->u.ep_poll.ep_handle, + ev->u.ep_poll.timeout_ms); + break; + case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT: + if (!transport->ep_disconnect) + return -EINVAL; + + transport->ep_disconnect(ev->u.ep_disconnect.ep_handle); + break; + } + return rc; +} + +static int +iscsi_tgt_dscvr(struct iscsi_transport *transport, + struct iscsi_uevent *ev) +{ + struct sockaddr *dst_addr; + + if (!transport->tgt_dscvr) + return -EINVAL; + + dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev)); + return transport->tgt_dscvr(ev->u.tgt_dscvr.type, + ev->u.tgt_dscvr.host_no, + ev->u.tgt_dscvr.enable, dst_addr); +} + +static int +iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + int err = 0; + struct iscsi_uevent *ev = NLMSG_DATA(nlh); + struct iscsi_transport *transport = NULL; + struct iscsi_internal *priv; + struct iscsi_cls_session *session; + struct iscsi_cls_conn *conn; + unsigned long flags; + + priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle)); + if (!priv) + return -EINVAL; + transport = priv->iscsi_transport; + + if (!try_module_get(transport->owner)) + return -EINVAL; + + priv->daemon_pid = NETLINK_CREDS(skb)->pid; + + switch (nlh->nlmsg_type) { + case ISCSI_UEVENT_CREATE_SESSION: + err = iscsi_if_create_session(priv, ev); + break; + case ISCSI_UEVENT_DESTROY_SESSION: + session = iscsi_session_lookup(ev->u.d_session.sid); + if (session) { + spin_lock_irqsave(&sesslock, flags); + list_del(&session->sess_list); + spin_unlock_irqrestore(&sesslock, flags); + + transport->destroy_session(session); + } else + err = -EINVAL; + break; + case ISCSI_UEVENT_CREATE_CONN: + err = iscsi_if_create_conn(transport, ev); + break; + case ISCSI_UEVENT_DESTROY_CONN: + err = iscsi_if_destroy_conn(transport, ev); + break; + case ISCSI_UEVENT_BIND_CONN: + session = iscsi_session_lookup(ev->u.b_conn.sid); + conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid); + + if (session && conn) + ev->r.retcode = transport->bind_conn(session, conn, + ev->u.b_conn.transport_eph, + ev->u.b_conn.is_leading); + else + err = -EINVAL; + break; + case ISCSI_UEVENT_SET_PARAM: + err = iscsi_set_param(transport, ev); + break; + case ISCSI_UEVENT_START_CONN: + conn = iscsi_conn_lookup(ev->u.start_conn.sid, ev->u.start_conn.cid); + if (conn) + ev->r.retcode = transport->start_conn(conn); + else + err = -EINVAL; + break; + case ISCSI_UEVENT_STOP_CONN: + conn = iscsi_conn_lookup(ev->u.stop_conn.sid, ev->u.stop_conn.cid); + if (conn) + transport->stop_conn(conn, ev->u.stop_conn.flag); + else + err = -EINVAL; + break; + case ISCSI_UEVENT_SEND_PDU: + conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid); + if (conn) + ev->r.retcode = transport->send_pdu(conn, + (struct iscsi_hdr*)((char*)ev + sizeof(*ev)), + (char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size, + ev->u.send_pdu.data_size); + else + err = -EINVAL; + break; + case ISCSI_UEVENT_GET_STATS: + err = iscsi_if_get_stats(transport, nlh); + break; + case ISCSI_UEVENT_TRANSPORT_EP_CONNECT: + case ISCSI_UEVENT_TRANSPORT_EP_POLL: + case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT: + err = iscsi_if_transport_ep(transport, ev, nlh->nlmsg_type); + break; + case ISCSI_UEVENT_TGT_DSCVR: + err = iscsi_tgt_dscvr(transport, ev); + break; + default: + err = -EINVAL; + break; + } + + module_put(transport->owner); + return err; +} + +/* + * Get message from skb (based on rtnetlink_rcv_skb). Each message is + * processed by iscsi_if_recv_msg. Malformed skbs with wrong lengths or + * invalid creds are discarded silently. + */ +static void +iscsi_if_rx(struct sock *sk, int len) +{ + struct sk_buff *skb; + + mutex_lock(&rx_queue_mutex); + while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { + if (NETLINK_CREDS(skb)->uid) { + skb_pull(skb, skb->len); + goto free_skb; + } + + while (skb->len >= NLMSG_SPACE(0)) { + int err; + uint32_t rlen; + struct nlmsghdr *nlh; + struct iscsi_uevent *ev; + + nlh = (struct nlmsghdr *)skb->data; + if (nlh->nlmsg_len < sizeof(*nlh) || + skb->len < nlh->nlmsg_len) { + break; + } + + ev = NLMSG_DATA(nlh); + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + + err = iscsi_if_recv_msg(skb, nlh); + if (err) { + ev->type = ISCSI_KEVENT_IF_ERROR; + ev->iferror = err; + } + do { + /* + * special case for GET_STATS: + * on success - sending reply and stats from + * inside of if_recv_msg(), + * on error - fall through. + */ + if (ev->type == ISCSI_UEVENT_GET_STATS && !err) + break; + err = iscsi_if_send_reply( + NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq, + nlh->nlmsg_type, 0, 0, ev, sizeof(*ev)); + } while (err < 0 && err != -ECONNREFUSED); + skb_pull(skb, rlen); + } +free_skb: + kfree_skb(skb); + } + mutex_unlock(&rx_queue_mutex); +} + +#define iscsi_cdev_to_conn(_cdev) \ + iscsi_dev_to_conn(_cdev->dev) + +#define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store) \ +struct class_device_attribute class_device_attr_##_prefix##_##_name = \ + __ATTR(_name,_mode,_show,_store) + +/* + * iSCSI connection attrs + */ +#define iscsi_conn_attr_show(param) \ +static ssize_t \ +show_conn_param_##param(struct class_device *cdev, char *buf) \ +{ \ + struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev); \ + struct iscsi_transport *t = conn->transport; \ + return t->get_conn_param(conn, param, buf); \ +} + +#define iscsi_conn_attr(field, param) \ + iscsi_conn_attr_show(param) \ +static ISCSI_CLASS_ATTR(conn, field, S_IRUGO, show_conn_param_##param, \ + NULL); + +iscsi_conn_attr(max_recv_dlength, ISCSI_PARAM_MAX_RECV_DLENGTH); +iscsi_conn_attr(max_xmit_dlength, ISCSI_PARAM_MAX_XMIT_DLENGTH); +iscsi_conn_attr(header_digest, ISCSI_PARAM_HDRDGST_EN); +iscsi_conn_attr(data_digest, ISCSI_PARAM_DATADGST_EN); +iscsi_conn_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN); +iscsi_conn_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN); +iscsi_conn_attr(persistent_port, ISCSI_PARAM_PERSISTENT_PORT); +iscsi_conn_attr(port, ISCSI_PARAM_CONN_PORT); +iscsi_conn_attr(exp_statsn, ISCSI_PARAM_EXP_STATSN); +iscsi_conn_attr(persistent_address, ISCSI_PARAM_PERSISTENT_ADDRESS); +iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS); + +#define iscsi_cdev_to_session(_cdev) \ + iscsi_dev_to_session(_cdev->dev) + +/* + * iSCSI session attrs + */ +#define iscsi_session_attr_show(param) \ +static ssize_t \ +show_session_param_##param(struct class_device *cdev, char *buf) \ +{ \ + struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \ + struct iscsi_transport *t = session->transport; \ + return t->get_session_param(session, param, buf); \ +} + +#define iscsi_session_attr(field, param) \ + iscsi_session_attr_show(param) \ +static ISCSI_CLASS_ATTR(sess, field, S_IRUGO, show_session_param_##param, \ + NULL); + +iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME); +iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN); +iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T); +iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN); +iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST); +iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST); +iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN); +iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN); +iscsi_session_attr(erl, ISCSI_PARAM_ERL); +iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT); + +#define iscsi_priv_session_attr_show(field, format) \ +static ssize_t \ +show_priv_session_##field(struct class_device *cdev, char *buf) \ +{ \ + struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\ + return sprintf(buf, format"\n", session->field); \ +} + +#define iscsi_priv_session_attr(field, format) \ + iscsi_priv_session_attr_show(field, format) \ +static ISCSI_CLASS_ATTR(priv_sess, field, S_IRUGO, show_priv_session_##field, \ + NULL) +iscsi_priv_session_attr(recovery_tmo, "%d"); + +#define SETUP_PRIV_SESSION_RD_ATTR(field) \ +do { \ + priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \ + count++; \ +} while (0) + + +#define SETUP_SESSION_RD_ATTR(field, param_flag) \ +do { \ + if (tt->param_mask & param_flag) { \ + priv->session_attrs[count] = &class_device_attr_sess_##field; \ + count++; \ + } \ +} while (0) + +#define SETUP_CONN_RD_ATTR(field, param_flag) \ +do { \ + if (tt->param_mask & param_flag) { \ + priv->conn_attrs[count] = &class_device_attr_conn_##field; \ + count++; \ + } \ +} while (0) + +static int iscsi_session_match(struct attribute_container *cont, + struct device *dev) +{ + struct iscsi_cls_session *session; + struct Scsi_Host *shost; + struct iscsi_internal *priv; + + if (!iscsi_is_session_dev(dev)) + return 0; + + session = iscsi_dev_to_session(dev); + shost = iscsi_session_to_shost(session); + if (!shost->transportt) + return 0; + + priv = to_iscsi_internal(shost->transportt); + if (priv->session_cont.ac.class != &iscsi_session_class.class) + return 0; + + return &priv->session_cont.ac == cont; +} + +static int iscsi_conn_match(struct attribute_container *cont, + struct device *dev) +{ + struct iscsi_cls_session *session; + struct iscsi_cls_conn *conn; + struct Scsi_Host *shost; + struct iscsi_internal *priv; + + if (!iscsi_is_conn_dev(dev)) + return 0; + + conn = iscsi_dev_to_conn(dev); + session = iscsi_dev_to_session(conn->dev.parent); + shost = iscsi_session_to_shost(session); + + if (!shost->transportt) + return 0; + + priv = to_iscsi_internal(shost->transportt); + if (priv->conn_cont.ac.class != &iscsi_connection_class.class) + return 0; + + return &priv->conn_cont.ac == cont; +} + +static int iscsi_host_match(struct attribute_container *cont, + struct device *dev) +{ + struct Scsi_Host *shost; + struct iscsi_internal *priv; + + if (!scsi_is_host_device(dev)) + return 0; + + shost = dev_to_shost(dev); + if (!shost->transportt || + shost->transportt->host_attrs.ac.class != &iscsi_host_class.class) + return 0; + + priv = to_iscsi_internal(shost->transportt); + return &priv->t.host_attrs.ac == cont; +} + +struct scsi_transport_template * +iscsi_register_transport(struct iscsi_transport *tt) +{ + struct iscsi_internal *priv; + unsigned long flags; + int count = 0, err; + + BUG_ON(!tt); + + priv = iscsi_if_transport_lookup(tt); + if (priv) + return NULL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return NULL; + INIT_LIST_HEAD(&priv->list); + priv->daemon_pid = -1; + priv->iscsi_transport = tt; + priv->t.user_scan = iscsi_user_scan; + + priv->cdev.class = &iscsi_transport_class; + snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name); + err = class_device_register(&priv->cdev); + if (err) + goto free_priv; + + err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group); + if (err) + goto unregister_cdev; + + /* host parameters */ + priv->t.host_attrs.ac.attrs = &priv->host_attrs[0]; + priv->t.host_attrs.ac.class = &iscsi_host_class.class; + priv->t.host_attrs.ac.match = iscsi_host_match; + priv->t.host_size = sizeof(struct iscsi_host); + priv->host_attrs[0] = NULL; + transport_container_register(&priv->t.host_attrs); + + /* connection parameters */ + priv->conn_cont.ac.attrs = &priv->conn_attrs[0]; + priv->conn_cont.ac.class = &iscsi_connection_class.class; + priv->conn_cont.ac.match = iscsi_conn_match; + transport_container_register(&priv->conn_cont); + + SETUP_CONN_RD_ATTR(max_recv_dlength, ISCSI_MAX_RECV_DLENGTH); + SETUP_CONN_RD_ATTR(max_xmit_dlength, ISCSI_MAX_XMIT_DLENGTH); + SETUP_CONN_RD_ATTR(header_digest, ISCSI_HDRDGST_EN); + SETUP_CONN_RD_ATTR(data_digest, ISCSI_DATADGST_EN); + SETUP_CONN_RD_ATTR(ifmarker, ISCSI_IFMARKER_EN); + SETUP_CONN_RD_ATTR(ofmarker, ISCSI_OFMARKER_EN); + SETUP_CONN_RD_ATTR(address, ISCSI_CONN_ADDRESS); + SETUP_CONN_RD_ATTR(port, ISCSI_CONN_PORT); + SETUP_CONN_RD_ATTR(exp_statsn, ISCSI_EXP_STATSN); + SETUP_CONN_RD_ATTR(persistent_address, ISCSI_PERSISTENT_ADDRESS); + SETUP_CONN_RD_ATTR(persistent_port, ISCSI_PERSISTENT_PORT); + + BUG_ON(count > ISCSI_CONN_ATTRS); + priv->conn_attrs[count] = NULL; + count = 0; + + /* session parameters */ + priv->session_cont.ac.attrs = &priv->session_attrs[0]; + priv->session_cont.ac.class = &iscsi_session_class.class; + priv->session_cont.ac.match = iscsi_session_match; + transport_container_register(&priv->session_cont); + + SETUP_SESSION_RD_ATTR(initial_r2t, ISCSI_INITIAL_R2T_EN); + SETUP_SESSION_RD_ATTR(max_outstanding_r2t, ISCSI_MAX_R2T); + SETUP_SESSION_RD_ATTR(immediate_data, ISCSI_IMM_DATA_EN); + SETUP_SESSION_RD_ATTR(first_burst_len, ISCSI_FIRST_BURST); + SETUP_SESSION_RD_ATTR(max_burst_len, ISCSI_MAX_BURST); + SETUP_SESSION_RD_ATTR(data_pdu_in_order, ISCSI_PDU_INORDER_EN); + SETUP_SESSION_RD_ATTR(data_seq_in_order, ISCSI_DATASEQ_INORDER_EN); + SETUP_SESSION_RD_ATTR(erl, ISCSI_ERL); + SETUP_SESSION_RD_ATTR(targetname, ISCSI_TARGET_NAME); + SETUP_SESSION_RD_ATTR(tpgt, ISCSI_TPGT); + SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo); + + BUG_ON(count > ISCSI_SESSION_ATTRS); + priv->session_attrs[count] = NULL; + + spin_lock_irqsave(&iscsi_transport_lock, flags); + list_add(&priv->list, &iscsi_transports); + spin_unlock_irqrestore(&iscsi_transport_lock, flags); + + printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name); + return &priv->t; + +unregister_cdev: + class_device_unregister(&priv->cdev); +free_priv: + kfree(priv); + return NULL; +} +EXPORT_SYMBOL_GPL(iscsi_register_transport); + +int iscsi_unregister_transport(struct iscsi_transport *tt) +{ + struct iscsi_internal *priv; + unsigned long flags; + + BUG_ON(!tt); + + mutex_lock(&rx_queue_mutex); + + priv = iscsi_if_transport_lookup(tt); + BUG_ON (!priv); + + spin_lock_irqsave(&iscsi_transport_lock, flags); + list_del(&priv->list); + spin_unlock_irqrestore(&iscsi_transport_lock, flags); + + transport_container_unregister(&priv->conn_cont); + transport_container_unregister(&priv->session_cont); + transport_container_unregister(&priv->t.host_attrs); + + sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group); + class_device_unregister(&priv->cdev); + mutex_unlock(&rx_queue_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_unregister_transport); + +static __init int iscsi_transport_init(void) +{ + int err; + + printk(KERN_INFO "Loading iSCSI transport class v%s.\n", + ISCSI_TRANSPORT_VERSION); + + atomic_set(&iscsi_session_nr, 0); + + err = class_register(&iscsi_transport_class); + if (err) + return err; + + err = transport_class_register(&iscsi_host_class); + if (err) + goto unregister_transport_class; + + err = transport_class_register(&iscsi_connection_class); + if (err) + goto unregister_host_class; + + err = transport_class_register(&iscsi_session_class); + if (err) + goto unregister_conn_class; + + nls = netlink_kernel_create(NETLINK_ISCSI, 1, iscsi_if_rx, + THIS_MODULE); + if (!nls) { + err = -ENOBUFS; + goto unregister_session_class; + } + + return 0; + +unregister_session_class: + transport_class_unregister(&iscsi_session_class); +unregister_conn_class: + transport_class_unregister(&iscsi_connection_class); +unregister_host_class: + transport_class_unregister(&iscsi_host_class); +unregister_transport_class: + class_unregister(&iscsi_transport_class); + return err; +} + +static void __exit iscsi_transport_exit(void) +{ + sock_release(nls->sk_socket); + transport_class_unregister(&iscsi_connection_class); + transport_class_unregister(&iscsi_session_class); + transport_class_unregister(&iscsi_host_class); + class_unregister(&iscsi_transport_class); +} + +module_init(iscsi_transport_init); +module_exit(iscsi_transport_exit); + +MODULE_AUTHOR("Mike Christie , " + "Dmitry Yusupov , " + "Alex Aizman "); +MODULE_DESCRIPTION("iSCSI Transport Interface"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISCSI_TRANSPORT_VERSION); Index: kernel/Makefile =================================================================== --- kernel/Makefile (revision 754) +++ kernel/Makefile (revision 779) @@ -3,7 +3,7 @@ # # This Makefile invokes KBuild from outside the kernel directory when # used from the main open-iscsi package. It also contains all of the -# KBuild stuff needed to build the the modules. +# KBuild stuff needed to build the modules. # # Kbuild stuff, the following is the only part of this file that KBuild @@ -36,84 +36,119 @@ # this is the basic Kbuild invocation, just append your make target KBUILD_BASE = +$(MAKE) -C $(KSRC) M=`pwd` KBUILD_OUTPUT=$(KBUILD_OUTPUT) $(KARCH) V=$(V) -# fun stuff for maintaining multiple versions +all: kernel_check + $(KBUILD_BASE) modules + +# ============ BEGIN code for kernel_check and source patching ================ +# We calculate the Linux version for compilation. Than according to version, +# if needed, we patch source-code to match the compiling kernel. +# if you need a new kernel sub-version just add a target below. +# +# IMPORTANT: do "make clean" before submitting to SVN so source is in unpatched +# form. + +#some constants +13_patch=2.6.13_compat.patch +14to15_patch=2.6.14-and-2.6.15-compat.patch +16to18_patch=2.6.16-18_compat.patch +19_patch=2.6.19_compat.patch +all_patches=13_patch 14to15_patch 16to18_patch 19_patch +cur_patched=cur_patched + +## fun stuff for maintaining multiple versions + +# check to see if code is unpatched +unpatch_code=$(shell test -e $(cur_patched) && echo do_unpatch_code ) + KSUBLEVEL = $(shell cat $(KSRC)/Makefile | awk -F= '/^SUBLEVEL =/ {print $$2}' | \ sed 's/^[ \t]*//;s/[ \t]*$$//') -ifeq ($(KSUBLEVEL), 11) -EXTRA_CFLAGS += -DNETLINK_ISCSI=12 -else -ifeq ($(KSUBLEVEL), 12) -EXTRA_CFLAGS += -DNETLINK_ISCSI=12 -endif -endif +KERNEL_TARGET=linux_2_6_$(KSUBLEVEL) +kernel_check: $(KERNEL_TARGET) -all: kernel_check - $(KBUILD_BASE) modules -# ====== BEGIN code for kernel_check +linux_2_6_13: has_13_patch +linux_2_6_14: has_14to15_patch -# generic make function -# syntax: -# @$(call generic_check_command, CHECKING-COMMAND, PATCHNAME) -generic_check_command = if $(1) ; then \ - echo "kernel check... FAILED"; \ - echo "Apply the $(2) first!"; exit 1; fi ; \ - echo "kernel check... OK" +linux_2_6_15: has_14to15_patch -# this variable defines how we check for iscsi_compat -check_iscsi_compat_command = ! grep iscsi_compat scsi_transport_iscsi.c >/dev/null -# this variable is the actual make function -check_iscsi_compat = $(call generic_check_command, $(check_iscsi_compat_command), $(1)) -# syntax: -# @$(call check_iscsi_compat, PATCHNAME) +linux_2_6_16: has_16to18_patch -# this if-else ladder is horrible -# but I don't know any quick way to clean it up -# since Make doesn't support a switch construct -.NOTPARALLEL: kernel_check -kernel_check: -ifeq ($(KSUBLEVEL),16) - echo "kernel check... OK" - cp 2.6.16-2.6.19/*.c . - cp 2.6.16-2.6.19/*.h . -else -ifeq ($(KSUBLEVEL),17) - echo "kernel check... OK" - cp 2.6.16-2.6.19/*.c . - cp 2.6.16-2.6.19/*.h . -else -ifeq ($(KSUBLEVEL),18) - echo "kernel check... OK" - cp 2.6.16-2.6.19/*.c . - cp 2.6.16-2.6.19/*.h . -else -ifeq ($(KSUBLEVEL),19) - echo "kernel check... OK" - cp 2.6.16-2.6.19/*.c . - cp 2.6.16-2.6.19/*.h . -else -ifeq ($(KSUBLEVEL),20) - echo "kernel check... OK" - cp 2.6.20/*.c . - cp 2.6.20/*.h . -else - @echo "kernel check... UNSUPPORTED KERNEL DETECTED" - exit 1; -endif -endif -endif -endif -endif +linux_2_6_17: has_16to18_patch -# ====== END code for kernel_check +linux_2_6_18: has_16to18_patch -clean: +linux_2_6_19: has_19_patch + +linux_2_6_20: $(unpatch_code) + +linux_2_6_21: $(unpatch_code) + +do_unpatch_code: + echo "Un-patching source code for use with linux-2.6.20 and up ..." + patch -R -E -p1 < $(cur_patched) + rm -f `readlink $(cur_patched)` + rm -f $(cur_patched) + +# these below targets must be the same as the variable name prefixed by has_ +# otherwise below compat_patch: target will not work +has_13_patch: $(13_patch) + echo "Patching source code for linux-2.6.13 ..." + if [ -e $(cur_patched) ]; then \ + make -C . clean; \ + fi + patch -p1 < $(13_patch) + cp $(13_patch) $@ + ln -s $@ $(cur_patched) + +has_14to15_patch: $(14to15_patch) + echo "Patching source code for linux-2.6.14-15 ..." + if [ -e $(cur_patched) ]; then \ + make -C . clean; \ + fi + patch -p1 < $(14to15_patch) + cp $(14to15_patch) $@ + ln -s $@ $(cur_patched) + +has_16to18_patch: $(16to18_patch) + echo "Patching source code for linux-2.6.16-18 ..." + if [ -e $(cur_patched) ]; then \ + make -C . clean; \ + fi + patch -p1 < $(16to18_patch) + cp $(16to18_patch) $@ + ln -s $@ $(cur_patched) + +has_19_patch: $(19_patch) + echo "Patching source code for linux-2.6.19 ..." + if [ -e $(cur_patched) ]; then \ + make -C . clean; \ + fi + patch -p1 < $(19_patch) + cp $(19_patch) $@ + ln -s $@ $(cur_patched) + +# ============ END code for kernel_check and source patching ================= + +clean: $(unpatch_code) $(KBUILD_BASE) clean - rm *.c *.h Module.symvers -#echo rm -f -r *.mod.c .*cmd *.o *.ko .tmp_versions + rm -f Module.symvers +## The folowing compat_patch target is what we need to do to prepare a clean +# compat_patch set after new code is check-in to svn. To keep patches fuzzless. +# the new patches are writen into .new files so svn diff of next file will +# not trip on them. +compat_patch: $(unpatch_code) + test -z "$(svn diff|head)" || { \ + echo "please run make compat_patch after changse are submited to svn"; \ + exit 1; \ + } + for the_patch in all_patches ; do \ + make -C . has_$(the_patch); \ + svn diff > ${!the_patch}.new; \ + done + # the following is only for convienience # do not submit to Linus # it's also called from the toplevel makefile Index: kernel/scsi_transport_iscsi.h =================================================================== --- kernel/scsi_transport_iscsi.h (revision 0) +++ kernel/scsi_transport_iscsi.h (revision 779) @@ -0,0 +1,224 @@ +/* + * iSCSI transport class definitions + * + * Copyright (C) IBM Corporation, 2004 + * Copyright (C) Mike Christie, 2004 - 2006 + * Copyright (C) Dmitry Yusupov, 2004 - 2005 + * Copyright (C) Alex Aizman, 2004 - 2005 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef SCSI_TRANSPORT_ISCSI_H +#define SCSI_TRANSPORT_ISCSI_H + +#include +#include "iscsi_if.h" + +struct scsi_transport_template; +struct iscsi_transport; +struct Scsi_Host; +struct iscsi_cls_conn; +struct iscsi_conn; +struct iscsi_cmd_task; +struct iscsi_mgmt_task; +struct sockaddr; + +/** + * struct iscsi_transport - iSCSI Transport template + * + * @name: transport name + * @caps: iSCSI Data-Path capabilities + * @create_session: create new iSCSI session object + * @destroy_session: destroy existing iSCSI session object + * @create_conn: create new iSCSI connection + * @bind_conn: associate this connection with existing iSCSI session + * and specified transport descriptor + * @destroy_conn: destroy inactive iSCSI connection + * @set_param: set iSCSI parameter. Return 0 on success, -ENODATA + * when param is not supported, and a -Exx value on other + * error. + * @get_param get iSCSI parameter. Must return number of bytes + * copied to buffer on success, -ENODATA when param + * is not supported, and a -Exx value on other error + * @start_conn: set connection to be operational + * @stop_conn: suspend/recover/terminate connection + * @send_pdu: send iSCSI PDU, Login, Logout, NOP-Out, Reject, Text. + * @session_recovery_timedout: notify LLD a block during recovery timed out + * @init_cmd_task: Initialize a iscsi_cmd_task and any internal structs. + * Called from queuecommand with session lock held. + * @init_mgmt_task: Initialize a iscsi_mgmt_task and any internal structs. + * Called from iscsi_conn_send_generic with xmitmutex. + * @xmit_cmd_task: Requests LLD to transfer cmd task. Returns 0 or the + * the number of bytes transferred on success, and -Exyz + * value on error. + * @xmit_mgmt_task: Requests LLD to transfer mgmt task. Returns 0 or the + * the number of bytes transferred on success, and -Exyz + * value on error. + * @cleanup_cmd_task: requests LLD to fail cmd task. Called with xmitmutex + * and session->lock after the connection has been + * suspended and terminated during recovery. If called + * from abort task then connection is not suspended + * or terminated but sk_callback_lock is held + * + * Template API provided by iSCSI Transport + */ +struct iscsi_transport { + struct module *owner; + char *name; + unsigned int caps; + /* LLD sets this to indicate what values it can export to sysfs */ + unsigned int param_mask; + struct scsi_host_template *host_template; + /* LLD connection data size */ + int conndata_size; + /* LLD session data size */ + int sessiondata_size; + int max_lun; + unsigned int max_conn; + unsigned int max_cmd_len; + struct iscsi_cls_session *(*create_session) (struct iscsi_transport *it, + struct scsi_transport_template *t, uint32_t sn, uint32_t *hn); + void (*destroy_session) (struct iscsi_cls_session *session); + struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess, + uint32_t cid); + int (*bind_conn) (struct iscsi_cls_session *session, + struct iscsi_cls_conn *cls_conn, + uint64_t transport_eph, int is_leading); + int (*start_conn) (struct iscsi_cls_conn *conn); + void (*stop_conn) (struct iscsi_cls_conn *conn, int flag); + void (*destroy_conn) (struct iscsi_cls_conn *conn); + int (*set_param) (struct iscsi_cls_conn *conn, enum iscsi_param param, + char *buf, int buflen); + int (*get_conn_param) (struct iscsi_cls_conn *conn, + enum iscsi_param param, char *buf); + int (*get_session_param) (struct iscsi_cls_session *session, + enum iscsi_param param, char *buf); + int (*send_pdu) (struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size); + void (*get_stats) (struct iscsi_cls_conn *conn, + struct iscsi_stats *stats); + void (*init_cmd_task) (struct iscsi_cmd_task *ctask); + void (*init_mgmt_task) (struct iscsi_conn *conn, + struct iscsi_mgmt_task *mtask, + char *data, uint32_t data_size); + int (*xmit_cmd_task) (struct iscsi_conn *conn, + struct iscsi_cmd_task *ctask); + void (*cleanup_cmd_task) (struct iscsi_conn *conn, + struct iscsi_cmd_task *ctask); + int (*xmit_mgmt_task) (struct iscsi_conn *conn, + struct iscsi_mgmt_task *mtask); + void (*session_recovery_timedout) (struct iscsi_cls_session *session); + int (*ep_connect) (struct sockaddr *dst_addr, int non_blocking, + uint64_t *ep_handle); + int (*ep_poll) (uint64_t ep_handle, int timeout_ms); + void (*ep_disconnect) (uint64_t ep_handle); + int (*tgt_dscvr) (enum iscsi_tgt_dscvr type, uint32_t host_no, + uint32_t enable, struct sockaddr *dst_addr); +}; + +/* + * transport registration upcalls + */ +extern struct scsi_transport_template *iscsi_register_transport(struct iscsi_transport *tt); +extern int iscsi_unregister_transport(struct iscsi_transport *tt); + +/* + * control plane upcalls + */ +extern void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error); +extern int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size); + + +/* Connection's states */ +#define ISCSI_CONN_INITIAL_STAGE 0 +#define ISCSI_CONN_STARTED 1 +#define ISCSI_CONN_STOPPED 2 +#define ISCSI_CONN_CLEANUP_WAIT 3 + +struct iscsi_cls_conn { + struct list_head conn_list; /* item in connlist */ + void *dd_data; /* LLD private data */ + struct iscsi_transport *transport; + uint32_t cid; /* connection id */ + + int active; /* must be accessed with the connlock */ + struct device dev; /* sysfs transport/container device */ +}; + +#define iscsi_dev_to_conn(_dev) \ + container_of(_dev, struct iscsi_cls_conn, dev) + +/* Session's states */ +#define ISCSI_STATE_FREE 1 +#define ISCSI_STATE_LOGGED_IN 2 +#define ISCSI_STATE_FAILED 3 +#define ISCSI_STATE_TERMINATE 4 +#define ISCSI_STATE_IN_RECOVERY 5 +#define ISCSI_STATE_RECOVERY_FAILED 6 + +struct iscsi_cls_session { + struct list_head sess_list; /* item in session_list */ + struct list_head host_list; + struct iscsi_transport *transport; + + /* recovery fields */ + int recovery_tmo; + struct delayed_work recovery_work; + + int target_id; + + int sid; /* session id */ + void *dd_data; /* LLD private data */ + struct device dev; /* sysfs transport/container device */ +}; + +#define iscsi_dev_to_session(_dev) \ + container_of(_dev, struct iscsi_cls_session, dev) + +#define iscsi_session_to_shost(_session) \ + dev_to_shost(_session->dev.parent) + +#define starget_to_session(_stgt) \ + iscsi_dev_to_session(_stgt->dev.parent) + +struct iscsi_host { + struct list_head sessions; + struct mutex mutex; +}; + +/* + * session and connection functions that can be used by HW iSCSI LLDs + */ +extern struct iscsi_cls_session *iscsi_alloc_session(struct Scsi_Host *shost, + struct iscsi_transport *transport); +extern int iscsi_add_session(struct iscsi_cls_session *session, + unsigned int target_id); +extern int iscsi_if_create_session_done(struct iscsi_cls_conn *conn); +extern int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn); +extern struct iscsi_cls_session *iscsi_create_session(struct Scsi_Host *shost, + struct iscsi_transport *t, + unsigned int target_id); +extern void iscsi_remove_session(struct iscsi_cls_session *session); +extern void iscsi_free_session(struct iscsi_cls_session *session); +extern int iscsi_destroy_session(struct iscsi_cls_session *session); +extern struct iscsi_cls_conn *iscsi_create_conn(struct iscsi_cls_session *sess, + uint32_t cid); +extern int iscsi_destroy_conn(struct iscsi_cls_conn *conn); +extern void iscsi_unblock_session(struct iscsi_cls_session *session); +extern void iscsi_block_session(struct iscsi_cls_session *session); + + +#endif Index: kernel/2.6.19_compat.patch =================================================================== --- kernel/2.6.19_compat.patch (revision 0) +++ kernel/2.6.19_compat.patch (revision 779) @@ -0,0 +1,74 @@ +diff -Nurp a/libiscsi.c b/libiscsi.c +--- a/libiscsi.c 2007-02-21 11:57:49.000000000 +0200 ++++ b/libiscsi.c 2007-02-21 11:57:49.000000000 +0200 +@@ -716,10 +716,9 @@ again: + return rc; + } + +-static void iscsi_xmitworker(struct work_struct *work) ++static void iscsi_xmitworker(void *data) + { +- struct iscsi_conn *conn = +- container_of(work, struct iscsi_conn, xmitwork); ++ struct iscsi_conn *conn = data; + int rc; + /* + * serialize Xmit worker on a per-connection basis. +@@ -1509,7 +1508,7 @@ iscsi_conn_setup(struct iscsi_cls_sessio + if (conn->mgmtqueue == ERR_PTR(-ENOMEM)) + goto mgmtqueue_alloc_fail; + +- INIT_WORK(&conn->xmitwork, iscsi_xmitworker); ++ INIT_WORK(&conn->xmitwork, iscsi_xmitworker, conn); + + /* allocate login_mtask used for the login/text sequences */ + spin_lock_bh(&session->lock); +diff -Nurp a/libiscsi.h b/libiscsi.h +--- a/libiscsi.h 2007-01-10 12:38:25.000000000 +0200 ++++ b/libiscsi.h 2007-01-10 12:38:25.000000000 +0200 +@@ -25,8 +25,6 @@ + + #include + #include +-#include +-#include + #include "iscsi_proto.h" + #include "iscsi_if.h" + +diff -Nurp a/scsi_transport_iscsi.c b/scsi_transport_iscsi.c +--- a/scsi_transport_iscsi.c 2007-01-10 12:38:25.000000000 +0200 ++++ b/scsi_transport_iscsi.c 2007-01-10 12:38:25.000000000 +0200 +@@ -234,11 +234,9 @@ static int iscsi_user_scan(struct Scsi_H + return 0; + } + +-static void session_recovery_timedout(struct work_struct *work) ++static void session_recovery_timedout(void *data) + { +- struct iscsi_cls_session *session = +- container_of(work, struct iscsi_cls_session, +- recovery_work.work); ++ struct iscsi_cls_session *session = data; + + dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed " + "out after %d secs\n", session->recovery_tmo); +@@ -278,7 +276,7 @@ iscsi_alloc_session(struct Scsi_Host *sh + + session->transport = transport; + session->recovery_tmo = 120; +- INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout); ++ INIT_WORK(&session->recovery_work, session_recovery_timedout, session); + INIT_LIST_HEAD(&session->host_list); + INIT_LIST_HEAD(&session->sess_list); + +diff -Nurp a/scsi_transport_iscsi.h b/scsi_transport_iscsi.h +--- a/scsi_transport_iscsi.h 2007-01-10 12:38:26.000000000 +0200 ++++ b/scsi_transport_iscsi.h 2007-01-10 12:38:25.000000000 +0200 +@@ -176,7 +176,7 @@ struct iscsi_cls_session { + + /* recovery fields */ + int recovery_tmo; +- struct delayed_work recovery_work; ++ struct work_struct recovery_work; + + int target_id; Index: utils/fwparam_ibft/fwparam_ibft.c =================================================================== --- utils/fwparam_ibft/fwparam_ibft.c (revision 0) +++ utils/fwparam_ibft/fwparam_ibft.c (revision 779) @@ -0,0 +1,516 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright (C) IBM Corporation, 2006 + * + * Authors: Patrick Mansfield + * Mike Anderson + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwparam_ibft.h" + +char *progname; +int debug; +char default_file_name[] = "/dev/mem"; +char *filename = default_file_name; +int boot_selected_only; + +const char nulls[16]; /* defaults to zero */ + +/* + * Prefix strings, for the "prefixN:NAME=value". + */ +#define NETWORK "network" +#define INITIATOR "iscsi-initiator" +#define TGT "target" + + +void +verify_hdr(char *name, struct ibft_hdr *hdr, int id, int version, int length) +{ +#define VERIFY_HDR_FIELD(val) \ + if (hdr->val != val) { \ + fprintf(stderr, \ + "%s: error, %s structure expected %s %d but" \ + " got %d\n", \ + progname, name, #val, hdr->val, val); \ + exit(1); \ + } + + if (debug > 1) + fprintf(stderr, "%s: verifying %s header\n", __FUNCTION__, + name); + + VERIFY_HDR_FIELD(id); + VERIFY_HDR_FIELD(version); + VERIFY_HDR_FIELD(length); + +#undef VERIFY_HDR_FIELD +} + +#define CHECK_HDR(ident, name) \ + verify_hdr(#name, &ident->hdr, id_##name, version_##name, \ + sizeof(*ident)) + +/* + * Format 8 byte scsi LUN. Just format 8 bytes of hex, we could also + * format in the format as specified in rfc4173 (1-2-3-4, or 1-2), that is + * a nice format for humans :) + */ +void +format_lun(char *buf, size_t size, uint8_t *lun) +{ + int i; + + for (i = 0; i < 8; i++) + snprintf(buf++, size--, "%x", lun[i]); +} + +void +dump_lun(char *prefix, char *id, uint8_t *lun) +{ + char buf[32]; + + format_lun(buf, sizeof(buf), lun); + + if (prefix) + printf("%s%s=%s\n", prefix, id, buf); + else + printf("%s=%s\n", id, buf); + +} + +void +dump_word(char *prefix, char *id, unsigned short value) +{ + if (prefix) + printf("%s%s=%d\n", prefix, id, value); + else + printf("%s=%d\n", id, value); +} + +void +dump_string(char *prefix, char *id, char *value, int len) +{ + if (len == 0) + return; + /* + * Not checking if the offset is non-zero, it is not even passed + * in, else we need to pass a start and offset rather than value. + */ + + /* + * prints the string in "value" that has "len" characters (the + * printf "*" * means use the next argument as the length). + */ + if (prefix) + printf("%s%s=%.*s\n", prefix, id, len, value); + else + printf("%s=%.*s\n", id, len, value); +} + +void +format_ipaddr(char *buf, size_t size, uint8_t *ip) +{ + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && + ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 && + ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff) { + /* + * IPV4 + */ + snprintf(buf, size, "%d.%d.%d.%d", ip[12], ip[13], ip[14], ip[15]); + } else { + /* XXX ... */ + fprintf(stderr, "%s: warning no IPV6 support.\n", progname); + buf[0] = '\0'; + return; + } + +} + +/* + * Dump the 16 byte ipaddr, as IPV6 or IPV4. + */ +void +dump_ipaddr(char *prefix, char *id, uint8_t *ip) +{ + char buf[32]; + + /* + * Assumes all zero means no IP address. + */ + if (!memcmp(ip, nulls, sizeof(nulls))) + return; + + format_ipaddr(buf, sizeof(buf), ip); + + if (prefix) + printf("%s%s=%s\n", prefix, id, buf); + else + printf("%s=%s\n", id, buf); + +} + +/* + * Dump the 8 byte mac address + */ +void +dump_mac(char *prefix, char *id, uint8_t *mac) +{ + int i; + + if (prefix) + printf("%s%s=", prefix, id); + else + printf("%s=", id); + + for (i = 0; i < 5; i++) + printf("%02x:", mac[i]); + printf("%02x\n", mac[i]); +} + + +void +dump_initiator_prefix(void *ibft_loc, struct ibft_initiator *initiator, char *prefix) +{ + if (!initiator) + return; + /* + * Not all fields are (or were) supported by open-iscsi. Plus, + * some of these are for discovery. + */ + dump_ipaddr(prefix, "ISNS", initiator->isns_server); + dump_ipaddr(prefix, "SLP", initiator->slp_server); + dump_ipaddr(prefix, "PRIMARY_RADIUS_SERVER", initiator->pri_radius_server); + dump_ipaddr(prefix, "SECONDARY_RADIUS_SERVER", initiator->sec_radius_server); + dump_string(prefix, "NAME", ibft_loc + + initiator->initiator_name_off, initiator->initiator_name_len); +} + +void +dump_nic_prefix(void *ibft_loc, struct ibft_nic *nic, char *prefix) +{ + + if (!nic) + return; + + dump_mac(prefix, "HWADDR", nic->mac); + /* + * Assume dhcp if any non-zero portions of its address are set + * (again, undocumented). + */ + if (memcmp(nic->dhcp, nulls, sizeof(nic->dhcp))) { + dump_ipaddr(prefix, "DHCP", nic->dhcp); + } else { + dump_ipaddr(prefix, "IPADDR", nic->ip_addr); + /* + * XXX: Not sure how a mask "prefix" will be used in network + * bringup, this sounds less flexible than the normal + * masks used. + */ + printf("%s%s:%d\n", prefix, "MASK", nic->subnet_mask_prefix); + dump_ipaddr(prefix, "GATEWAY", nic->gateway); + dump_ipaddr(prefix, "DNSADDR1", nic->primary_dns); + dump_ipaddr(prefix, "DNSADDR2", nic->secondary_dns); + } + + dump_string(prefix, "HOSTNAME", ibft_loc + nic->hostname_off, + nic->hostname_len); + /* + * XXX unknown vlan: + */ + dump_word(prefix, "VLAN", nic->vlan); + /* + * XXX sort of unknown pci_bdf: 8 bits bus, 5 bits device, 3 bits + * function. + */ + if (prefix ) + printf("%s%s=%d:%d:%d\n", prefix, "PCI_BDF", + /* bus */ (nic->pci_bdf & 0xff00) >> 8, + /* device */ (nic->pci_bdf & 0xf8) >> 3, + /* function */ (nic->pci_bdf & 0x07)); + else + printf("%s=%d:%d:%d\n", "PCI_BDF", + /* bus */ (nic->pci_bdf & 0xff00) >> 8, + /* device */ (nic->pci_bdf & 0xf8) >> 3, + /* function */ (nic->pci_bdf & 0x07)); +} + +void +dump_tgt_prefix(void *ibft_loc, struct ibft_tgt *tgt, char *prefix) +{ + + if (!tgt) + return; + + dump_ipaddr(prefix, "IPADDR", tgt->ip_addr); + dump_word(prefix, "PORT", tgt->port); + /* + * XXX there should at least be a "no LUN specified field", or + * have different location objects, so the setup can search for + * the appropriate LU (like mount by label, or use of the + * /dev/disk/by-id names, or .... + * + * Like: + * uint8_t lu_type; 0: nothing specified, 1: LUN, 2: misc + * name - OS can use any way it wants, would have embedded a + * "NAME=string", like "LABEL=myrootvolume", or + * "DEV_NAME=/dev/disk/by-id/scsi-198279562093043094003030903". + * union lu_value { + * uint8_t lun[8]; + * uint8_t misc_name[64]; + * }; + * + * Maybe just add an extension header, and let the admin/user put + * strings like: "area:VALUE=string" into it? + */ + dump_lun(prefix, "LUN", tgt->lun); + dump_string(prefix, "NAME", ibft_loc + tgt->tgt_name_off, + tgt->tgt_name_len); + /* + * Note: don't dump the nic association, just let the IP address take + * care of the routing. + */ + /* + * Note: don't dump the chap "type", just the chap names and secrets + * if any are specified - they imply CHAP and reversed CHAP. + */ + dump_string(prefix, "CHAP_NAME", ibft_loc + tgt->chap_name_off, + tgt->chap_name_len); + dump_string(prefix, "CHAP_PASSWORD", ibft_loc + tgt->chap_secret_off, + tgt->chap_secret_len); + dump_string(prefix, "CHAP_NAME_IN", ibft_loc + tgt->rev_chap_name_off, + tgt->rev_chap_name_len); + dump_string(prefix, "CHAP_PASSWORD_IN", + ibft_loc + tgt->rev_chap_secret_off, + tgt->rev_chap_secret_len); +} + +/* + * Read in and dump ASCII output for ibft starting at ibft_loc. + */ +int +dump_ibft(void *ibft_loc) +{ + struct ibft_table_hdr *ibft_hdr = ibft_loc; + struct ibft_control *control; + struct ibft_initiator *initiator = NULL; + struct ibft_nic *nic0 = NULL, *nic1 = NULL; + struct ibft_tgt *tgt0 = NULL, *tgt1 = NULL; + char sum = 0, *buf = ibft_loc; + char prefix[32]; + + for (; buf <= (char *) (ibft_loc + ibft_hdr->length);) + sum += *buf++; + if (sum) + fprintf(stderr, "Checksum not zero 0x%x \n", sum); + + control = ibft_loc + sizeof(*ibft_hdr); + CHECK_HDR(control, control); + + /* + * The ibft is setup to return multiple pieces for each + * object (like multiple nic's or multiple targets), but it only + * maps 1 initiator, two targets, and two nics, follow that layout + * here (i.e. don't search for others). + * + * Also, unknown what to do for extensions piece, it is not + * documented. + */ + + if (control->initiator_off) { + initiator = ibft_loc + control->initiator_off; + CHECK_HDR(initiator, initiator); + } + + if (control->nic0_off) { + nic0 = ibft_loc + control->nic0_off; + CHECK_HDR(nic0, nic); + } + + if (control->nic1_off) { + nic1 = ibft_loc + control->nic1_off; + CHECK_HDR(nic1, nic); + } + + if (control->tgt0_off) { + tgt0 = ibft_loc + control->tgt0_off; + CHECK_HDR(tgt0, target); + } + + if (control->tgt1_off) { + tgt1 = ibft_loc + control->tgt1_off; + CHECK_HDR(tgt1, target); + } + + if (boot_selected_only) { + + snprintf(prefix, sizeof(prefix), "iSCSI_INITIATOR_"); + + if (initiator && (initiator->hdr.flags & + INIT_FLAG_FW_SEL_BOOT)) + dump_initiator_prefix(ibft_loc, initiator, prefix); + + if (nic0 && (nic0->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) + dump_nic_prefix(ibft_loc, nic0, prefix); + else if (nic1 && (nic1->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) + dump_nic_prefix(ibft_loc, nic1, prefix); + + snprintf(prefix, sizeof(prefix), "iSCSI_TARGET_"); + if (tgt0 && (tgt0->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) + dump_tgt_prefix(ibft_loc, tgt0, prefix); + else if (tgt1 && (tgt1->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) + dump_tgt_prefix(ibft_loc, tgt1, prefix); + + } else { + + snprintf(prefix, sizeof(prefix), "%s%d:", INITIATOR, 0); + dump_initiator_prefix(ibft_loc, initiator, prefix); + + snprintf(prefix, sizeof(prefix), "%s%d:", NETWORK, 0); + dump_nic_prefix(ibft_loc, nic0, prefix); + snprintf(prefix, sizeof(prefix), "%s%d:", TGT, 0); + dump_tgt_prefix(ibft_loc, tgt0, prefix); + + snprintf(prefix, sizeof(prefix), "%s%d:", NETWORK, 1); + dump_nic_prefix(ibft_loc, nic1, prefix); + snprintf(prefix, sizeof(prefix), "%s%d:", TGT, 1); + dump_tgt_prefix(ibft_loc, tgt1, prefix); + } + + return 0; +} + +/* + * return the address of the location of string in filebuf, search up to + * max bytes of *filebuf, if not found returns NULL. + */ +char * +search_file(char *filebuf, char *string, int len, int max) +{ + char *cur = filebuf; + char *end = filebuf + max; + int i = 0; + + if (debug > 1) { + fprintf(stderr, + "%s: cur 0x%p, end 0x%p, string '%.4s', len %d\n", + __FUNCTION__, cur, end, string, len); + } + while ((cur < end) && memcmp(cur, string, len)) { + if (debug > 2) { + fprintf(stderr, "i %d, cur 0x%p: 0x%x ('%c')\n", + i, cur, cur[0], cur[0]); + i++; + } + cur++; + } + if (cur < end) + return cur; + else + return NULL; +} + +int +main (int argc, char **argv) +{ + int fd, option, ret; + char *filebuf, *ibft_loc; + int start = 512 * 1024; /* 512k */ + int end_search = (1024 * 1024) - start; /* 512k */ + + progname = argv[0]; + + while (1) { + option = getopt(argc, argv, "f:m:s:e:vhb"); + if (option == -1) + break; + switch (option) { + case 'b': + boot_selected_only = 1; + break; + case 'e': + end_search = strtoul(optarg, NULL, 0); + break; + case 'f': + filename = optarg; + break; + case 's': + start = strtoul(optarg, NULL, 0); + break; + case 'v': + debug++; + break; + default: + fprintf(stderr, "Unknown or bad option '%c'\n", option); + case 'h': + printf("Usage: %s OPTIONS\n" + "-b print only fw boot selected sections\n" + "-f file_to_search (default /dev/mem)\n" + "-s offset to start search\n" + "-e length of search\n" + "-v verbose\n", + progname); + exit(1); + } + } + + if (debug) + fprintf(stderr, "file: %s; start %d, end_search %d, debug %d\n", + filename, start, end_search, debug); + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Could not open %s: %s (%d)\n", + filename, strerror(errno), errno); + exit(1); + } + + /* + * XXX Possibly warn and exit if start > filesize(fd), or if start + + * end_search > filesize(fd). Else, we will get a bus error for + * small files (with memmap, and for testing at least, it would + * be hard to find a system with less than 1024k). + */ + filebuf = mmap(NULL, end_search, PROT_READ, MAP_PRIVATE, fd, start); + if (filebuf == MAP_FAILED) { + fprintf(stderr, "Could not mmap %s: %s (%d)\n", + filename, strerror(errno), errno); + exit(1); + } + + ibft_loc = search_file(filebuf, iBFTSTR, strlen(iBFTSTR), end_search); + if (ibft_loc) { + if (dump_ibft(ibft_loc)) + ret = 0; + else + ret = 1; + } else + ret = 1; + munmap(filebuf, end_search); + close(fd); + exit(ret); +} Index: utils/fwparam_ibft/fwparam_ibft.h =================================================================== --- utils/fwparam_ibft/fwparam_ibft.h (revision 0) +++ utils/fwparam_ibft/fwparam_ibft.h (revision 779) @@ -0,0 +1,139 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright (C) IBM Corporation, 2006 + * + * Authors: Doug Maxey + * Patrick Mansfield + * + */ + +#ifndef FWPARAM_IBFT_H_ +#define FWPARAM_IBFT_H_ + +/* #include */ +#include + +/* + * Structures here are is based on Doug's original code, and Patrick's + * interpretation of the IBM internal design document title the "iSCSI + * Boot Firmware Table (iBFT)". + */ +#define iBFTSTR "iBFT" +#define iBFT_SIG { 'i','B','F','T' } + + +/* + * These macros are lower case to make the verify_hdr macro easier. + */ +#define version_control 1 +#define version_initiator 1 +#define version_nic 1 +#define version_target 1 +#define version_extensions 1 + +enum ibft_id { + id_control = 1, + id_initiator, + id_nic, + id_target, + id_extensions, +}; + +struct ibft_hdr { + uint8_t id; + uint8_t version; + uint16_t length; + uint8_t ind; + uint8_t flags; +}; + +struct ibft_table_hdr { + uint8_t signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + uint8_t oemid[6]; + uint8_t oem_table_id[8]; + uint8_t rsvd1[24]; +} __attribute__((__packed__)); + +struct ibft_control { + struct ibft_hdr hdr; + uint16_t extensions; + uint16_t initiator_off; + uint16_t nic0_off; + uint16_t tgt0_off; + uint16_t nic1_off; + uint16_t tgt1_off; +} __attribute__((__packed__)); + +struct ibft_initiator { +#define INIT_FLAG_VALID 1 +#define INIT_FLAG_FW_SEL_BOOT 2 + struct ibft_hdr hdr; + uint8_t isns_server[16]; + uint8_t slp_server[16]; + uint8_t pri_radius_server[16]; + uint8_t sec_radius_server[16]; + uint16_t initiator_name_len; + uint16_t initiator_name_off; +} __attribute__((__packed__)); + +struct ibft_nic { +#define NIC_FLAG_VALID 1 +#define NIC_FLAG_FW_SEL_BOOT 2 + struct ibft_hdr hdr; + uint8_t ip_addr[16]; + uint8_t subnet_mask_prefix; + uint8_t origin; + uint8_t gateway[16]; + uint8_t primary_dns[16]; + uint8_t secondary_dns[16]; + uint8_t dhcp[16]; + uint16_t vlan; + uint8_t mac[6]; + uint16_t pci_bdf; + uint16_t hostname_len; + uint16_t hostname_off; +} __attribute__((__packed__)); + +struct ibft_tgt { +#define TGT_FLAG_VALID 1 +#define TGT_FLAG_FW_SEL_BOOT 2 +#define TGT_FLAG_USE_RADIUS_CHAT 4 +#define TGT_FLAG_USE_RADIUS_RCHAT 8 + struct ibft_hdr hdr; + uint8_t ip_addr[16]; + uint16_t port; + uint8_t lun[8]; +#define TGT_CHAP 1 +#define TGT_MUTUAL_CHAP 2 + uint8_t chap_type; + uint8_t nic_assoc; + uint16_t tgt_name_len; + uint16_t tgt_name_off; + uint16_t chap_name_len; + uint16_t chap_name_off; + uint16_t chap_secret_len; + uint16_t chap_secret_off; + uint16_t rev_chap_name_len; + uint16_t rev_chap_name_off; + uint16_t rev_chap_secret_len; + uint16_t rev_chap_secret_off; +} __attribute__((__packed__)); + +#endif /* FWPARAM_IBFT_H_ */ Index: utils/fwparam_ibft/Makefile =================================================================== --- utils/fwparam_ibft/Makefile (revision 0) +++ utils/fwparam_ibft/Makefile (revision 779) @@ -0,0 +1,11 @@ +OPTFLAGS ?= -O2 -g +WARNFLAGS ?= -Wall -Wstrict-prototypes +CFLAGS += $(OPTFLAGS) $(WARNFLAGS) +PROGRAMS = fwparam_ibft + +all: $(PROGRAMS) + +fwparam_ibft: fwparam_ibft.c fwparam_ibft.h + $(CC) $(CFLAGS) $^ -o $@ +clean: + rm -f *.o $(PROGRAMS) Index: utils/iscsi-iname.c =================================================================== --- utils/iscsi-iname.c (revision 0) +++ utils/iscsi-iname.c (revision 779) @@ -0,0 +1,140 @@ +/* + * iSCSI InitiatorName creation utility + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-iname.c,v 1.1.2.3 2005/03/15 06:33:44 wysochanski Exp $ + * + * iscsi-iname.c - Compute an iSCSI InitiatorName for this host. + * Note that to ensure uniqueness, the system time is + * a factor. This name must be cached and only regenerated + * if there is no cached value. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "md5.h" + +#define RANDOM_NUM_GENERATOR "/dev/urandom" + +int +main(int argc, char *argv[]) +{ + char iname[256]; + struct timeval time; + struct utsname system_info; + long hostid; + struct MD5Context context; + unsigned char digest[16]; + unsigned char *bytes = digest; + unsigned char entropy[16]; + int e; + int fd; + char *prefix; + + /* initialize */ + memset(iname, 0, sizeof (iname)); + memset(digest, 0, sizeof (digest)); + memset(&context, 0, sizeof (context)); + MD5Init(&context); + + /* take a prefix if given, otherwise use a default. */ + if (argc > 1 && argv[1]) { + prefix = argv[1]; + if (( strcmp(prefix, "-h") == 0 ) || + ( strcmp(prefix, "--help") == 0 )) { + printf("\nDisplays the iSCSI initiator name\n"); + exit(0); + } else if ( strcmp(prefix, "-p") == 0 ) { + prefix = argv[2]; + } else { + printf("\nUsage: iscsi-iname [-h | --help | " + "-p ]\n"); + exit(0); + } + } else { + prefix = "iqn.2005-03.org.open-iscsi"; + } + + /* try to feed some entropy from the pool to MD5 in order to get + * uniqueness properties + */ + + if ((fd = open(RANDOM_NUM_GENERATOR, O_RDONLY))) { + e = read(fd, &entropy, 16); + if (e >= 1) + MD5Update(&context, (md5byte *)entropy, e); + close(fd); + } + + /* time the name is created is a factor in order to get + * uniqueness properties + */ + if (gettimeofday(&time, NULL) < 0) { + perror("error: gettimeofday failed"); + return 1; + } + MD5Update(&context, (md5byte *) & time.tv_sec, sizeof (time.tv_sec)); + MD5Update(&context, (md5byte *) & time.tv_usec, sizeof (time.tv_usec)); + + /* hostid */ + hostid = gethostid(); + MD5Update(&context, (md5byte *) & hostid, sizeof (hostid)); + + /* get the hostname and system name */ + if (uname(&system_info) < 0) { + perror("error: uname failed"); + return 1; + } + MD5Update(&context, (md5byte *) system_info.sysname, + sizeof (system_info.sysname)); + MD5Update(&context, (md5byte *) system_info.nodename, + sizeof (system_info.nodename)); + MD5Update(&context, (md5byte *) system_info.release, + sizeof (system_info.release)); + MD5Update(&context, (md5byte *) system_info.version, + sizeof (system_info.version)); + MD5Update(&context, (md5byte *) system_info.machine, + sizeof (system_info.machine)); + + /* compute the md5 hash of all the bits we just collected */ + MD5Final(digest, &context); + + /* vary which md5 bytes we pick (though we probably don't need to do + * this, since hopefully MD5 produces results such that each byte is as + * good as any other). + */ + + if ((fd = open(RANDOM_NUM_GENERATOR, O_RDONLY))) { + if (read(fd, entropy, 1) == 1) + bytes = &digest[(entropy[0] % (sizeof(digest) - 6))]; + close(fd); + } + + /* print the prefix followed by 6 bytes of the MD5 hash */ + sprintf(iname, "%s:%x%x%x%x%x%x", prefix, + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]); + + iname[sizeof (iname) - 1] = '\0'; + printf("%s\n", iname); + return 0; +} Index: utils/md5.c =================================================================== --- utils/md5.c (revision 0) +++ utils/md5.c (revision 779) @@ -0,0 +1,242 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' header + * definitions; now uses stuff from dpkg's config.h. + * - Ian Jackson . + * Still in the public domain. + */ + +#include + +#include "md5.h" + +#if (__BYTE_ORDER == __BIG_ENDIAN) +/* + * we can compile this away for little endian since + * it does not do anything on those archs + */ +void +byteSwap(uint32_t * buf, unsigned words) +{ + md5byte *p = (md5byte *) buf; + + do { + *buf++ = (uint32_t) ((unsigned) p[3] << 8 | p[2]) << 16 | + ((unsigned) p[1] << 8 | p[0]); + p += 4; + } while (--words); +} +#else +#define byteSwap(buf,words) +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *) ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *) ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], struct MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *) ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *) ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof (ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<>(32-s)) + x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif Index: utils/md5.h =================================================================== --- utils/md5.h (revision 0) +++ utils/md5.h (revision 779) @@ -0,0 +1,41 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' + * header definitions; now uses stuff from dpkg's config.h + * - Ian Jackson . + * Still in the public domain. + */ + +#ifndef MD5_H +#define MD5_H + +#include + +#define md5byte unsigned char + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +#endif /* !MD5_H */ Index: utils/Makefile =================================================================== --- utils/Makefile (revision 0) +++ utils/Makefile (revision 779) @@ -0,0 +1,12 @@ +# This Makefile will work only with GNU make. + +CFLAGS += $(OPTFLAGS) -O2 -fno-inline -Wall -Wstrict-prototypes -g +PROGRAMS = iscsi-iname + +all: $(PROGRAMS) + +iscsi-iname: md5.o iscsi-iname.o + $(CC) $(CFLAGS) $^ $(DBM_LIB) -o $@ + +clean: + rm -f *.o $(PROGRAMS) Index: include/iscsi_proto.h =================================================================== --- include/iscsi_proto.h (revision 754) +++ include/iscsi_proto.h (revision 779) @@ -41,6 +41,29 @@ #define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;} /* + * If running svn modules we may need to define these. + * This should not go upstream since this is already properly defined there + */ +#ifdef __CHECKER__ +#define __bitwise__ __attribute__((bitwise)) +#else +#define __bitwise__ +#endif +#ifdef __CHECK_ENDIAN__ +#define __bitwise __bitwise__ +#else +#define __bitwise +#endif + +/* initiator tags; opaque for target */ +typedef uint32_t __bitwise__ itt_t; +/* below makes sense only for initiator that created this tag */ +#define build_itt(itt, id, age) ((__force itt_t)\ + ((itt) | ((id) << ISCSI_CID_SHIFT) | ((age) << ISCSI_AGE_SHIFT))) +#define get_itt(itt) ((__force uint32_t)(itt_t)(itt) & ISCSI_ITT_MASK) +#define RESERVED_ITT ((__force itt_t)0xffffffff) + +/* * iSCSI Template Message Header */ struct iscsi_hdr { @@ -50,7 +73,7 @@ uint8_t hlength; /* AHSs total length */ uint8_t dlength[3]; /* Data length */ uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag, opaque for target */ __be32 ttt; /* Target Task Tag */ __be32 statsn; __be32 exp_statsn; @@ -111,7 +134,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 data_length; __be32 cmdsn; __be32 exp_statsn; @@ -148,7 +171,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 rsvd1; __be32 statsn; __be32 exp_cmdsn; @@ -206,7 +229,7 @@ uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 ttt; /* Target Transfer Tag */ __be32 cmdsn; __be32 exp_statsn; @@ -221,7 +244,7 @@ uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 ttt; /* Target Transfer Tag */ __be32 statsn; __be32 exp_cmdsn; @@ -237,8 +260,8 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ - __be32 rtt; /* Reference Task Tag */ + itt_t itt; /* Initiator Task Tag */ + itt_t rtt; /* Reference Task Tag */ __be32 cmdsn; __be32 exp_statsn; __be32 refcmdsn; @@ -267,8 +290,8 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd2[8]; - __be32 itt; /* Initiator Task Tag */ - __be32 rtt; /* Reference Task Tag */ + itt_t itt; /* Initiator Task Tag */ + itt_t rtt; /* Reference Task Tag */ __be32 statsn; __be32 exp_cmdsn; __be32 max_cmdsn; @@ -293,7 +316,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 ttt; /* Target Transfer Tag */ __be32 statsn; __be32 exp_cmdsn; @@ -311,7 +334,7 @@ uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; + itt_t itt; __be32 ttt; __be32 rsvd4; __be32 exp_statsn; @@ -331,7 +354,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; - __be32 itt; + itt_t itt; __be32 ttt; __be32 statsn; __be32 exp_cmdsn; @@ -355,7 +378,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd4[8]; - __be32 itt; + itt_t itt; __be32 ttt; __be32 cmdsn; __be32 exp_statsn; @@ -373,7 +396,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd4[8]; - __be32 itt; + itt_t itt; __be32 ttt; __be32 statsn; __be32 exp_cmdsn; @@ -392,7 +415,7 @@ uint8_t dlength[3]; uint8_t isid[6]; /* Initiator Session ID */ __be16 tsih; /* Target Session Handle */ - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be16 cid; __be16 rsvd3; __be32 cmdsn; @@ -421,7 +444,7 @@ uint8_t dlength[3]; uint8_t isid[6]; /* Initiator Session ID */ __be16 tsih; /* Target Session Handle */ - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 rsvd3; __be32 statsn; __be32 exp_cmdsn; @@ -478,7 +501,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd2[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be16 cid; uint8_t rsvd3[2]; __be32 cmdsn; @@ -505,7 +528,7 @@ uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd3[8]; - __be32 itt; /* Initiator Task Tag */ + itt_t itt; /* Initiator Task Tag */ __be32 rsvd4; __be32 statsn; __be32 exp_cmdsn; @@ -528,7 +551,7 @@ uint8_t opcode; uint8_t flags; uint8_t rsvd2[14]; - __be32 itt; + itt_t itt; __be32 begrun; __be32 runlength; __be32 exp_statsn; @@ -580,8 +603,18 @@ #define VALUE_MAXLEN 255 #define TARGET_NAME_MAXLEN VALUE_MAXLEN -#define DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192 +#define ISCSI_DEF_MAX_RECV_SEG_LEN 8192 +#define ISCSI_MIN_MAX_RECV_SEG_LEN 512 +#define ISCSI_MAX_MAX_RECV_SEG_LEN 16777215 +#define ISCSI_DEF_FIRST_BURST_LEN 65536 +#define ISCSI_MIN_FIRST_BURST_LEN 512 +#define ISCSI_MAX_FIRST_BURST_LEN 16777215 + +#define ISCSI_DEF_MAX_BURST_LEN 262144 +#define ISCSI_MIN_MAX_BURST_LEN 512 +#define ISCSI_MAX_MAX_BURST_LEN 16777215 + /************************* RFC 3720 End *****************************/ #endif /* ISCSI_PROTO_H */ Index: doc/iscsiadm.8 =================================================================== --- doc/iscsiadm.8 (revision 754) +++ doc/iscsiadm.8 (revision 779) @@ -5,15 +5,25 @@ \fBiscsiadm\fR -m discovery [ -dhV ] [ -t type -p ip:port [ -l ] ] | [ -o operation ] [ -n name ] [ -v value ] -\fBiscsiadm\fR -m node [ -dhV ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port | -M sysdir ] [ -l | -u ] ] +\fBiscsiadm\fR -m node [ -dhV ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port ] [ -l | -u ] ] [ [ -o operation ] [ -n name ] [ -v value ] [ -p ip:port ] ] -\fBiscsiadm\fR -m session [ -dhV ] [ -r sessionid [ -R ] [ -u | -s ] ] +\fBiscsiadm\fR -m session [ -dhV ] [ -r sessionid | sysfsdir [ -R ] [ -u | -s ] ] .SH "DESCRIPTION" The iscsiadm utility is a command-line tool allowing discovery and login to iSCSI targets, as well as access and management of the open-iscsi database. + +Open-iscsi does not use the term node as defined by the iSCSI RFC, +where a node is a single iSCSI initiator or target. Open-iscsi uses the +term node to refer to a portal on a target, so tools like iscsiadm +require that --targetname and --portal argument be used when in node mode. + +For session mode, a session id (sid) is used. The sid of a session can be +found by running iscsiadm -m session -i. The session id is not currently +persistent and is partially determined by when the session is setup. + .PP Note that many of the node and discovery operations require that the iSCSI daemon (iscsid) be running. @@ -72,7 +82,7 @@ Specifies a database operator \fIop\fR. \fIop\fR must be one of \fInew\fR, \fIdelete\fR, \fIupdate\fR or \fIshow\fR. .IP -This option is only valid for all modes. +This option is only valid for all modes, but delete should not be used on a running session. .IP \fInew\fR is currently valid only for node and session mode. It creates a new database record for a given \fIportal\fR (IP address and port number). @@ -95,16 +105,23 @@ .IP This option is only valid for discovery, or for node operations with the \fInew\fR operator. +.IP +This should be used along with --target in node mode, to specify what the open-iscsi docs refer to as a node or node record. Note: open-iscsi's use of the word node, does not match the iSCSI RFC's iSCSI Node term. .TP \fB\-T\fR, \fB\-\-targetname=\fItargetname\fR Use target \fItargetname\fR. +.IP +This should be used along with --portal in node mode, to specify what the open-iscsi docs refer to as a node or node record. Note: open-iscsi's use of the word node, does not match the iSCSI RFC's iSCSI Node term. .TP -\fB\-r\fR, \fB\-\-sid=\fIsid\fR -Use session ID \fIsid\fR. +\fB\-r\fR, \fB\-\-sid=\fIsid | sysfsdir\fR +Use session ID \fIsid\fR. The sid of a session can be found from running +iscsiadm in session mode with the --info argument. + +Instead of sid, a sysfs path containing the session can be used. For example using one of the following: /sys/devices/platform/hostH/sessionS/targetH:B:I/H:B:I:L, /sys/devices/platform/hostH/sessionS/targetH:B:I, or /sys/devices/platform/hostH/sessionS, for the sysfsdir argument would result in the session with sid S to be used. .IP -\fIsid\fR is only required for session mode. +\fIsid | sysfsdir\fR is only required for session mode. .TP \fB\-R\fR, \fB\-\-rescan\fR @@ -129,8 +146,8 @@ .TP \fB\-t\fR, \fB\-\-type=\fItype\fR \fItype\fR must be \fIsendtargets\fR (or abbreviated as \fIst\fR), -\fIslp\fR, or \fIisns\fR. Currently only sendtargets is supported, see the -DISCOVERY TYPES section. +\fIslp\fR, or \fIisns\fR. Currently only sendtargets and iSNS is supported, +see the DISCOVERY TYPES section. .IP This option is only valid for discovery mode. @@ -178,18 +195,18 @@ .TP .B iSNS -iSNS (Internet Storage Name Service) is a proposed standard to record -information about storage volumes within a larger network. In theory -it can be implemented for iSCSI and Fibre Channel. However, no -implementation of the central nameserver mandated by this standard is -currently available. +iSNS (Internet Storage Name Service) records information about storage +volumes within a larger network. To utilize iSNS, the address of the +iSNS server must be set in iscsid.conf using the "isns.address" value, +and iscsiadm must be run in discovery mode with the "isns" discovery type. + .P -Currently open-iscsi supports only the +iscsiadm supports the .B -SendTargets +iSNS (isns) or .B -st +SendTargets (st) discovery type. An SLP implementation is under development. .SH EXAMPLES Index: usr/mgmt_ipc.c =================================================================== --- usr/mgmt_ipc.c (revision 754) +++ usr/mgmt_ipc.c (revision 779) @@ -207,6 +207,12 @@ return MGMT_IPC_ERR; } +static mgmt_ipc_err_e +mgmt_ipc_isns_dev_attr_query(queue_task_t *qtask) +{ + return isns_dev_attr_query_task(qtask); +} + static int mgmt_peeruser(int sock, char *user) { @@ -291,6 +297,21 @@ #endif } +void +mgmt_ipc_write_rsp(queue_task_t *qtask, mgmt_ipc_err_e err) +{ + log_debug(4, "%s: rsp to fd %d", __FUNCTION__, + qtask->mgmt_ipc_fd); + + if (qtask->mgmt_ipc_fd < 0) + return; + + qtask->rsp.err = err; + write(qtask->mgmt_ipc_fd, &qtask->rsp, sizeof(qtask->rsp)); + close(qtask->mgmt_ipc_fd); + free(qtask); +} + static int mgmt_ipc_handle(int accept_fd) { @@ -332,8 +353,8 @@ rc = -ENOMEM; goto err; } - memcpy(&qtask->u.login.req, &req, sizeof(iscsiadm_req_t)); - qtask->u.login.mgmt_ipc_fd = fd; + memcpy(&qtask->req, &req, sizeof(iscsiadm_req_t)); + qtask->mgmt_ipc_fd = fd; switch(req.command) { case MGMT_IPC_SESSION_LOGIN: @@ -379,6 +400,9 @@ immrsp = 1; rc = 1; break; + case MGMT_IPC_ISNS_DEV_ATTR_QUERY: + rsp.err = mgmt_ipc_isns_dev_attr_query(qtask); + break; default: log_error("unknown request: %s(%d) %u", __FUNCTION__, __LINE__, req.command); @@ -428,10 +452,12 @@ #define POLL_CTRL 0 #define POLL_IPC 1 -#define POLL_MAX 2 +#define POLL_ISNS 2 +#define POLL_MAX 3 /* TODO: this should go somewhere else */ -void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd) +void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd, + int isns_fd) { struct pollfd poll_array[POLL_MAX]; int res; @@ -441,6 +467,13 @@ poll_array[POLL_IPC].fd = mgmt_ipc_fd; poll_array[POLL_IPC].events = POLLIN; + if (isns_fd < 0) + poll_array[POLL_ISNS].fd = poll_array[POLL_ISNS].events = 0; + else { + poll_array[POLL_ISNS].fd = isns_fd; + poll_array[POLL_ISNS].events = POLLIN; + } + while (1) { res = poll(poll_array, POLL_MAX, ACTOR_RESOLUTION); if (res > 0) { @@ -451,6 +484,9 @@ if (poll_array[POLL_IPC].revents) if (mgmt_ipc_handle(mgmt_ipc_fd) == 1) break; + if (poll_array[POLL_ISNS].revents) + isns_handle(isns_fd); + } else if (res < 0) { if (errno == EINTR) { log_debug(1, "event_loop interrupted"); Index: usr/iscsi_settings.h =================================================================== --- usr/iscsi_settings.h (revision 0) +++ usr/iscsi_settings.h (revision 779) @@ -0,0 +1,15 @@ +/* + * Default initiator settings. These may not be the same as + * in the RFC. See iscsi_proto.h for those. + */ +/* timeouts in seconds */ +#define DEF_LOGIN_TIMEO 15 +#define DEF_LOGOUT_TIMEO 15 +#define DEF_NOOP_OUT_INTERVAL 10 +#define DEF_NOOP_OUT_TIMEO 15 +#define DEF_REPLACEMENT_TIMEO 120 + +/* data and segment lengths in bytes */ +#define DEF_INI_FIRST_BURST_LEN 262144 +#define DEF_INI_MAX_BURST_LEN 16776192 +#define DEF_INI_MAX_RECV_SEG_LEN 131072 Index: usr/mgmt_ipc.h =================================================================== --- usr/mgmt_ipc.h (revision 754) +++ usr/mgmt_ipc.h (revision 779) @@ -44,6 +44,7 @@ MGMT_IPC_ERR_TRANS_CAPS = 14, MGMT_IPC_ERR_EXISTS = 15, MGMT_IPC_ERR_INVALID_REQ = 16, + MGMT_IPC_ERR_ISNS_UNAVAILABLE = 17, } mgmt_ipc_err_e; typedef enum iscsiadm_cmd { @@ -60,6 +61,7 @@ MGMT_IPC_IMMEDIATE_STOP = 11, MGMT_IPC_SESSION_SYNC = 12, MGMT_IPC_SESSION_INFO = 13, + MGMT_IPC_ISNS_DEV_ATTR_QUERY = 14, } iscsiadm_cmd_e; typedef enum iscsi_conn_state_e { @@ -125,7 +127,10 @@ struct iscsi_ipc *ipc; void need_reap(void); -void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd); +void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd, int isns_fd); + +struct queue_task; +void mgmt_ipc_write_rsp(struct queue_task *qtask, mgmt_ipc_err_e err); int mgmt_ipc_listen(void); void mgmt_ipc_close(int fd); Index: usr/ioctl.c =================================================================== --- usr/ioctl.c (revision 754) +++ usr/ioctl.c (revision 779) @@ -44,10 +44,10 @@ static void *pdu_sendbuf; #define IOCTL_BUF_DEFAULT_MAX \ - (DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH + sizeof(struct iscsi_hdr)) + (ISCSI_DEF_MAX_RECV_SEG_LEN + sizeof(struct iscsi_hdr)) #define PDU_SENDBUF_DEFAULT_MAX \ - (DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH + sizeof(struct iscsi_hdr)) + (ISCSI_DEF_MAX_RECV_SEG_LEN + sizeof(struct iscsi_hdr)) static int kread(char *data, int count) Index: usr/util.c =================================================================== --- usr/util.c (revision 754) +++ usr/util.c (revision 779) @@ -19,6 +19,7 @@ #include "config.h" #include "initiator.h" #include "version.h" +#include "iscsi_settings.h" void daemon_init(void) { @@ -170,11 +171,11 @@ rec->session.auth.password_in_length = 0; rec->session.err_timeo.abort_timeout = 10; rec->session.err_timeo.reset_timeout = 30; - rec->session.timeo.replacement_timeout = 120; + rec->session.timeo.replacement_timeout = DEF_REPLACEMENT_TIMEO; rec->session.iscsi.InitialR2T = 0; rec->session.iscsi.ImmediateData = 1; - rec->session.iscsi.FirstBurstLength = 256 * 1024; - rec->session.iscsi.MaxBurstLength = (16 * 1024 * 1024) - 1024; + rec->session.iscsi.FirstBurstLength = DEF_INI_FIRST_BURST_LEN; + rec->session.iscsi.MaxBurstLength = DEF_INI_MAX_BURST_LEN; rec->session.iscsi.DefaultTime2Wait = 0; rec->session.iscsi.DefaultTime2Retain = 0; rec->session.iscsi.MaxConnections = 1; @@ -186,17 +187,18 @@ rec->conn[i].port = DEF_ISCSI_PORT; rec->conn[i].tcp.window_size = 512 * 1024; rec->conn[i].tcp.type_of_service = 0; - rec->conn[i].timeo.login_timeout=15; - rec->conn[i].timeo.logout_timeout=15; + rec->conn[i].timeo.login_timeout= DEF_LOGIN_TIMEO; + rec->conn[i].timeo.logout_timeout= DEF_LOGOUT_TIMEO; rec->conn[i].timeo.auth_timeout = 45; rec->conn[i].timeo.active_timeout=5; rec->conn[i].timeo.idle_timeout = 60; rec->conn[i].timeo.ping_timeout = 5; - rec->conn[i].timeo.noop_out_interval = 0; - rec->conn[i].timeo.noop_out_timeout = 0; + rec->conn[i].timeo.noop_out_interval = DEF_NOOP_OUT_INTERVAL; + rec->conn[i].timeo.noop_out_timeout = DEF_NOOP_OUT_TIMEO; - rec->conn[i].iscsi.MaxRecvDataSegmentLength = 128 * 1024; + rec->conn[i].iscsi.MaxRecvDataSegmentLength = + DEF_INI_MAX_RECV_SEG_LEN; rec->conn[i].iscsi.HeaderDigest = CONFIG_DIGEST_PREFER_OFF; rec->conn[i].iscsi.DataDigest = CONFIG_DIGEST_NEVER; rec->conn[i].iscsi.IFMarker = 0; @@ -224,6 +226,8 @@ /* 13 */ "daemon access denied", /* 14 */ "iSCSI transport capability failure", /* 15 */ "already exists", + /* 16 */ "Unknown request", + /* 17 */ "encountered iSNS failure", }; log_error("initiator reported error (%d - %s)", err, err_msgs[err]); } Index: usr/Makefile =================================================================== --- usr/Makefile (revision 754) +++ usr/Makefile (revision 779) @@ -34,11 +34,11 @@ PROGRAMS = iscsid iscsiadm iscsistart # sources shared between iscsid, iscsiadm and iscsistart -ISCSI_LIB_SRCS = util.o io.o auth.o login.o log.o md5.o sha1.o iscsi_sysfs.o +ISCSI_LIB_SRCS = util.o io.o auth.o login.o log.o md5.o sha1.o iscsi_sysfs.o idbm.o # sources shared between iscsid and iscsiadm -COMMON_SRCS = $(ISCSI_LIB_SRCS) idbm.o +COMMON_SRCS = $(ISCSI_LIB_SRCS) # core initiator files -INITIATOR_SRCS = initiator.o queue.o actor.o mgmt_ipc.o transport.o +INITIATOR_SRCS = initiator.o queue.o actor.o mgmt_ipc.o isns.o transport.o all: $(PROGRAMS) Index: usr/initiator.c =================================================================== --- usr/initiator.c (revision 754) +++ usr/initiator.c (revision 779) @@ -45,6 +45,7 @@ #include "log.h" #include "util.h" #include "iscsi_sysfs.h" +#include "iscsi_settings.h" static void __session_mainloop(void *data); static void __conn_error_handle(iscsi_session_t*, iscsi_conn_t*); @@ -136,21 +137,6 @@ set_device_online); } -static void -write_mgmt_rsp(queue_task_t *qtask, mgmt_ipc_err_e err) -{ - log_debug(4, "%s: rsp to fd %d", __FUNCTION__, - qtask->u.login.mgmt_ipc_fd); - if (qtask->u.login.mgmt_ipc_fd == 0) - return; - - qtask->u.login.rsp.err = err; - write(qtask->u.login.mgmt_ipc_fd, &qtask->u.login.rsp, - sizeof(qtask->u.login.rsp)); - close(qtask->u.login.mgmt_ipc_fd); - free(qtask); -} - static conn_login_status_e __login_response_status(iscsi_conn_t *conn, enum iscsi_login_status login_status) @@ -367,24 +353,65 @@ /* connection's timeouts */ conn->id = cid; conn->logout_timeout = conn_rec->timeo.logout_timeout; + if (!conn->logout_timeout) { + log_error("Invalid timeo.logout_timeout. Must be greater " + "than zero. Using default %d.\n", + DEF_LOGOUT_TIMEO); + conn->logout_timeout = DEF_LOGOUT_TIMEO; + } + conn->login_timeout = conn_rec->timeo.login_timeout; + if (!conn->login_timeout) { + log_error("Invalid timeo.login_timeout. Must be greater " + "than zero. Using default %d.\n", + DEF_LOGIN_TIMEO); + conn->login_timeout = DEF_LOGIN_TIMEO; + } + + /* noop-out setting */ + conn->noop_out_interval = conn_rec->timeo.noop_out_interval; + conn->noop_out_timeout = conn_rec->timeo.noop_out_timeout; + if (conn->noop_out_interval && !conn->noop_out_timeout) { + log_error("Invalid timeo.noop_out_timeout. Must be greater " + "than zero. Using default %d.\n", + DEF_NOOP_OUT_TIMEO); + conn->noop_out_timeout = DEF_NOOP_OUT_TIMEO; + } + + if (conn->noop_out_timeout && !conn->noop_out_interval) { + log_error("Invalid timeo.noop_out_interval. Must be greater " + "than zero. Using default %d.\n", + DEF_NOOP_OUT_INTERVAL); + conn->noop_out_timeout = DEF_NOOP_OUT_INTERVAL; + } + + /* + * currently not used (leftover from linux-iscsi which we + * may do one day) + */ conn->auth_timeout = conn_rec->timeo.auth_timeout; conn->active_timeout = conn_rec->timeo.active_timeout; conn->idle_timeout = conn_rec->timeo.idle_timeout; conn->ping_timeout = conn_rec->timeo.ping_timeout; - /* noop-out setting */ - conn->noop_out_interval = conn_rec->timeo.noop_out_interval; - conn->noop_out_timeout = conn_rec->timeo.noop_out_timeout; - /* operational parameters */ conn->max_recv_dlength = __padding(conn_rec->iscsi.MaxRecvDataSegmentLength); + if (conn->max_recv_dlength < ISCSI_MIN_MAX_RECV_SEG_LEN || + conn->max_recv_dlength > ISCSI_MAX_MAX_RECV_SEG_LEN) { + log_error("Invalid iscsi.MaxRecvDataSegmentLength. Must be " + "within %u and %u. Setting to %u\n", + ISCSI_MIN_MAX_RECV_SEG_LEN, + ISCSI_MAX_MAX_RECV_SEG_LEN, + DEF_INI_MAX_RECV_SEG_LEN); + conn->max_recv_dlength = DEF_INI_MAX_RECV_SEG_LEN; + } + /* * iSCSI default, unless declared otherwise by the * target during login */ - conn->max_xmit_dlength = DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + conn->max_xmit_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; conn->hdrdgst_en = conn_rec->iscsi.HeaderDigest; conn->datadgst_en = conn_rec->iscsi.DataDigest; @@ -486,6 +513,7 @@ session->ctrl_fd = control_fd; session->transport_handle = provider->handle; session->provider = provider; + session->reopen_qtask.mgmt_ipc_fd = -1; /* save node record. we might need it for redirection */ memcpy(&session->nrec, rec, sizeof(node_rec_t)); @@ -506,7 +534,27 @@ session->initial_r2t_en = rec->session.iscsi.InitialR2T; session->imm_data_en = rec->session.iscsi.ImmediateData; session->first_burst = __padding(rec->session.iscsi.FirstBurstLength); + if (session->first_burst < ISCSI_MIN_FIRST_BURST_LEN || + session->first_burst > ISCSI_MAX_FIRST_BURST_LEN) { + log_error("Invalid iscsi.FirstBurstLength of %u. Must be " + "within %u and %u. Setting to %u\n", + session->first_burst, + ISCSI_MIN_FIRST_BURST_LEN, + ISCSI_MAX_FIRST_BURST_LEN, + DEF_INI_FIRST_BURST_LEN); + session->first_burst = DEF_INI_FIRST_BURST_LEN; + } + session->max_burst = __padding(rec->session.iscsi.MaxBurstLength); + if (session->max_burst < ISCSI_MIN_MAX_BURST_LEN || + session->max_burst > ISCSI_MAX_MAX_BURST_LEN) { + log_error("Invalid iscsi.MaxBurstLength of %u. Must be " + "within %u and %u. Setting to %u\n", + session->max_burst, ISCSI_MIN_MAX_BURST_LEN, + ISCSI_MAX_MAX_BURST_LEN, DEF_INI_MAX_BURST_LEN); + session->max_burst = DEF_INI_MAX_BURST_LEN; + } + session->def_time2wait = rec->session.iscsi.DefaultTime2Wait; session->def_time2retain = rec->session.iscsi.DefaultTime2Retain; session->erl = rec->session.iscsi.ERL; @@ -523,7 +571,7 @@ if (session->replacement_timeout == 0) { log_error("Cannot set replacement_timeout to zero. Setting " "120 seconds\n"); - session->replacement_timeout = 120; + session->replacement_timeout = DEF_REPLACEMENT_TIMEO; } /* OUI and uniqifying number */ @@ -580,7 +628,7 @@ iscsi_conn_t *conn = qtask->conn; iscsi_session_t *session = conn->session; - write_mgmt_rsp(qtask, err); + mgmt_ipc_write_rsp(qtask, err); session_conn_destroy(session, conn->id); if (conn->id == 0) __session_destroy(session); @@ -668,7 +716,9 @@ iscsi_conn_t *conn = (iscsi_conn_t*)data; iscsi_session_t *session = conn->session; - log_debug(3, "noop out rsp timeout, closing conn...\n"); + log_warning("Nop-out timedout after %d seconds on connection %d:%d " + "state (%d). Dropping session.", conn->noop_out_timeout, + session->id, conn->id, conn->state); /* XXX: error handle */ __conn_error_handle(session, conn); } @@ -730,7 +780,7 @@ * iSCSI default, unless declared otherwise by the * target during login */ - conn->max_xmit_dlength = DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + conn->max_xmit_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; conn->hdrdgst_en = conn_rec->iscsi.HeaderDigest; conn->datadgst_en = conn_rec->iscsi.DataDigest; @@ -876,14 +926,14 @@ pid = scan_host(session); if (pid == 0) { - write_mgmt_rsp(qtask, MGMT_IPC_OK); + mgmt_ipc_write_rsp(qtask, MGMT_IPC_OK); exit(0); } else if (pid > 0) { - close(qtask->u.login.mgmt_ipc_fd); + close(qtask->mgmt_ipc_fd); need_reap(); free(qtask); } else - write_mgmt_rsp(qtask, MGMT_IPC_ERR_INTERNAL); + mgmt_ipc_write_rsp(qtask, MGMT_IPC_ERR_INTERNAL); } static void @@ -1070,8 +1120,10 @@ log_warning("connection%d:%d is operational now", session->id, conn->id); } else { + session->sync_qtask = NULL; + __session_online_devs(session); - write_mgmt_rsp(c->qtask, MGMT_IPC_OK); + mgmt_ipc_write_rsp(c->qtask, MGMT_IPC_OK); log_warning("connection%d:%d is operational after recovery " "(%d attempts)", session->id, conn->id, session->reopen_cnt); @@ -1240,7 +1292,7 @@ case STATE_LOGOUT_REQUESTED: /* read incomming PDU */ if (!iscsi_io_recv_pdu(conn, &hdr, ISCSI_DIGEST_NONE, - conn->data, DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH, + conn->data, ISCSI_DEF_MAX_RECV_SEG_LEN, ISCSI_DIGEST_NONE, 0)) { return; } @@ -1261,17 +1313,17 @@ } break; case STATE_XPT_WAIT: - recvpool_put(conn, conn->recv_handle); + recvpool_put(conn, (void *)conn->recv_handle); log_debug(1, "ignoring incomming PDU in XPT_WAIT. " "let connection re-establish or fail"); break; case STATE_CLEANUP_WAIT: - recvpool_put(conn, conn->recv_handle); + recvpool_put(conn, (void *)conn->recv_handle); log_debug(1, "ignoring incomming PDU in XPT_WAIT. " "let connection cleanup"); break; default: - recvpool_put(conn, conn->recv_handle); + recvpool_put(conn, (void *)conn->recv_handle); log_error("Invalid state. Dropping PDU.\n"); } } @@ -1544,8 +1596,14 @@ break; case STATE_IN_LOGIN: if (session->r_stage == R_STAGE_SESSION_REOPEN) { - session_conn_reopen(conn, &session->reopen_qtask, - STOP_CONN_RECOVER); + queue_task_t *qtask; + + if (session->sync_qtask) + qtask = session->sync_qtask; + else + qtask = &session->reopen_qtask; + + session_conn_reopen(conn, qtask, STOP_CONN_RECOVER); return; } @@ -1579,7 +1637,7 @@ iscsi_conn_t *conn = item->context; iscsi_session_t *session = conn->session; - log_warning("detected iSCSI connection %d:%d error (%d) " + log_warning("Kernel reported iSCSI connection %d:%d error (%d) " "state (%d)", session->id, conn->id, error, conn->state); __conn_error_handle(session, conn); @@ -1820,8 +1878,8 @@ actor_timer(&conn->connect_timer, conn->login_timeout*1000, __connect_timedout, qtask); - qtask->u.login.rsp.command = MGMT_IPC_SESSION_LOGIN; - qtask->u.login.rsp.err = MGMT_IPC_OK; + qtask->rsp.command = MGMT_IPC_SESSION_LOGIN; + qtask->rsp.err = MGMT_IPC_OK; return MGMT_IPC_OK; } @@ -1879,7 +1937,8 @@ goto destroy_session; } - qtask->u.login.rsp.command = MGMT_IPC_SESSION_SYNC; + session->sync_qtask = qtask; + qtask->rsp.command = MGMT_IPC_SESSION_SYNC; session_conn_reopen(&session->conn[0], qtask, STOP_CONN_RECOVER); log_debug(3, "Started sync iSCSI session %d", session->id); @@ -1898,9 +1957,10 @@ mgmt_ipc_err_e rc = MGMT_IPC_OK; conn = &session->conn[0]; - if (conn->state == STATE_XPT_WAIT && + if (session->sync_qtask || + (conn->state == STATE_XPT_WAIT && (session->r_stage == R_STAGE_NO_CHANGE || - session->r_stage == R_STAGE_SESSION_REDIRECT)) { + session->r_stage == R_STAGE_SESSION_REDIRECT))) { log_error("session in invalid state for logout. " "Try again later\n"); return MGMT_IPC_ERR_INTERNAL; @@ -1911,7 +1971,7 @@ /* FIXME: implement Logout Request */ qtask->conn = conn; - qtask->u.login.rsp.command = MGMT_IPC_SESSION_LOGOUT; + qtask->rsp.command = MGMT_IPC_SESSION_LOGOUT; conn->logout_qtask = qtask; switch (conn->state) { Index: usr/isns_proto.h =================================================================== --- usr/isns_proto.h (revision 0) +++ usr/isns_proto.h (revision 779) @@ -0,0 +1,200 @@ +/* + * iSNS protocol data types + * + * Copyright (C) 2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef ISNS_PROTO_H +#define ISNS_PROTO_H + +#define ISNS_PORT 3205 +#define ISNS_ALIGN 4 + +struct isns_hdr { + uint16_t version; + uint16_t function; + uint16_t length; + uint16_t flags; + uint16_t transaction; + uint16_t sequence; + uint32_t pdu[0]; +} __attribute__ ((packed)); + +struct isns_tlv { + uint32_t tag; + uint32_t length; + uint32_t value[0]; +} __attribute__ ((packed)); + +/* Commands and responses (4.1.3) */ +#define ISNS_FUNC_DEV_ATTR_REG 0x0001 +#define ISNS_FUNC_DEV_ATTR_QRY 0x0002 +#define ISNS_FUNC_DEV_GET_NEXT 0x0003 +#define ISNS_FUNC_DEV_DEREG 0x0004 +#define ISNS_FUNC_SCN_REG 0x0005 +#define ISNS_FUNC_SCN_DEREG 0x0006 +#define ISNS_FUNC_SCN_EVENT 0x0007 +#define ISNS_FUNC_SCN 0x0008 +#define ISNS_FUNC_DD_REG 0x0009 +#define ISNS_FUNC_DD_DEREG 0x000a +#define ISNS_FUNC_DDS_REG 0x000b +#define ISNS_FUNC_DDS_DEREG 0x000c +#define ISNS_FUNC_ESI 0x000d +#define ISNS_FUNC_HEARTBEAT 0x000e + +#define ISNS_FUNC_DEV_ATTR_REG_RSP 0x8001 +#define ISNS_FUNC_DEV_ATTR_QRY_RSP 0x8002 +#define ISNS_FUNC_DEV_GET_NEXT_RSP 0x8003 +#define ISNS_FUNC_DEV_DEREG_RSP 0x8004 +#define ISNS_FUNC_SCN_REG_RSP 0x8005 +#define ISNS_FUNC_SCN_DEREG_RSP 0x8006 +#define ISNS_FUNC_SCN_EVENT_RSP 0x8007 +#define ISNS_FUNC_SCN_RSP 0x8008 +#define ISNS_FUNC_DD_REG_RSP 0x8009 +#define ISNS_FUNC_DD_DEREG_RSP 0x800a +#define ISNS_FUNC_DDS_REG_RSP 0x800b +#define ISNS_FUNC_DDS_DEREG_RSP 0x800c +#define ISNS_FUNC_ESI_RSP 0x800d + +/* iSNSP flags (5.1.4) */ +#define ISNS_FLAG_CLIENT (1U << 15) +#define ISNS_FLAG_SERVER (1U << 14) +#define ISNS_FLAG_AUTH (1U << 13) +#define ISNS_FLAG_REPLACE (1U << 12) +#define ISNS_FLAG_LAST_PDU (1U << 11) +#define ISNS_FLAG_FIRST_PDU (1U << 10) + +/* Response Status Codes (5.4) */ +#define ISNS_STATUS_SUCCESS 0 +#define ISNS_STATUS_UNKNOWN_ERROR 1 +#define ISNS_STATUS_FORMAT_ERROR 2 +#define ISNS_STATUS_INVALID_REGISTRATION 3 +#define ISNS_STATUS_RESERVED 4 +#define ISNS_STATUS_INVALID_QUERY 5 +#define ISNS_STATUS_SOURCE_UNKNOWN 6 +#define ISNS_STATUS_SOURCE_ABSENT 7 +#define ISNS_STATUS_SOURCE_UNAUTHORIZED 8 +#define ISNS_STATUS_NO_SUCH_ENTRY 9 +#define ISNS_STATUS_VERSION_NOT_SUPPORTED 10 +#define ISNS_STATUS_INTERNAL_ERROR 11 +#define ISNS_STATUS_BUSY 12 +#define ISNS_STATUS_OPTION_NOT_UNDERSTOOD 13 +#define ISNS_STATUS_INVALID_UPDATE 14 +#define ISNS_STATUS_MESSAGE_NOT_SUPPORTED 15 +#define ISNS_STATUS_SCN_EVENT_REJECTED 16 +#define ISNS_STATUS_SCN_REGISTRATION_REJECTED 17 +#define ISNS_STATUS_ATTRIBUTE_NOT_IMPLEMENTED 18 +#define ISNS_STATUS_FC_DOMAIN_ID_NOT_AVAILABLE 19 +#define ISNS_STATUS_FC_DOMAIN_ID_NOT_ALLOCATED 20 +#define ISNS_STATUS_ESI_NOT_AVAILABLE 21 +#define ISNS_STATUS_INVALIDE_DEREGISTRATION 22 +#define ISNS_STATUS_REGISTRATION_NOT_SUPPORTED 23 + +/* Node type (5.4.2) */ +#define ISNS_NODE_CONTROL (1U << 2) +#define ISNS_NODE_INITIATOR (1U << 1) +#define ISNS_NODE_TARGET (1U << 0) + +/* Attributes (6.1) */ +#define ISNS_ATTR_DELIMITER 0 +#define ISNS_ATTR_ENTITY_IDENTIFIER 1 +#define ISNS_ATTR_ENTITY_PROTOCOL 2 +#define ISNS_ATTR_MANAGEMENT_IP_ADDRESS 3 +#define ISNS_ATTR_TIMESTAMP 4 +#define ISNS_ATTR_PROTOCOL_VERSION_RANGE 5 +#define ISNS_ATTR_REGISTRATION_PERIOD 6 +#define ISNS_ATTR_ENTITY_INDEX 7 +#define ISNS_ATTR_ENTITY_NEXT_INDEX 8 +#define ISNS_ATTR_ISAKMP_PHASE1 11 +#define ISNS_ATTR_CERTIFICATE 12 +#define ISNS_ATTR_PORTAL_IP_ADDRESS 16 +#define ISNS_ATTR_PORTAL_PORT 17 +#define ISNS_ATTR_PORTAL_SYMBOLIC_NAME 18 +#define ISNS_ATTR_ESI_INTERVAL 19 +#define ISNS_ATTR_ESI_PORT 20 +#define ISNS_ATTR_PORTAL_INDEX 22 +#define ISNS_ATTR_SCN_PORT 23 +#define ISNS_ATTR_PORTAL_NEXT_INDEX 24 +#define ISNS_ATTR_PORTAL_SECURITY_BITMAP 27 +#define ISNS_ATTR_PORTAL_ISAKMP_PHASE1 28 +#define ISNS_ATTR_PORTAL_ISAKMP_PHASE2 29 +#define ISNS_ATTR_PORTAL_CERTIFICATE 31 +#define ISNS_ATTR_ISCSI_NAME 32 +#define ISNS_ATTR_ISCSI_NODE_TYPE 33 +#define ISNS_ATTR_ISCSI_ALIAS 34 +#define ISNS_ATTR_ISCSI_SCN_BITMAP 35 +#define ISNS_ATTR_ISCSI_NODE_INDEX 36 +#define ISNS_ATTR_WWNN_TOKEN 37 +#define ISNS_ATTR_ISCSI_NODE_NEXT_INDEX 38 +#define ISNS_ATTR_ISCSI_AUTHMETHOD 42 +#define ISNS_ATTR_PG_ISCSI_NAME 48 +#define ISNS_ATTR_PG_PORTAL_IP_ADDRESS 49 +#define ISNS_ATTR_PG_PORTAL_PORT 50 +#define ISNS_ATTR_PG_TAG 51 +#define ISNS_ATTR_PG_INDEX 52 +#define ISNS_ATTR_PG_NEXT_INDEX 53 +#define ISNS_ATTR_FC_PORT_NAME_WWPN 64 +#define ISNS_ATTR_PORT_ID 65 +#define ISNS_ATTR_PORT_TYPE 66 +#define ISNS_ATTR_SYMBOLIC_PORT_NAME 67 +#define ISNS_ATTR_FABRIC_PORT_NAME 68 +#define ISNS_ATTR_HARD_ADDRESS 69 +#define ISNS_ATTR_PORT_IP_ADDRESS 70 +#define ISNS_ATTR_CLASS_OF_SERVICE 71 +#define ISNS_ATTR_FC4_TYPES 72 +#define ISNS_ATTR_FC4_DESCRIPOTR 73 +#define ISNS_ATTR_FC4_FEATURES 74 +#define ISNS_ATTR_IFCP_SCN_BITMAP 75 +#define ISNS_ATTR_PORT_ROLE 76 +#define ISNS_ATTR_PERMANENT_PORT_NAME 77 +#define ISNS_ATTR_FC4_TYPE_CODE 95 +#define ISNS_ATTR_FC_NODE_NAME_WWNN 96 +#define ISNS_ATTR_SYMBOLIC_NODE_NAME 97 +#define ISNS_ATTR_NODE_IP_ADDRESS 98 +#define ISNS_ATTR_NODE_IPA 99 +#define ISNS_ATTR_PORXY_ISCSI_NAME 101 +#define ISNS_ATTR_SWITCH_NAME 128 +#define ISNS_ATTR_PREFERRED_ID 129 +#define ISNS_ATTR_ASSIGNED_ID 130 +#define ISNS_ATTR_VIRTUAL_FABRIC_ID 131 +#define ISNS_ATTR_ISNS_SERVER_VENDOR_OUI 256 +#define ISNS_ATTR_DD_SET_ID 2049 +#define ISNS_ATTR_DD_SET_SYM_NAME 2050 +#define ISNS_ATTR_DD_SET_STATUS 2051 +#define ISNS_ATTR_DD_SET_NEXT_ID 2052 +#define ISNS_ATTR_DD_ID 2065 +#define ISNS_ATTR_DD_SYMBOLIC_NAME 2066 +#define ISNS_ATTR_DD_MEMBER_ISCSI_INDEX 2067 +#define ISNS_ATTR_DD_MEMBER_ISCSI_NAME 2068 +#define ISNS_ATTR_DD_MEMBER_FC_PORT_NAME 2069 +#define ISNS_ATTR_DD_MEMBER_PORTAL_INDEX 2070 +#define ISNS_ATTR_DD_MEMBER_IP_ADDR 2071 +#define ISNS_ATTR_DD_MEMBER_TCP_UDP 2072 +#define ISNS_ATTR_DD_FEATURES 2078 +#define ISNS_ATTR_DD_ID_NEXT_ID 2079 + +/* SCN flags (6.4.4) */ +#define ISNS_SCN_FLAG_INITIATOR (1U << 24) +#define ISNS_SCN_FLAG_TARGET (1U << 25) +#define ISNS_SCN_FLAG_MANAGEMENT (1U << 26) +#define ISNS_SCN_FLAG_OBJECT_REMOVE (1U << 27) +#define ISNS_SCN_FLAG_OBJECT_ADDED (1U << 28) +#define ISNS_SCN_FLAG_OBJECT_UPDATED (1U << 29) +#define ISNS_SCN_FLAG_DD_REMOVED (1U << 30) +#define ISNS_SCN_FLAG_DD_ADDED (1U << 31) +#endif Index: usr/netlink.c =================================================================== --- usr/netlink.c (revision 754) +++ usr/netlink.c (revision 779) @@ -52,11 +52,11 @@ static int ctldev_handle(void); #define NLM_BUF_DEFAULT_MAX \ - (NLMSG_SPACE(DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH + \ + (NLMSG_SPACE(ISCSI_DEF_MAX_RECV_SEG_LEN + \ sizeof(struct iscsi_hdr))) #define PDU_SENDBUF_DEFAULT_MAX \ - (DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH + sizeof(struct iscsi_hdr)) + (ISCSI_DEF_MAX_RECV_SEG_LEN + sizeof(struct iscsi_hdr)) #define NLM_SETPARAM_DEFAULT_MAX \ (NI_MAXHOST + 1 + sizeof(struct iscsi_uevent)) Index: usr/discovery.c =================================================================== --- usr/discovery.c (revision 754) +++ usr/discovery.c (revision 779) @@ -667,8 +667,8 @@ config->send_async_text : -1; session->conn[0].hdrdgst_en = ISCSI_DIGEST_NONE; session->conn[0].datadgst_en = ISCSI_DIGEST_NONE; - session->conn[0].max_recv_dlength = DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; - session->conn[0].max_xmit_dlength = DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + session->conn[0].max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; + session->conn[0].max_xmit_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; session->reopen_cnt = config->reopen_max; Index: usr/initiator.h =================================================================== --- usr/initiator.h (revision 754) +++ usr/initiator.h (revision 779) @@ -120,7 +120,7 @@ struct iscsi_session *session; iscsi_login_context_t login_context; struct queue_task *logout_qtask; - char data[DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH]; + char data[ISCSI_DEF_MAX_RECV_SEG_LEN]; char host[NI_MAXHOST]; /* scratch */ iscsi_conn_state_e state; actor_t connect_timer; @@ -186,22 +186,9 @@ typedef struct queue_task { iscsi_conn_t *conn; - union { - /* iSCSI requests originated via IPC */ - struct ipcreq_login { - iscsiadm_req_t req; - iscsiadm_rsp_t rsp; - int mgmt_ipc_fd; - } login; - struct ipcreq_logout { - iscsiadm_req_t req; - iscsiadm_rsp_t rsp; - int mgmt_ipc_fd; - } logout; - /* iSCSI requests originated via CTL */ - struct ctlreq_recv_pdu { - } recv_pdu; - } u; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int mgmt_ipc_fd; } queue_task_t; typedef enum iscsi_provider_status_e { @@ -283,6 +270,9 @@ iscsi_session_r_stage_e r_stage; uint32_t replacement_timeout; + /* sync up fields */ + queue_task_t *sync_qtask; + /* session's processing */ actor_t mainloop; queue_t *queue; @@ -368,4 +358,10 @@ extern mgmt_ipc_err_e iscsi_sync_session(node_rec_t *rec, queue_task_t *tsk, uint32_t sid); +/* isns.c */ +extern int isns_init(void); +extern void isns_handle(int); +extern void isns_exit(void); +extern int isns_dev_attr_query_task(queue_task_t *qtask); + #endif /* INITIATOR_H */ Index: usr/login.c =================================================================== --- usr/login.c (revision 754) +++ usr/login.c (revision 779) @@ -1079,7 +1079,7 @@ if ((conn->hdrdgst_en != ISCSI_DIGEST_NONE) || (conn->datadgst_en != ISCSI_DIGEST_NONE) || (conn->max_recv_dlength != - DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH)) + ISCSI_DEF_MAX_RECV_SEG_LEN)) conn->next_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE; else Index: usr/isns.c =================================================================== --- usr/isns.c (revision 0) +++ usr/isns.c (revision 779) @@ -0,0 +1,794 @@ +/* + * iSNS functions + * + * Copyright (C) 2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "idbm.h" +#include "log.h" +#include "util.h" +#include "isns_proto.h" + +enum isns_task_state { + ISNS_TASK_WAIT_CONN, + ISNS_TASK_SEND_PDU, + ISNS_TASK_RECV_PDU, +}; + +struct isns_task { + int state; + int fd; + int len; + char data[ISCSI_DEF_MAX_RECV_SEG_LEN]; + int transaction; + int done; + int retry; + queue_task_t *qtask; +}; + +static actor_t isns_actor; +static queue_t *isns_queue = NULL; +static struct sockaddr_storage ss; +static uint16_t transaction; + +static char isns_address[NI_MAXHOST]; +static int isns_port = 3205, isns_listen_port, max_retry = 10000; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define get_hdr_param(hdr, function, length, flags, transaction, sequence) \ +{ \ + function = ntohs(hdr->function); \ + length = ntohs(hdr->length); \ + flags = ntohs(hdr->flags); \ + transaction = ntohs(hdr->transaction); \ + sequence = ntohs(hdr->sequence); \ +} + +/* use io.c */ +static int set_non_blocking(int fd) +{ + int res = fcntl(fd, F_GETFL); + + if (res == -1) + log_warning("unable to get fd flags %m"); + else { + res = fcntl(fd, F_SETFL, res | O_NONBLOCK); + if (res) + log_warning("unable to set fd flags %m"); + } + + return res; +} + +static void +isns_hdr_init(struct isns_hdr *hdr, uint16_t function, uint16_t length, + uint16_t flags, uint16_t trans, uint16_t sequence) +{ + hdr->version = htons(0x0001); + hdr->function = htons(function); + hdr->length = htons(length); + hdr->flags = htons(flags); + hdr->transaction = htons(trans); + hdr->sequence = htons(sequence); +} + +static int +isns_tlv_set(struct isns_tlv **tlv, uint32_t tag, uint32_t length, void *value) +{ + if (length) + memcpy((*tlv)->value, value, length); + if (length % ISNS_ALIGN) + length += (ISNS_ALIGN - (length % ISNS_ALIGN)); + + (*tlv)->tag = htonl(tag); + (*tlv)->length = htonl(length); + + length += sizeof(struct isns_tlv); + *tlv = (struct isns_tlv *) ((char *) *tlv + length); + + return length; +} + +static void build_dev_reg_req(struct isns_task *task) +{ + struct isns_hdr *hdr = (struct isns_hdr *) task->data; + struct isns_tlv *tlv = (struct isns_tlv *) hdr->pdu; + struct sockaddr_storage lss; + static uint8_t ip[16]; + char eid[NI_MAXHOST]; + char *name = dconfig->initiator_name; + char *alias = dconfig->initiator_alias; + socklen_t slen = sizeof(lss); + int i; + uint16_t flags = 0, length = 0; + uint32_t addr; + uint32_t port; + uint32_t node = htonl(ISNS_NODE_INITIATOR); + uint32_t type = htonl(2); + + memset(hdr, 0, sizeof(task->data)); + + getsockname(task->fd, (struct sockaddr *) &lss, &slen); + getnameinfo((struct sockaddr *) &lss, sizeof(lss), eid, sizeof(eid), + NULL, 0, 0); + + switch (lss.ss_family) { + case AF_INET: + addr = (((struct sockaddr_in *) &lss)->sin_addr.s_addr); + + ip[10] = ip[11] = 0xff; + ip[15] = 0xff & (addr >> 24); + ip[14] = 0xff & (addr >> 16); + ip[13] = 0xff & (addr >> 8); + ip[12] = 0xff & addr; + port = ((struct sockaddr_in *) &lss)->sin_port; + break; + case AF_INET6: + for (i = 0; i < ARRAY_SIZE(ip); i++) + ip[i] = ((struct sockaddr_in6 *) &lss)->sin6_addr.s6_addr[i]; + break; + port = ((struct sockaddr_in6 *) &lss)->sin6_port; + } + + port = htonl(ntohs(port)); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + length += isns_tlv_set(&tlv, 0, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_PROTOCOL, + sizeof(type), &type); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_IP_ADDRESS, + sizeof(ip), &ip); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_PORT, + sizeof(port), &port); + flags = ISNS_FLAG_REPLACE; + + port = htonl(isns_listen_port); + length += isns_tlv_set(&tlv, ISNS_ATTR_ESI_PORT, + sizeof(port), &port); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, + sizeof(node), &node); + if(alias) + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_ALIAS, + strlen(alias), alias); + + flags |= ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + task->transaction = ++transaction; + isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_REG, length, flags, + task->transaction, 0); + + task->len = length + sizeof(*hdr); +} + +static int isns_connect(void) +{ + int err; + int fd; + + fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + log_error("can't create socket %m"); + return -errno; + } + + err = set_non_blocking(fd); + if (err) { + log_error("can't set non-blocking %m"); + close(fd); + return -errno; + } + + err = connect(fd, (struct sockaddr *) &ss, sizeof(ss)); + if (err && errno != EINPROGRESS) { + log_error("can't connect %m"); + close(fd); + return -errno; + } + return fd; +} + +static int isns_send_pdu(struct isns_task *task) +{ + int err; + + err = write(task->fd, task->data + task->done, task->len - task->done); + if (err < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS) { + log_error("send fail %m"); + return -1; + } + } else + task->done += err; + + return 0; +} +static void isns_free_task(struct isns_task *task) +{ + close(task->fd); + free(task); +} + +static int isns_recv_pdu(struct isns_task *task) +{ + struct isns_hdr *hdr = (struct isns_hdr *) task->data; + uint16_t function, length, flags, transaction, sequence; + int err, size; + + if (task->done < sizeof(*hdr)) + size = sizeof(*hdr) - task->done; + else + size = task->len + sizeof(*hdr) - task->done; + + err = read(task->fd, task->data + task->done, size); + if (err <= 0) { + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS) { + log_error("send fail %m"); + return -1; + } + } else { + task->done += err; + + if (task->done == sizeof(*hdr)) { + get_hdr_param(hdr, function, length, flags, transaction, + sequence); + task->len = length; + } + } + return 0; +} + +static void add_new_target_node(char *targetname, uint8_t *ip, int port, + int tag) +{ + int err; + node_rec_t rec; + idbm_t *db; + char dst[INET6_ADDRSTRLEN]; + + memset(dst, 0, sizeof(dst)); + if (!memcmp(ip, dst, 10) && ip[10] == 0xff && ip[11] == 0xff) + inet_ntop(AF_INET, ip + 12, dst, sizeof(dst)); + else + inet_ntop(AF_INET6, ip, dst, sizeof(dst)); + + log_debug(1, "add a new target node:%s %s,%d %d", + targetname, dst, port, tag); + + db = idbm_init(dconfig->config_file); + + idbm_node_setup_defaults(&rec); + strncpy(rec.name, targetname, TARGET_NAME_MAXLEN); + rec.conn[0].port = port; + rec.tpgt = tag; + strncpy(rec.conn[0].address, dst, NI_MAXHOST); + err = idbm_new_node(db, &rec); + if (err) + log_error("Could not add new target node:%s %s,%d", + targetname, dst, port); + + idbm_terminate(db); +} + +static int qry_rsp_handle(struct isns_hdr *hdr) +{ + struct isns_tlv *tlv; + uint16_t function, length, flags, transaction, sequence; + uint32_t port, tag, status; + uint8_t *addr; + char *name; + + get_hdr_param(hdr, function, length, flags, transaction, sequence); + + status = (uint32_t) (*hdr->pdu); + if (status) + return status; + + /* skip status */ + tlv = (struct isns_tlv *) ((char *) hdr->pdu + 4); + length -= 4; + + /* check node type in the message key*/ + if ((ntohl(tlv->tag) != ISNS_ATTR_ISCSI_NODE_TYPE) || + ntohl(*(tlv->value)) != ISNS_NODE_TARGET) + return EINVAL; + + /* 12 + 8 bytes */ + length -= (sizeof(*tlv) + 4 + 8); + if (length <= 0) { + log_error("No target found."); + return EINVAL; + } + + tlv = (struct isns_tlv *) ((char *) tlv + 20); + + name = NULL; + addr = NULL; + port = tag = 0; + + /* FIXME: this assume the exact order. */ + while (length) { + uint32_t vlen = ntohl(tlv->length); + + switch (ntohl(tlv->tag)) { + case ISNS_ATTR_PG_ISCSI_NAME: + if (name && addr) { + add_new_target_node(name, addr, port, tag); + name = NULL; + addr = NULL; + } + name = (char *) tlv->value; + break; + case ISNS_ATTR_ISCSI_NODE_TYPE: + if (ntohl(*(tlv->value)) != ISNS_NODE_TARGET) + name = NULL; + break; + case ISNS_ATTR_PG_PORTAL_IP_ADDRESS: + addr = (uint8_t *) tlv->value; + break; + case ISNS_ATTR_PG_PORTAL_PORT: + port = ntohl(tlv->value[0]); + break; + case ISNS_ATTR_PG_TAG: + tag = ntohl(tlv->value[0]); + break; + case ISNS_ATTR_ISCSI_NAME: + case ISNS_ATTR_PORTAL_IP_ADDRESS: + case ISNS_ATTR_PORTAL_PORT: + break; + default: + log_error("unexpected type %d", ntohl(tlv->tag)); + break; + } + + length -= (sizeof(*tlv) + vlen); + tlv = (struct isns_tlv *) ((char *) tlv->value + vlen); + } + + if (name && addr) + add_new_target_node(name, addr, port, tag); + + return 0; +} + +static void send_mgmt_rsp(struct isns_task *task, int err) +{ + mgmt_ipc_write_rsp(task->qtask, + err ? MGMT_IPC_ERR_ISNS_UNAVAILABLE : MGMT_IPC_OK); +} + +static int isns_task_done(struct isns_task *task) +{ + struct isns_hdr *hdr = (struct isns_hdr *) task->data; + uint16_t function, length, flags, transaction, sequence; + uint32_t status = (uint32_t) (*hdr->pdu); + char *payload = (char *) hdr + sizeof(*hdr); + int finished = 1; + + get_hdr_param(hdr, function, length, flags, transaction, + sequence); + + if (function & 0x8000 && status) + log_error("error isns response %x %x", function, status); + + switch (function) { + case ISNS_FUNC_DEV_ATTR_REG_RSP: + break; + case ISNS_FUNC_DEV_ATTR_QRY_RSP: + if (!status) + qry_rsp_handle((struct isns_hdr *)task->data); + send_mgmt_rsp(task, status); + break; + case ISNS_FUNC_ESI: + memmove(payload + 4, payload, length); + *((uint32_t *) payload) = 0; + + length += 4; + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | + ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_ESI_RSP, length, flags, + transaction, 0); + task->state = ISNS_TASK_SEND_PDU; + task->len = length + sizeof(*hdr); + task->done = 0; + + queue_produce(isns_queue, EV_CONN_POLL, task, 0, NULL); + actor_schedule(&isns_actor); + finished = 0; + break; + default: + log_error("unexpected function %d", function); + break; + } + + return finished; +} + +int isns_dev_attr_query_task(queue_task_t *qtask) +{ + int fd; + struct isns_hdr *hdr; + struct isns_tlv *tlv; + char *name = dconfig->initiator_name; + uint16_t flags, length = 0; + uint32_t node = htonl(ISNS_NODE_TARGET); + struct isns_task *task; + + if (!strlen(isns_address)) + return MGMT_IPC_ERR_ISNS_UNAVAILABLE; + + fd = isns_connect(); + if (fd < 0) { + log_error("%s %m", __FUNCTION__); + return MGMT_IPC_ERR_ISNS_UNAVAILABLE; + } + + task = malloc(sizeof(*task)); + if (!task) { + log_error("%s %m", __FUNCTION__); + close(fd); + return MGMT_IPC_ERR_NOMEM; + } + memset(task, 0, sizeof(*task)); + + task->qtask = qtask; + task->fd = fd; + + hdr = (struct isns_hdr *) task->data; + tlv = (struct isns_tlv *) hdr->pdu; + + memset(hdr, 0, sizeof(task->data)); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, + sizeof(node), &node); + length += isns_tlv_set(&tlv, 0, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_IP_ADDRESS, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_PORT, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PG_ISCSI_NAME, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PG_PORTAL_IP_ADDRESS, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PG_PORTAL_PORT, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PG_TAG, 0, 0); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + task->transaction = ++transaction; + isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_QRY, length, flags, + task->transaction, 0); + + task->len = length + sizeof(*hdr); + task->state = ISNS_TASK_SEND_PDU; + + qtask->rsp.command = MGMT_IPC_ISNS_DEV_ATTR_QUERY; + + queue_produce(isns_queue, EV_CONN_POLL, task, 0, NULL); + actor_schedule(&isns_actor); + + return MGMT_IPC_OK; +} + +void isns_handle(int listen_fd) +{ + struct sockaddr_storage from; + socklen_t slen = sizeof(from); + int fd; + struct isns_task *task; + + fd = accept(listen_fd, (struct sockaddr *) &from, &slen); + if (fd < 0) { + log_error("%s: accept error %m", __FUNCTION__); + return; + } + + task = malloc(sizeof(*task)); + if (!task) { + log_error("%s %m", __FUNCTION__); + close(fd); + return; + } + + memset(task, 0, sizeof(*task)); + task->state = ISNS_TASK_RECV_PDU; + task->fd = fd; + + queue_produce(isns_queue, EV_CONN_POLL, task, 0, NULL); + actor_schedule(&isns_actor); +} + +static void isns_poll(queue_item_t *item) +{ + int err, finished; + struct pollfd pfd; + struct isns_task *task = item->context; + struct isns_hdr *hdr = (struct isns_hdr *) task->data; + uint16_t function = ntohs(hdr->function); + + pfd.fd = task->fd; + switch (task->state) { + case ISNS_TASK_WAIT_CONN: + case ISNS_TASK_SEND_PDU: + pfd.events = POLLOUT; + break; + case ISNS_TASK_RECV_PDU: + pfd.events = POLLIN; + } + + err = poll(&pfd, 1, 1); + if (err > 0) { + switch (task->state) { + case ISNS_TASK_WAIT_CONN: + task->state = ISNS_TASK_SEND_PDU; + case ISNS_TASK_SEND_PDU: + err = isns_send_pdu(task); + if (err) + goto abort_task; + else { + + if (task->done == task->len) { + task->state = ISNS_TASK_RECV_PDU; + task->done = task->len = 0; + + if (function == ISNS_FUNC_ESI_RSP) + goto free_task; + } + + queue_produce(isns_queue, EV_CONN_POLL, task, 0, + NULL); + actor_schedule(&isns_actor); + } + break; + case ISNS_TASK_RECV_PDU: + err = isns_recv_pdu(task); + if (err) + goto abort_task; + else { + if (task->done == + task->len + sizeof(struct isns_hdr)) { + finished = isns_task_done(task); + if (finished) + goto free_task; + } else { + /* need to read more */ + queue_produce(isns_queue, EV_CONN_POLL, + task, 0, NULL); + actor_schedule(&isns_actor); + } + } + } + } else if (!err) { + /* FIXME */ + if (task->retry++ > max_retry) { + log_error("abort task"); + goto abort_task; + } else { + queue_produce(isns_queue, EV_CONN_POLL, task, 0, NULL); + actor_schedule(&isns_actor); + } + } + + return; +abort_task: + if (task->qtask) + send_mgmt_rsp(task, 1); +free_task: + isns_free_task(task); +} + +static void isns_control(void *data) +{ + int count = isns_queue->count, i; + int err; + unsigned char item_buf[sizeof(queue_item_t) + EVENT_PAYLOAD_MAX]; + queue_item_t *item = (queue_item_t *)(void *)item_buf; + + for (i = 0; i < count; i++) { + err = queue_consume(isns_queue, EVENT_PAYLOAD_MAX, item); + if (err == QUEUE_IS_EMPTY) { + log_debug(4, "%d items flushed while mainloop " + "was processing", count - i); + break; + } + + switch (item->event_type) { + case EV_CONN_POLL: + isns_poll(item); + break; + default: + log_error("%d unknown event type", item->event_type); + break; + } + } +} + +static int isns_dev_register(void) +{ + struct isns_task *task; + + task = malloc(sizeof(*task)); + if (!task) + return -ENOMEM; + memset(task, 0, sizeof(*task)); + + task->fd = isns_connect(); + if (task->fd < 0) { + free(task); + return -ENOMEM; + } + + task->state = ISNS_TASK_WAIT_CONN; + build_dev_reg_req(task); + queue_produce(isns_queue, EV_CONN_POLL, task, 0, NULL); + + actor_new(&isns_actor, isns_control, NULL); + actor_schedule(&isns_actor); + + return 0; +} + +static int isns_listen_init(int *listen_fd) +{ + int fd, opt, err; + struct sockaddr_storage lss; + socklen_t slen; + + fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + log_error("%s %m", __FUNCTION__); + return -errno; + } + + opt = 1; + if (ss.ss_family == AF_INET6) { + err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, + sizeof(opt)); + if (err) + log_error("%s %m", __FUNCTION__); + goto out; + } + + err = listen(fd, 5); + if (err) { + log_error("%s %m", __FUNCTION__); + goto out; + } + + slen = sizeof(lss); + err = getsockname(fd, (struct sockaddr *) &lss, &slen); + if (err) { + log_error("%s %m", __FUNCTION__); + goto out; + } + + if (lss.ss_family == AF_INET6) + isns_listen_port = ((struct sockaddr_in6 *) &lss)->sin6_port; + else + isns_listen_port = ((struct sockaddr_in *) &lss)->sin_port; + + isns_listen_port = ntohs(isns_listen_port); +out: + if (err) { + close(fd); + return -1; + } else { + *listen_fd = fd; + return 0; + } +} + +int isns_init(void) +{ + char buf[2048], port[NI_MAXSERV]; + int fd = -1, err; + FILE *f; + + f = fopen(dconfig->config_file, "r"); + if (!f) + return -EIO; + + while (fgets(buf, sizeof(buf), f)) { + /* FIXME */ + if (buf[strlen(buf) - 1] == '\n') + buf[strlen(buf) - 1] = '\0'; + if (!strncmp(buf, "isns.address = ", 15)) + strncpy(isns_address, buf + 15, sizeof(isns_address)); + else if (!strncmp(buf, "isns.port = ", 12)) + isns_port = atoi(buf + 12); + } + + fclose(f); + + if (!strlen(isns_address)) + return -1; + + isns_queue = queue_create(4, 4, NULL, NULL); + if (!isns_queue) { + log_error("can't create queue %m"); + return -ENOMEM; + } + + snprintf(port, sizeof(port), "%d", isns_port); + err = resolve_address(isns_address, port, &ss); + if (err) { + log_error("can't resolve address %m, %s", isns_address); + goto free_queue; + } + + err = isns_listen_init(&fd); + if (err) + goto free_queue; + + isns_dev_register(); + return fd; + +free_queue: + queue_destroy(isns_queue); + return err; +} + +void isns_exit(void) +{ + int err, count, i; + unsigned char item_buf[sizeof(queue_item_t) + EVENT_PAYLOAD_MAX]; + queue_item_t *item = (queue_item_t *)(void *)item_buf; + + if (!isns_queue) + return; + + count = isns_queue->count; + /* + * TODO: Add some code to gracefully shutdown. + */ + for (i = 0; i < count; i++) { + err = queue_consume(isns_queue, EVENT_PAYLOAD_MAX, item); + if (err == QUEUE_IS_EMPTY) { + log_debug(4, "%d items flushed while mainloop " + "was processing", count - i); + break; + } + + log_debug(4, "Dropping event type %d\n", item->event_type); + switch (item->event_type) { + case EV_CONN_POLL: + isns_free_task(item->context); + continue; + default: + log_error("%d unknown event type", item->event_type); + continue; + } + } + + queue_destroy(isns_queue); +} Index: usr/iscsid.c =================================================================== --- usr/iscsid.c (revision 754) +++ usr/iscsid.c (revision 779) @@ -154,6 +154,7 @@ char *initiatorname_file = INITIATOR_NAME_FILE; char *pid_file = PID_FILE; int ch, longindex; + int isns_fd; uid_t uid = 0; gid_t gid = 0; struct sigaction sa_old; @@ -318,7 +319,9 @@ } actor_init(); - event_loop(ipc, control_fd, mgmt_ipc_fd); + isns_fd = isns_init(); + event_loop(ipc, control_fd, mgmt_ipc_fd, isns_fd); + isns_exit(); log_debug(1, "daemon stopping"); return 0; } Index: usr/iscsistart.c =================================================================== --- usr/iscsistart.c (revision 754) +++ usr/iscsistart.c (revision 779) @@ -328,7 +328,7 @@ * Start Main Event Loop */ actor_init(); - event_loop(ipc, control_fd, mgmt_ipc_fd); + event_loop(ipc, control_fd, mgmt_ipc_fd, -1); ipc->ctldev_close(); mgmt_ipc_close(mgmt_ipc_fd); Index: usr/iscsiadm.c =================================================================== --- usr/iscsiadm.c (revision 754) +++ usr/iscsiadm.c (revision 779) @@ -99,9 +99,9 @@ printf("\ iscsiadm -m discovery [ -dhV ] [ -t type -p ip:port [ -l ] ] | [ -p ip:port ] \ [ -o operation ] [ -n name ] [ -v value ]\n\ -iscsiadm -m node [ -dhV ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port | -M sysdir ] [ -l | -u ] ] \ +iscsiadm -m node [ -dhV ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port ] [ -l | -u ] ] \ [ [ -o operation ] [ -n name ] [ -v value ] [ -p ip:port ] ]\n\ -iscsiadm -m session [ -dhV ] [ -r sessionid [ -i | -R | -u | -s ] [ -o operation ] [ -n name ] [ -v value ] ]\n"); +iscsiadm -m session [ -dhV ] [ -r sessionid | sysfsdir [ -i | -R | -u | -s ] [ -o operation ] [ -n name ] [ -v value ] ]\n"); } exit(status == 0 ? 0 : -1); } @@ -256,11 +256,13 @@ struct session_mgmt_fn *mgmt = data; char *mode = mgmt->mode; idbm_t *db = mgmt->db; + iscsi_provider_t *p; node_rec_t rec; int rc = 0; /* for now skip qlogic and other HW and offload drivers */ - if (!get_transport_by_sid(sid)) + p = get_transport_by_sid(sid); + if (!p) return 0; if (idbm_node_read(db, &rec, targetname, address, port)) { @@ -272,6 +274,11 @@ targetname, address, port); return 0; } + + /* multiple drivers could be connected to the same portal */ + if (strcmp(rec.transport_name, p->name)) + return 0; + /* * we always skip on boot because if the user killed this on * they would not be able to do anything @@ -726,6 +733,21 @@ return rc; } +static int isns_dev_attr_query(idbm_t *db) +{ + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int err; + + memset(&req, 0, sizeof(iscsiadm_req_t)); + req.command = MGMT_IPC_ISNS_DEV_ATTR_QUERY; + + err = do_iscsid(&ipc_fd, &req, &rsp); + if (!err) + idbm_for_each_node(db, NULL, print_node_info); + return err; +} + static int verify_mode_params(int argc, char **argv, char *allowed, int skip_m) { @@ -874,6 +896,57 @@ return rc; } +static int parse_sid(char *session) +{ + struct stat statb; + char sys_session[64], *start, *last; + int sid = -1, len; + + if (stat(session, &statb)) { + log_debug(1, "Could not stat %s failed with %d", + session, errno); + if (index(session, '/')) { + log_error("%s is an invalid session path\n", + session); + exit(1); + } + return atoi(session); + } + + if (!S_ISDIR(statb.st_mode)) { + log_error("%s is not a directory", session); + exit(1); + } + + /* + * Given sysfs_device is a directory name of the form: + * + * /sys/devices/platform/hostH/sessionS/targetH:B:I/H:B:I:L + * /sys/devices/platform/hostH/sessionS/targetH:B:I + * /sys/devices/platform/hostH/sessionS + * + * We want to set sys_session to sessionS + */ + last = NULL; + start = strstr(session, "session"); + if (start && strncmp(start, "session", 7) == 0) { + len = strlen(start); + last = index(start, '/'); + /* + * If '/' not found last is NULL. + */ + if (last) + len = last - start; + strncpy(sys_session, start, len); + } else { + log_error("Unable to find session in %s", session); + exit(1); + } + + sscanf(sys_session, "session%d", &sid); + return sid; +} + int main(int argc, char **argv) { @@ -926,7 +999,7 @@ value = optarg; break; case 'r': - sid = atoi(optarg); + sid = parse_sid(optarg); if (sid < 0) { log_error("invalid sid '%s'", optarg); @@ -1028,9 +1101,10 @@ rc = -1; goto out; } else if (type == DISCOVERY_TYPE_ISNS) { - log_error("iSNS discovery is not fully " - "implemented yet."); - rc = -1; + if ((rc = isns_dev_attr_query(db)) > 0) { + iscsid_handle_error(rc); + rc = -1; + } goto out; } else if (type < 0) { if (ip) { Index: etc/iscsid.conf =================================================================== --- etc/iscsid.conf (revision 754) +++ etc/iscsid.conf (revision 779) @@ -7,6 +7,13 @@ # and man page for iscsiadm for details on the --op command. # +################ +# iSNS settings +################ +# Address of iSNS server +#isns.address = 192.168.0.1 +#isns.port = 3205 + #***************** # Startup settings #***************** @@ -124,8 +131,8 @@ # in an iSCSI PDU from a target, edit the following line. # # The value is the number of bytes in the range of 512 to (2^24-1) and -# the default is 65536 -node.conn[0].iscsi.MaxRecvDataSegmentLength = 65536 +# the default is 131072 +node.conn[0].iscsi.MaxRecvDataSegmentLength = 131072 # To allow the targets to control the setting of the digest checking, # with the initiator requesting a preference of enabling the checking, uncomment# one or both of the following lines: @@ -151,3 +158,8 @@ # The default is to never use DataDigests and to allow the target to control # the setting of the HeaderDigest checking with the initiator requesting # a preference of disabling the checking. + +# +# To enable the iSNS server +#isns.address = 192.168.0.1 +#isns.port = 3205 Index: etc/initd/initd.suse =================================================================== --- etc/initd/initd.suse (revision 754) +++ etc/initd/initd.suse (revision 779) @@ -69,7 +69,7 @@ else echo -n "Starting iSCSI initiator service: " modprobe iscsi_tcp - modprobe ib_iser + modprobe -q ib_iser startproc $DAEMON $ARGS RETVAL=$? rc_status -v @@ -89,7 +89,7 @@ if [ "$RETVAL" == "0" ]; then rm -f $PID_FILE modprobe -r iscsi_tcp - modprobe -r ib_iser + modprobe -q -r ib_iser rc_failed 0 else rc_failed 1 Index: Makefile =================================================================== --- Makefile (revision 754) +++ Makefile (revision 779) @@ -15,7 +15,8 @@ initddir = $(etcdir)/init.d MANPAGES = doc/iscsid.8 doc/iscsiadm.8 doc/iscsi_discovery.8 -PROGRAMS = usr/iscsid usr/iscsiadm utils/iscsi_discovery +PROGRAMS = usr/iscsid usr/iscsiadm utils/iscsi_discovery \ + utils/fwparam_ibft/fwparam_ibft utils/iscsi-iname INSTALL = install ETCFILES = etc/iscsid.conf @@ -26,6 +27,8 @@ all: $(MAKE) -C usr $(MAKE) -C kernel + $(MAKE) -C utils + $(MAKE) -C utils/fwparam_ibft @echo @echo "Compilation complete Output file" @echo "----------------------------------- ----------------" @@ -34,22 +37,25 @@ @echo "Built iSCSI over TCP kernel module: kernel/iscsi_tcp.ko" @echo "Built iSCSI daemon: usr/iscsid" @echo "Built management application: usr/iscsiadm" + @echo "Built utility: utils/fwparam_ibft/fwparam_ibft" @echo @echo Read README file for detailed information. clean: + $(MAKE) -C utils clean $(MAKE) -C usr clean $(MAKE) -C kernel clean + $(MAKE) -C utils/fwparam_ibft clean # this is for safety # now -jXXX will still be safe # note that make may still execute the blocks in parallel .NOTPARALLEL: install_usr install_programs install_initd \ install_initd_suse install_initd_redhat install_initd_debian \ - install_etc install_doc install_kernel + install_etc install_doc install_kernel install_iname install: install_kernel install_programs install_doc install_etc \ - install_initd + install_initd install_iname install_programs: $(PROGRAMS) $(INSTALL) -d $(DESTDIR)$(sbindir) @@ -93,4 +99,13 @@ install_kernel: $(MAKE) -C kernel install_kernel +install_iname: + if [ ! -f /etc/iscsi/initiatorname.iscsi ]; then \ + echo "InitiatorName=`/sbin/iscsi-iname`" > /etc/iscsi/initiatorname.iscsi ; \ + echo "***************************************************" ; \ + echo "Setting InitiatorName to `cat /etc/iscsi/initiatorname.iscsi`" ; \ + echo "To override edit /etc/iscsi/initiatorname.iscsi" ; \ + echo "***************************************************" ; \ + fi + # vim: ft=make tw=72 sw=4 ts=4: Index: README =================================================================== --- README (revision 754) +++ README (revision 779) @@ -4,7 +4,7 @@ ================================================================= - November 8, 2006 + Jan 26, 2007 Contents ======== @@ -16,11 +16,9 @@ - 5. Open-iSCSI Configuration Utility - 6. Configuration - 7. Getting Started -- 8. TBD -- Appendix A. SendTargets snapshot. +- 8. iSCSI System Info - 1. In This Release ================== @@ -36,7 +34,6 @@ 1.1. Features - highly optimized and very small-footprint data path; - - multiple outstanding R2Ts; - persistent configuration database; - SendTargets discovery; - CHAP; @@ -151,6 +148,15 @@ The utility presents set of operations that a user can perform on iSCSI nodes, sessions, connections, and discovery records. +Open-iscsi does not use the term node as defined by the iSCSI RFC, +where a node is a single iSCSI initiator or target. Open-iscsi uses the +term node to refer to a portal on a target, so tools like iscsiadm +require that --targetname and --portal argument be used when in node mode. + +For session mode, a session id (sid) is used. The sid of a session can be +found by running iscsiadm -m session -i. The session id is not currently +persistent and is partially determined by when the session is setup. + Note that some of the iSCSI Node and iSCSI Discovery operations do not require iSCSI daemon (iscsid) loaded. @@ -211,7 +217,8 @@ -h, --help display this help and exit - Usage examples (using the one-letter options): + Usage examples using the one-letter options (see iscsiadm man page + for long options): 1) SendTargets iSCSI Discovery: @@ -278,12 +285,17 @@ 7. Getting Started ================== -There are three steps needed to set up a system to use iscsi storage: -1. automate iscsi startup using the init script. -2. discover targets. -3. automate target logins for future system reboots. +There are three steps needed to set up a system to use iSCSI storage: +7.1. iSCSI startup using the init script or manual startup. +7.2. Discover targets. +7.3. Automate target logins for future system reboots. -1. automate iscsi startup using the init script +The init scripts will start the iSCSI daemon and log into any +connections or nodes that are set up for automatic login. If your distro +does not have a init script, then you will have to start the daemon +and log into the targets manually. + +7.1.1 iSCSI startup using the init script ----------------------------------------------- Red Hat or Fedora: @@ -312,8 +324,11 @@ will usually get you started. -Other: ------- +7.1.2 Manual Startup: +--------------------- + +7.1.2.1 Starting up the iSCSI daemon (iscsid) and loading modules: +----------------------------------------------------------------- If there is no initd script, you must start the tools by hand. First load the iscsi modules with: @@ -328,26 +343,70 @@ ./iscsid -d8 -f & -and use configuration utility to add/remove/update Discovery records, -iSCSI Node records or monitor active iSCSI sessions (see above or the -iscsiadm man files). +7.1.2.2 Logging into Targets: +--------------------------- +Use the configuration utility, iscsiadm, to add/remove/update Discovery +records, iSCSI Node records or monitor active iSCSI sessions (see above or the +iscsiadm man files and see section 7.2 below for how to discover targets). - ./iscsiadm + ./iscsiadm -m node +will print out the nodes that have been discovered as: -To login: + 10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 + 10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 +The format is: + +ip:port,target_portal_group_tag targetname + +where targetname is the name of the target and ip_address:port is the address +and port of the portal. target_portal_group_tag, is the portal group tag of +the portal, and is not used in iscsiadm commands. + +To login, take the ip, port and targetname from above and run: + ./iscsiadm -m node -T targetname -p ip:port -l -where targetname is the name of the target amd ip_address:port is the address -and port of the portal of a discovered or manually added iSCSI Target Node -(for iscsiadm usage examples see previous sections). +In this example we would run -2. discover targets -------------------- -Once iscsi is up, you can perform discovery to targets using: -iscsiadm -m discovery -t sendtargets -p 192.168.1.1:3260 + ./iscsiadm -m node -T iqn.1992-08.com.netapp:sn.33615311 -p 10.15.84.19:3260 -l + Note: drop the portal group tag from the "iscsiadm -m node" output. + +7.2. Discover Targets +--------------------- +Once the iSCSI service is running, you can perform discovery using +SendTarget with: + +iscsiadm -m discovery -t sendtargets -p ip:port + +where "ip" is the address of the portal and port is the port. + +Or you can you perform discovery using iSNS by setting the address +of the iSNS server in iscsid.conf with the "isns.address" value and +running: + +iscsiadm -m discovery -t isns + +Both commands will print out the list of all discovered targets and their +portals: + +# iscsiadm -m discovery -t st -p 10.15.85.19:3260 +10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 +10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 + +Note: this prints out every node in the db including the ones just discovered. +This is a bug and will change in future releases. + +The format for the output is: + +ip:port,target_portal_group_tag targetname + +In this example, for the first target the ip address is 10.15.85.19. +The port is 3260. The target portal group is 3, and the target name +is iqn.1992-08.com.netapp:sn.33615311. + While discovery targets are kept in the discovery db, they are usefull only for re-discovery. The discovered targets (a.k.a. nodes) are stored as records in the node db. @@ -356,12 +415,16 @@ into the discovered nodes (making LUs from those nodes available as storage), it is better to automate the login to the nodes we need. -3. automate target logins for future system reboots ---------------------------------------------------- -Note: this may only work for Red Hat, Fedora and SUSE configurations +If you wish to log into a target manually now, see section +"7.1.2.2 Logging in targets" above. -To automate login to a node, use the following with the record ID of the -node discovered in the discovery above: +7.3. Automate Target Logins for Future System Statups +----------------------------------------------------- +Note: this may only work for distros with init scripts. + +To automate login to a node, use the following with the record ID +(record ID is the targetname and portal) of the node discovered in the +discovery above: iscsiadm -m node -T targetname -p ip:port --op update -n node.conn[0].startup -v automatic Or to set the "node.conn[0].statup" attribute to "startup" as default for @@ -369,36 +432,37 @@ node.conn[0].startup = automatic -To login to all the automated nodes, simply restart the iscsi service -e.g /etc/init.d/open-iscsi restart +Setting this in iscsid.conf, will not affect existing nodes. It will only +affect nodes that are discovered after setting the value. +To login to all the automated nodes, simply restart the iscsi service: +e.g /etc/init.d/open-iscsi restart. On your next startup the nodes will +be logged into autmotically. -8. TBD -====== -To be completed: +8. iSCSI System Info +==================== - - Kernel tracing and Troubleshooting - - Immediate and not-so-immediate plans - - Useful scripts - - White paper on Open-iSCSI design +To get information about the running sessions: including the session and +device state, session ids (sid) for session mode, and some of the +negioated parameters, run: + iscsiadm -m session -i -Appendix A. SendTargets iSCSI Discovery session snapshot. -========================================================= +If you are looking for something shorter like just the sid to node mapping +run: --bash-2.05b# ./iscsiadm -m discovery -tst -p 10.16.16.223:3260 -[02f611] 10.16.16.223:3260,1 iqn.2002-07.com.ttechnologies.target.a -[01acd1] 17.1.1.223:3260,1 iqn.2002-07.com.ttechnologies.target.a --bash-2.05b# --bash-2.05b# ./iscsiadm -m node -[02f611] 10.16.16.223:3260,1 iqn.2002-07.com.ttechnologies.target.a -[01acd1] 17.1.1.223:3260,1 iqn.2002-07.com.ttechnologies.target.a --bash-2.05b# --bash-2.05b# ./iscsiadm -m discovery -tst -p 10.16.16.227:3260 -[02fb91] 10.16.16.227:3260,1 iqn.2001-04.com.example:storage.disk2.sys1.xyz --bash-2.05b# --bash-2.05b# ./iscsiadm -m node -[02f611] 10.16.16.223:3260,1 iqn.2002-07.com.ttechnologies.target.a -[02fb91] 10.16.16.227:3260,1 iqn.2001-04.com.example:storage.disk2.sys1.xyz -[01acd1] 17.1.1.223:3260,1 iqn.2002-07.com.ttechnologies.target.a + iscsiadm -m session + +This will print the list of running sessions with the format: + +kernel module: [sid] ip:port,target_portal_group_tag targetname + +# iscsiadm -m session +tcp: [2] 10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 +tcp: [3] 10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 + +For example this first node is using the iscsi_tcp kernel module, has +session id (sid) 2, is connected to a portal with address and port +10.15.84.19:3260 in portal group 2 on the target, +iqn.1992-08.com.netapp:sn.33615311.