This is the core iscsi tcp target coide. It demonstates how a target LLD can reuse a existing transport class (in this case we use the iscsi transport class's interface) and hook into scsi-tgt. The interesting parts are the scsi_host_template and iscsi_tranpsort templates. This patch alone does not compile. I am just sending it as a way to show how it all works. There is tons of cleanup needed before this is ready for a review and the goal is to kill most of this code and either merge the target PDU handling with iscsi_tcp.c in mainline already or move parts of iscsi_tcp.c to the transport class where it can be shared. Most likely a lot of both will happen. diff -Naurp linux-2.6.16-rc1/drivers/scsi/iscsi_tcp_tgt.c linux-2.6.16-rc1.tmp/drivers/scsi/iscsi_tcp_tgt.c --- linux-2.6.16-rc1/drivers/scsi/iscsi_tcp_tgt.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.16-rc1.tmp/drivers/scsi/iscsi_tcp_tgt.c 2006-01-24 20:25:06.000000000 -0600 @@ -0,0 +1,1941 @@ +/* + * (C) 2004 - 2005 FUJITA Tomonori <tomof@xxxxxxx> + * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@xxxxxxxxxxxxx> + * Copyright (C) 2005 - 2006 Mike Christie + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include <linux/module.h> +#include <linux/hash.h> +#include <linux/mempool.h> +#include <net/tcp.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tgt.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_iscsi.h> +#include <iscsi.h> + +static kmem_cache_t *istgt_cmd_cache; +static char dummy_data[1024]; + +static uint32_t cmnd_write_size(struct istgt_cmd *cmnd) +{ + struct iscsi_cmd *hdr = cmd_hdr(cmnd); + + if (hdr->flags & ISCSI_FLAG_CMD_WRITE) + return be32_to_cpu(hdr->data_length); + return 0; +} + +static uint32_t cmnd_read_size(struct istgt_cmd *cmnd) +{ + struct iscsi_cmd *hdr = cmd_hdr(cmnd); + + if (hdr->flags & ISCSI_FLAG_CMD_READ) { + if (!(hdr->flags & ISCSI_FLAG_CMD_WRITE)) + return be32_to_cpu(hdr->data_length); + if (hdr->flags & ISCSI_FLAG_CMD_READ) { + struct iscsi_rlength_ahdr *ahdr = + (struct iscsi_rlength_ahdr *)cmnd->pdu.ahs; + if (ahdr && ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH) + return be32_to_cpu(ahdr->read_length); + } + } + return 0; +} + +/* + * create a new command. + * + * iscsi_cmnd_create - + * @conn: ptr to connection (for i/o) + * + * @return ptr to command or NULL + */ + +struct istgt_cmd *cmnd_alloc(struct iscsi_conn *conn, int req) +{ + struct istgt_cmd *cmnd; + + /* TODO: async interface is necessary ? */ + cmnd = kmem_cache_alloc(istgt_cmd_cache, GFP_KERNEL | __GFP_NOFAIL); + + memset(cmnd, 0, sizeof(*cmnd)); + INIT_LIST_HEAD(&cmnd->list); + INIT_LIST_HEAD(&cmnd->pdu_list); + INIT_LIST_HEAD(&cmnd->conn_list); + INIT_LIST_HEAD(&cmnd->hash_list); + cmnd->conn = conn; + spin_lock(&conn->list_lock); + atomic_inc(&conn->nr_cmnds); + init_completion(&cmnd->event); + if (req) + list_add_tail(&cmnd->conn_list, &conn->pdu_list); + spin_unlock(&conn->list_lock); + cmnd->sg = NULL; + + if (req) + BUG_ON(!conn->session); + + dprintk("%p:%p\n", conn, cmnd); + + return cmnd; +} + +/** + * create a new command used as response. + * + * iscsi_cmnd_create_rsp_cmnd - + * @cmnd: ptr to request command + * + * @return ptr to response command or NULL + */ + +static struct istgt_cmd *iscsi_cmnd_create_rsp_cmnd(struct istgt_cmd *cmnd, int final) +{ + struct istgt_cmd *rsp = cmnd_alloc(cmnd->conn, 0); + + if (final) + set_cmd_final(rsp); + list_add_tail(&rsp->pdu_list, &cmnd->pdu_list); + rsp->req = cmnd; + return rsp; +} + +static struct istgt_cmd *get_rsp_cmnd(struct istgt_cmd *req) +{ + return list_entry(req->pdu_list.prev, struct istgt_cmd, pdu_list); +} + +static void iscsi_cmnds_init_write(struct list_head *send) +{ + struct istgt_cmd *cmnd = list_entry(send->next, struct istgt_cmd, list); + struct iscsi_conn *conn = cmnd->conn; + struct list_head *pos, *next; + + spin_lock(&conn->list_lock); + + list_for_each_safe(pos, next, send) { + cmnd = list_entry(pos, struct istgt_cmd, list); + + dprintk("%p:%x\n", cmnd, cmd_opcode(cmnd)); + + list_del_init(&cmnd->list); + BUG_ON(conn != cmnd->conn); + list_add_tail(&cmnd->list, &conn->write_list); + } + + spin_unlock(&conn->list_lock); + + nthread_wakeup(conn->session); +} + +static void iscsi_cmnd_init_write(struct istgt_cmd *cmnd) +{ + LIST_HEAD(head); + + if (!list_empty(&cmnd->list)) { + eprintk("%x %x %x %x %lx %u %u %u %u %u %u %u %d %d\n", + cmd_itt(cmnd), cmd_ttt(cmnd), cmd_opcode(cmnd), + cmd_scsicode(cmnd), cmnd->flags, + cmnd->r2t_sn, cmnd->r2t_length, cmnd->is_unsolicited_data, + cmnd->target_task_tag, cmnd->outstanding_r2t, + cmnd->hdigest, cmnd->ddigest, + list_empty(&cmnd->pdu_list), list_empty(&cmnd->hash_list)); + + BUG_ON(!list_empty(&cmnd->list)); + } + list_add(&cmnd->list, &head); + iscsi_cmnds_init_write(&head); +} + +static void do_send_data_rsp(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct istgt_cmd *data_cmnd; + struct scatterlist *sg = cmnd->scmd->request_buffer; + struct iscsi_cmd *req = cmd_hdr(cmnd); + struct iscsi_data_rsp *rsp; + uint32_t pdusize, expsize, scsisize, size, offset, sn; + LIST_HEAD(send); + + dprintk("%p\n", cmnd); + pdusize = conn->session->param.max_xmit_data_length; + expsize = cmnd_read_size(cmnd); + size = min(expsize, cmnd->scmd->request_bufflen); + dprintk("%u %u\n", expsize, cmnd->scmd->request_bufflen); + offset = 0; + sn = 0; + + BUG_ON(!sg); + + while (1) { + data_cmnd = iscsi_cmnd_create_rsp_cmnd(cmnd, size <= pdusize); + data_cmnd->sg = sg; + rsp = (struct iscsi_data_rsp *)&data_cmnd->pdu.bhs; + + rsp->opcode = ISCSI_OP_SCSI_DATA_IN; + rsp->itt = req->itt; + rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + rsp->offset = offset; + rsp->datasn = cpu_to_be32(sn); + + if (size <= pdusize) { + data_cmnd->pdu.datasize = size; + rsp->flags = ISCSI_FLAG_CMD_FINAL | + ISCSI_FLAG_DATA_STATUS; + + scsisize = cmnd->scmd->request_bufflen; + if (scsisize < expsize) { + rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW; + size = expsize - scsisize; + } else if (scsisize > expsize) { + rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW; + size = scsisize - expsize; + } else + size = 0; + rsp->residual_count = cpu_to_be32(size); + list_add_tail(&data_cmnd->list, &send); + + break; + } + + data_cmnd->pdu.datasize = pdusize; + + size -= pdusize; + offset += pdusize; + sn++; + + list_add_tail(&data_cmnd->list, &send); + } + + iscsi_cmnds_init_write(&send); +} + +static struct istgt_cmd *create_scsi_rsp(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_cmd *req_hdr = cmd_hdr(req); + struct iscsi_cmd_rsp *rsp_hdr; + + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + + rsp_hdr = (struct iscsi_cmd_rsp *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_SCSI_CMD_RSP; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->response = ISCSI_STATUS_CMD_COMPLETED; + rsp_hdr->cmd_status = SAM_STAT_GOOD; + rsp_hdr->itt = req_hdr->itt; + + return rsp; +} + +static void send_scsi_rsp(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_cmd_rsp *rsp_hdr; + uint32_t size; + + rsp = create_scsi_rsp(req); + rsp_hdr = (struct iscsi_cmd_rsp *) &rsp->pdu.bhs; + if ((size = cmnd_read_size(req)) != 0) { + rsp_hdr->flags |= ISCSI_FLAG_CMD_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(size); + } + + iscsi_cmnd_init_write(rsp); +} + +static struct istgt_cmd *do_create_sense_rsp(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_cmd_rsp *rsp_hdr; + struct iscsi_sense_data *sense = &req->sense; + struct scatterlist *sg = &req->sense_sg; + struct scatterlist *sg_data = req->scmd->request_buffer; + struct page *page; + + page = sg_data[0].page; + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + + rsp_hdr = (struct iscsi_cmd_rsp *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_SCSI_CMD_RSP; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->response = ISCSI_STATUS_CMD_COMPLETED; + rsp_hdr->cmd_status = SAM_STAT_CHECK_CONDITION; + rsp_hdr->itt = cmd_hdr(req)->itt; + + memcpy(sense->sense_buff, req->scmd->sense_buffer, + sizeof(sense->sense_buff)); + /* + * this looks broken for ppc + */ + sense->length = cpu_to_be16(req->scmd->request_bufflen); + + sg->page = virt_to_page(sense); + sg->offset = offset_in_page(sense); + sg->length = req->scmd->request_bufflen + sizeof(uint16_t); + rsp->pdu.datasize = sg->length; + rsp->sg = sg; + + return rsp; +} + +static struct istgt_cmd *create_sense_rsp(struct istgt_cmd *req, + uint8_t sense_key, uint8_t asc, uint8_t ascq) +{ + struct istgt_cmd *rsp; + struct iscsi_cmd_rsp *rsp_hdr; + struct scatterlist *sg = &req->sense_sg; + struct iscsi_sense_data *sense = &req->sense; + uint8_t *data = sense->sense_buff; + + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + + rsp_hdr = (struct iscsi_cmd_rsp *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_SCSI_CMD_RSP; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->response = ISCSI_STATUS_CMD_COMPLETED; + rsp_hdr->cmd_status = SAM_STAT_CHECK_CONDITION; + rsp_hdr->itt = cmd_hdr(req)->itt; + + sg->page = virt_to_page(sense); + sg->offset = offset_in_page(sense); + + sense->length = cpu_to_be16(14); + data[0] = 0xf0; + data[2] = sense_key; + data[7] = 6; // Additional sense length + data[12] = asc; + data[13] = ascq; + + rsp->pdu.datasize = sizeof(uint16_t) + 14; + rsp->sg = sg; + + sg->length = (rsp->pdu.datasize + 3) & -4; + + return rsp; +} + +/** + * Free a command. + * Also frees the additional header. + * + * iscsi_cmnd_remove - + * @cmnd: ptr to command + */ + +void iscsi_cmnd_remove(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn; + + if (!cmnd) + return; + dprintk("%p\n", cmnd); + conn = cmnd->conn; + kfree(cmnd->pdu.ahs); + + if (!list_empty(&cmnd->list)) { + struct iscsi_cmd *req = cmd_hdr(cmnd); + + eprintk("cmnd %p still on some list?, %x %x %x %x %x %x %x %lx\n", + cmnd, req->opcode, req->cdb[0], req->flags, req->itt, + be32_to_cpu(req->data_length), req->cmdsn, + be32_to_cpu(cmnd->pdu.datasize), conn->state); + + if (cmnd->req) { + struct iscsi_cmd *req = cmd_hdr(cmnd->req); + eprintk("%p %x %u\n", req, req->opcode, req->cdb[0]); + } + BUG(); + } + list_del(&cmnd->list); + spin_lock(&conn->list_lock); + atomic_dec(&conn->nr_cmnds); + list_del(&cmnd->conn_list); + spin_unlock(&conn->list_lock); + + if (cmnd->scmd) + cmnd->done(cmnd->scmd); + kmem_cache_free(istgt_cmd_cache, cmnd); +} + +static void cmnd_skip_pdu(struct istgt_cmd *cmnd) +{ +/* struct iscsi_conn *conn = cmnd->conn; */ +/* struct tio *tio = cmnd->tio; */ +/* char *addr; */ +/* u32 size; */ +/* int i; */ + + BUG_ON(1); + +/* eprintk("%x %x %x %u\n", cmd_itt(cmnd), cmd_opcode(cmnd), */ +/* cmd_hdr(cmnd)->cdb[0], cmnd->pdu.datasize); */ + +/* if (!(size = cmnd->pdu.datasize)) */ +/* return; */ + +/* if (tio) */ +/* assert(tio->pg_cnt > 0); */ +/* else */ +/* tio = cmnd->tio = tio_alloc(1); */ + +/* addr = page_address(tio->pvec[0]); */ +/* assert(addr); */ +/* size = (size + 3) & -4; */ +/* conn->read_size = size; */ +/* for (i = 0; size > PAGE_CACHE_SIZE; i++, size -= PAGE_CACHE_SIZE) { */ +/* assert(i < ISCSI_CONN_IOV_MAX); */ +/* conn->read_iov[i].iov_base = addr; */ +/* conn->read_iov[i].iov_len = PAGE_CACHE_SIZE; */ +/* } */ +/* conn->read_iov[i].iov_base = addr; */ +/* conn->read_iov[i].iov_len = size; */ +/* conn->read_msg.msg_iov = conn->read_iov; */ +/* conn->read_msg.msg_iovlen = ++i; */ +} + +static void iscsi_cmnd_reject(struct istgt_cmd *req, int reason) +{ +/* struct istgt_cmd *rsp; */ +/* struct iscsi_reject_hdr *rsp_hdr; */ +/* struct tio *tio; */ +/* char *addr; */ + + BUG_ON(1); + +/* rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); */ +/* rsp_hdr = (struct iscsi_reject_hdr *)&rsp->pdu.bhs; */ + +/* rsp_hdr->opcode = ISCSI_OP_REJECT; */ +/* rsp_hdr->ffffffff = ISCSI_RESERVED_TAG; */ +/* rsp_hdr->reason = reason; */ + +/* rsp->tio = tio = tio_alloc(1); */ +/* addr = page_address(tio->pvec[0]); */ +/* clear_page(addr); */ +/* memcpy(addr, &req->pdu.bhs, sizeof(struct iscsi_hdr)); */ +/* tio->size = rsp->pdu.datasize = sizeof(struct iscsi_hdr); */ +/* cmnd_skip_pdu(req); */ + +/* req->pdu.bhs.opcode = ISCSI_OP_PDU_REJECT; */ +} + +static void cmnd_set_sn(struct istgt_cmd *cmnd, int set_stat_sn) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iscsi_session *sess = conn->session; + + if (set_stat_sn) + cmnd->pdu.bhs.statsn = cpu_to_be32(conn->stat_sn++); + cmnd->pdu.bhs.exp_statsn = cpu_to_be32(sess->exp_cmd_sn); + cmnd->pdu.bhs.max_statsn = cpu_to_be32(sess->exp_cmd_sn + + sess->max_queued_cmnds); +} + +static void update_stat_sn(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + uint32_t exp_stat_sn; + + cmnd->pdu.bhs.exp_statsn = exp_stat_sn = be32_to_cpu(cmnd->pdu.bhs.exp_statsn); + dprintk("%x,%x\n", cmd_opcode(cmnd), exp_stat_sn); + if ((int32_t) (exp_stat_sn - conn->exp_stat_sn) > 0 && + (int32_t) (exp_stat_sn - conn->stat_sn) <= 0) { + // free pdu resources + cmnd->conn->exp_stat_sn = exp_stat_sn; + } +} + +static int check_cmd_sn(struct istgt_cmd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + uint32_t cmd_sn; + + cmnd->pdu.bhs.statsn = cmd_sn = be32_to_cpu(cmnd->pdu.bhs.statsn); + dprintk("%d(%d)\n", cmd_sn, session->exp_cmd_sn); + if ((int32_t) (cmd_sn - session->exp_cmd_sn) >= 0) + return 0; + eprintk("sequence error (%x,%x)\n", cmd_sn, session->exp_cmd_sn); + return -ISCSI_REASON_PROTOCOL_ERROR; +} + +static struct istgt_cmd *__cmnd_find_hash(struct iscsi_session *session, + uint32_t itt, uint32_t ttt) +{ + struct list_head *head; + struct istgt_cmd *cmnd; + + head = &session->cmnd_hash[cmnd_hashfn(itt)]; + + list_for_each_entry(cmnd, head, hash_list) { + if (cmnd->pdu.bhs.itt == itt) { + if ((ttt != ISCSI_RESERVED_TAG) && (ttt != cmnd->target_task_tag)) + continue; + return cmnd; + } + } + + return NULL; +} + +static struct istgt_cmd *cmnd_find_hash(struct iscsi_session *session, + uint32_t itt, uint32_t ttt) +{ + struct istgt_cmd *cmnd; + + spin_lock(&session->cmnd_hash_lock); + + cmnd = __cmnd_find_hash(session, itt, ttt); + + spin_unlock(&session->cmnd_hash_lock); + + return cmnd; +} + +static int cmnd_insert_hash(struct istgt_cmd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct istgt_cmd *tmp; + struct list_head *head; + int err = 0; + uint32_t itt = cmnd->pdu.bhs.itt; + + dprintk("%p:%x\n", cmnd, itt); + if (itt == ISCSI_RESERVED_TAG) { + err = -ISCSI_REASON_PROTOCOL_ERROR; + goto out; + } + + head = &session->cmnd_hash[cmnd_hashfn(cmnd->pdu.bhs.itt)]; + + spin_lock(&session->cmnd_hash_lock); + + tmp = __cmnd_find_hash(session, itt, ISCSI_RESERVED_TAG); + if (!tmp) { + list_add_tail(&cmnd->hash_list, head); + set_cmd_hashed(cmnd); + } else + err = -ISCSI_REASON_TASK_IN_PROGRESS; + + spin_unlock(&session->cmnd_hash_lock); + + if (!err) { + update_stat_sn(cmnd); + err = check_cmd_sn(cmnd); + } + +out: + return err; +} + +static void __cmnd_remove_hash(struct istgt_cmd *cmnd) +{ + list_del(&cmnd->hash_list); +} + +static void cmnd_remove_hash(struct istgt_cmd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct istgt_cmd *tmp; + + spin_lock(&session->cmnd_hash_lock); + + tmp = __cmnd_find_hash(session, cmnd->pdu.bhs.itt, ISCSI_RESERVED_TAG); + + if (tmp && tmp == cmnd) + __cmnd_remove_hash(tmp); + else + eprintk("%p:%x not found\n", cmnd, cmd_itt(cmnd)); + + spin_unlock(&session->cmnd_hash_lock); +} + +static void cmnd_skip_data(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_cmd_rsp *rsp_hdr; + uint32_t size; + + rsp = get_rsp_cmnd(req); + rsp_hdr = (struct iscsi_cmd_rsp *)&rsp->pdu.bhs; + if (cmd_opcode(rsp) != ISCSI_OP_SCSI_CMD_RSP) { + eprintk("unexpected response command %u\n", cmd_opcode(rsp)); + return; + } + + size = cmnd_write_size(req); + if (size) { + rsp_hdr->flags |= ISCSI_FLAG_CMD_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(size); + } + size = cmnd_read_size(req); + if (size) { + if (cmd_hdr(req)->flags & ISCSI_FLAG_CMD_WRITE) { + rsp_hdr->flags |= ISCSI_FLAG_CMD_BIDI_UNDERFLOW; + rsp_hdr->bi_residual_count = cpu_to_be32(size); + } else { + rsp_hdr->flags |= ISCSI_FLAG_CMD_BIDI_OVERFLOW; + rsp_hdr->residual_count = cpu_to_be32(size); + } + } + req->pdu.bhs.opcode = + (req->pdu.bhs.opcode & ~ISCSI_OPCODE_MASK) | ISCSI_OP_SCSI_REJECT; + + cmnd_skip_pdu(req); +} + +static int cmnd_recv_pdu(struct iscsi_conn *conn, struct scsi_cmnd *scmd, + uint32_t offset, uint32_t size) +{ + int idx, i; + char *addr; + struct scatterlist *sg; + + dprintk("%u,%u\n", offset, size); + + BUG_ON(!scmd); + BUG_ON(!scmd->request_buffer); + sg = scmd->request_buffer; + offset += sg->offset; + + if (!(offset < sg->offset + scmd->request_bufflen) || + !(offset + size <= sg->offset + scmd->request_bufflen)) { + eprintk("%u %u %u %u", offset, size, sg->offset, + scmd->request_bufflen); + return -EIO; + } + BUG_ON(!(offset < sg->offset + scmd->request_bufflen)); + BUG_ON(!(offset + size <= sg->offset + scmd->request_bufflen)); + + idx = offset >> PAGE_CACHE_SHIFT; + offset &= ~PAGE_CACHE_MASK; + + conn->read_msg.msg_iov = conn->read_iov; + conn->read_size = (size + 3) & -4; + conn->read_overflow = 0; + + i = 0; + while (1) { + sg = scmd->request_buffer + idx; + BUG_ON(!sg); + BUG_ON(!sg->page); + addr = page_address(sg->page); + BUG_ON(!addr); + + conn->read_iov[i].iov_base = addr + offset; + if (offset + size <= PAGE_CACHE_SIZE) { + conn->read_iov[i].iov_len = size; + conn->read_msg.msg_iovlen = ++i; + break; + } + conn->read_iov[i].iov_len = PAGE_CACHE_SIZE - offset; + size -= conn->read_iov[i].iov_len; + offset = 0; + if (++i >= ISCSI_CONN_IOV_MAX) { + conn->read_msg.msg_iovlen = i; + conn->read_overflow = size; + conn->read_size -= size; + break; + } + + idx++; + } + + return 0; +} + +static void send_r2t(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_r2t_rsp *rsp_hdr; + uint32_t length, offset, burst; + LIST_HEAD(send); + + length = req->r2t_length; + burst = req->conn->session->param.max_burst_length; + offset = be32_to_cpu(cmd_hdr(req)->data_length) - length; + + do { + rsp = iscsi_cmnd_create_rsp_cmnd(req, 0); + rsp->pdu.bhs.ttt = req->target_task_tag; + + rsp_hdr = (struct iscsi_r2t_rsp *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_R2T; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + memcpy(rsp_hdr->lun, cmd_hdr(req)->lun, 8); + rsp_hdr->itt = cmd_hdr(req)->itt; + rsp_hdr->r2tsn = cpu_to_be32(req->r2t_sn++); + rsp_hdr->data_offset = cpu_to_be32(offset); + if (length > burst) { + rsp_hdr->data_length = cpu_to_be32(burst); + length -= burst; + offset += burst; + } else { + rsp_hdr->data_length = cpu_to_be32(length); + length = 0; + } + + dprintk("%x %u %u %u %u\n", cmd_itt(req), + be32_to_cpu(rsp_hdr->data_length), + be32_to_cpu(rsp_hdr->data_offset), + be32_to_cpu(rsp_hdr->r2tsn), req->outstanding_r2t); + + list_add_tail(&rsp->list, &send); + + if (++req->outstanding_r2t >= req->conn->session->param.max_outstanding_r2t) + break; + + } while (length); + + iscsi_cmnds_init_write(&send); +} + +static void __scsi_cmnd_done(void *data) +{ + struct scsi_cmnd *scmd = data; + struct istgt_cmd *cmnd = (struct istgt_cmd *) scmd->SCp.ptr; + struct iscsi_cmd *req = cmd_hdr(cmnd); + + if (scmd->result) { + struct istgt_cmd *rsp; + + rsp = do_create_sense_rsp(cmnd); + iscsi_cmnd_init_write(rsp); + return; + } + + switch (req->cdb[0]) { + case INQUIRY: + case REPORT_LUNS: + case READ_CAPACITY: + case MODE_SENSE: + case REQUEST_SENSE: + case SERVICE_ACTION_IN: + case READ_6: + case READ_10: + case READ_16: + do_send_data_rsp(cmnd); + break; + case WRITE_6: + case WRITE_10: + case WRITE_16: + case WRITE_VERIFY: + case START_STOP: + case TEST_UNIT_READY: + case SYNCHRONIZE_CACHE: + case VERIFY: + case VERIFY_16: + case RESERVE: + case RELEASE: + case RESERVE_10: + case RELEASE_10: + send_scsi_rsp(cmnd); + break; + default: + BUG_ON(1); + break; + } +} + +/* TODO : merge this with nthread. */ +static int scsi_cmnd_done(struct scsi_cmnd *scmd, + void (*done)(struct scsi_cmnd *)) +{ + struct istgt_cmd *cmnd = (struct istgt_cmd *) scmd->SCp.ptr; + int err; + + cmnd->done = done; + INIT_WORK(&cmnd->work, __scsi_cmnd_done, scmd); + err = schedule_work(&cmnd->work); + BUG_ON(!err); + + return TGT_CMD_XMIT_OK; +} + +static void tgt_scsi_cmd_create(struct istgt_cmd *req) +{ + struct iscsi_cmd *req_hdr = cmd_hdr(req); + struct scsi_cmnd *scmd; + enum dma_data_direction data_dir; + + /* + * handle bidi later + */ + if (req_hdr->flags & ISCSI_FLAG_CMD_WRITE) + data_dir = DMA_TO_DEVICE; + else if (req_hdr->flags & ISCSI_FLAG_CMD_READ) + data_dir = DMA_FROM_DEVICE; + else + data_dir = DMA_NONE; + + + scmd = scsi_host_get_command(req->conn->session->shost, data_dir, + GFP_KERNEL); + BUG_ON(!scmd); + req->scmd = scmd; + + memcpy(scmd->data_cmnd, req_hdr->cdb, MAX_COMMAND_SIZE); + scmd->request_bufflen = be32_to_cpu(req_hdr->data_length); + scmd->SCp.ptr = (char *) req; + + switch (req->pdu.bhs.flags & ISCSI_FLAG_CMD_ATTR_MASK) { + case ISCSI_ATTR_UNTAGGED: + case ISCSI_ATTR_SIMPLE: + scmd->tag = MSG_SIMPLE_TAG; + break; + case ISCSI_ATTR_ORDERED: + scmd->tag = MSG_ORDERED_TAG; + break; + case ISCSI_ATTR_HEAD_OF_QUEUE: + scmd->tag = MSG_HEAD_TAG; + break; + case ISCSI_ATTR_ACA: + scmd->tag = MSG_SIMPLE_TAG; + break; + default: + scmd->tag = MSG_SIMPLE_TAG; + } + + if (scmd->sc_data_direction == DMA_TO_DEVICE && + be32_to_cpu(req_hdr->data_length)) { + switch (req_hdr->cdb[0]) { + case WRITE_6: + case WRITE_10: + case WRITE_16: + case WRITE_VERIFY: + break; + default: + eprintk("%x\n", req_hdr->cdb[0]); + break; + } + } + + scsi_tgt_queue_command(scmd, (struct scsi_lun *)req_hdr->lun, 0); +} + +static void scsi_cmnd_exec(struct istgt_cmd *cmnd) +{ + struct scsi_cmnd *scmd = cmnd->scmd; + + if (cmnd->r2t_length) { + if (!cmnd->is_unsolicited_data) + send_r2t(cmnd); + } else { + set_cmd_waitio(cmnd); + if (scmd) { + if (!cmnd->done) + BUG(); + else + cmnd->done(scmd); + } else + tgt_scsi_cmd_create(cmnd); + } +} + +static int noop_out_start(struct iscsi_conn *conn, struct istgt_cmd *cmnd) +{ + uint32_t size, tmp; + int i = 0, err = 0; + + if (cmd_ttt(cmnd) != cpu_to_be32(ISCSI_RESERVED_TAG)) { + /* + * We don't request a NOP-Out by sending a NOP-In. + * See 10.18.2 in the draft 20. + */ + eprintk("initiator bug %x\n", cmd_itt(cmnd)); + err = -ISCSI_REASON_PROTOCOL_ERROR; + goto out; + } + + if (cmd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG)) { + if (!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE)) + eprintk("%s\n","initiator bug!"); + update_stat_sn(cmnd); + err = check_cmd_sn(cmnd); + goto out; + } else if ((err = cmnd_insert_hash(cmnd)) < 0) { + eprintk("ignore this request %x\n", cmd_itt(cmnd)); + goto out; + } + + if ((size = cmnd->pdu.datasize)) { + size = (size + 3) & -4; + conn->read_msg.msg_iov = conn->read_iov; + if (cmnd->pdu.bhs.itt != cpu_to_be32(ISCSI_RESERVED_TAG)) { +/* struct tio *tio; */ + int pg_cnt = get_pgcnt(size, 0); + + BUG_ON(pg_cnt >= ISCSI_CONN_IOV_MAX); + BUG_ON(1); +/* cmnd->tio = tio = tio_alloc(pg_cnt); */ +/* tio_set(tio, size, 0); */ + +/* for (i = 0; i < pg_cnt; i++) { */ +/* conn->read_iov[i].iov_base */ +/* = page_address(tio->pvec[i]); */ +/* tmp = min_t(u32, size, PAGE_CACHE_SIZE); */ +/* conn->read_iov[i].iov_len = tmp; */ +/* conn->read_size += tmp; */ +/* size -= tmp; */ +/* } */ + } else { + for (i = 0; i < ISCSI_CONN_IOV_MAX; i++) { + conn->read_iov[i].iov_base = dummy_data; + tmp = min_t(uint32_t, size, sizeof(dummy_data)); + conn->read_iov[i].iov_len = tmp; + conn->read_size += tmp; + size -= tmp; + } + } + BUG_ON(size); + conn->read_overflow = size; + conn->read_msg.msg_iovlen = i; + } + +out: + return err; +} + +static uint32_t get_next_ttt(struct iscsi_session *session) +{ + uint32_t ttt; + + if (session->next_ttt == ISCSI_RESERVED_TAG) + session->next_ttt++; + ttt = session->next_ttt++; + + return cpu_to_be32(ttt); +} + +static void scsi_cmnd_start(struct iscsi_conn *conn, struct istgt_cmd *req) +{ + struct iscsi_cmd *req_hdr = cmd_hdr(req); + + dprintk("scsi command: %02x\n", req_hdr->cdb[0]); + + switch (req_hdr->cdb[0]) { + case SERVICE_ACTION_IN: + if ((req_hdr->cdb[1] & 0x1f) != 0x10) + goto error; + + case INQUIRY: + case REPORT_LUNS: + case TEST_UNIT_READY: + case SYNCHRONIZE_CACHE: + case VERIFY: + case VERIFY_16: + case START_STOP: + case READ_CAPACITY: + case MODE_SENSE: + case REQUEST_SENSE: + case RESERVE: + case RELEASE: + case RESERVE_10: + case RELEASE_10: + case READ_6: + case READ_10: + case READ_16: + { + if (!(req_hdr->flags & ISCSI_FLAG_CMD_FINAL) || + req->pdu.datasize) { + /* unexpected unsolicited data */ + eprintk("%x %x\n", cmd_itt(req), req_hdr->cdb[0]); + create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc); + cmnd_skip_data(req); + } + break; + } + case WRITE_6: + case WRITE_10: + case WRITE_16: + case WRITE_VERIFY: + { + struct iscsi_sess_param *param = &conn->session->param; + + /* + * We don't know this command arrives in order, + * however we need to allocate buffer for immediate + * and unsolicited data. tgt will not start to perform + * this command until we call cmd->done so we don't + * need to worry about the order of the command. + */ + tgt_scsi_cmd_create(req); + wait_for_completion(&req->event); + + req->r2t_length = be32_to_cpu(req_hdr->data_length) - req->pdu.datasize; + req->is_unsolicited_data = !(req_hdr->flags & + ISCSI_FLAG_CMD_FINAL); + req->target_task_tag = get_next_ttt(conn->session); + + if (!param->immediate_data && req->pdu.datasize) + eprintk("%x %x\n", cmd_itt(req), req_hdr->cdb[0]); + + if (param->initial_r2t && + !(req_hdr->flags & ISCSI_FLAG_CMD_FINAL)) + eprintk("%x %x\n", cmd_itt(req), req_hdr->cdb[0]); + + if (req_hdr->cdb[0] == WRITE_VERIFY && req_hdr->cdb[1] & 0x02) + eprintk("Verification is ignored %x\n", cmd_itt(req)); + + if (req->pdu.datasize) { + if (cmnd_recv_pdu(conn, req->scmd, 0, + req->pdu.datasize) < 0) + BUG_ON(1); + } + break; + } + error: + default: + eprintk("Unsupported %x\n", req_hdr->cdb[0]); + create_sense_rsp(req, ILLEGAL_REQUEST, 0x20, 0x0); + cmnd_skip_data(req); + break; + } + + return; +} + +static void data_out_start(struct iscsi_conn *conn, struct istgt_cmd *cmnd) +{ + struct iscsi_data *req = (struct iscsi_data *)&cmnd->pdu.bhs; + struct istgt_cmd *scsi_cmnd = NULL; + uint32_t offset = be32_to_cpu(req->offset); + + update_stat_sn(cmnd); + + cmnd->req = scsi_cmnd = cmnd_find_hash(conn->session, req->itt, req->ttt); + if (!scsi_cmnd) { + eprintk("unable to find scsi task %x %x\n", + cmd_itt(cmnd), cmd_ttt(cmnd)); + goto skip_data; + } + + if (scsi_cmnd->r2t_length < cmnd->pdu.datasize) { + eprintk("invalid data len %x %u %u\n", + cmd_itt(scsi_cmnd), cmnd->pdu.datasize, scsi_cmnd->r2t_length); + goto skip_data; + } + + if (scsi_cmnd->r2t_length + offset != cmnd_write_size(scsi_cmnd)) { + eprintk("%x %u %u %u\n", cmd_itt(scsi_cmnd), scsi_cmnd->r2t_length, + offset, cmnd_write_size(scsi_cmnd)); + goto skip_data; + } + + scsi_cmnd->r2t_length -= cmnd->pdu.datasize; + + if (req->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) { + /* unsolicited burst data */ + if (scsi_cmnd->pdu.bhs.flags & ISCSI_FLAG_CMD_FINAL) { + eprintk("unexpected data from %x %x\n", + cmd_itt(cmnd), cmd_ttt(cmnd)); + goto skip_data; + } + } + + dprintk("%u %p %p %u %u\n", req->ttt, cmnd, scsi_cmnd, + offset, cmnd->pdu.datasize); + + if (cmnd_recv_pdu(conn, scsi_cmnd->scmd, offset, cmnd->pdu.datasize) < 0) + goto skip_data; + return; + +skip_data: + cmnd->pdu.bhs.opcode = ISCSI_OP_DATA_REJECT; + cmnd_skip_pdu(cmnd); + return; +} + +static void data_out_end(struct iscsi_conn *conn, struct istgt_cmd *cmnd) +{ + struct iscsi_data *req = (struct iscsi_data *) &cmnd->pdu.bhs; + struct istgt_cmd *scsi_cmnd; + uint32_t offset; + + BUG_ON(!cmnd); + scsi_cmnd = cmnd->req; + BUG_ON(!scsi_cmnd); + + if (conn->read_overflow) { + eprintk("%x %u\n", cmd_itt(cmnd), conn->read_overflow); + offset = be32_to_cpu(req->offset); + offset += cmnd->pdu.datasize - conn->read_overflow; + if (cmnd_recv_pdu(conn, scsi_cmnd->scmd, offset, + conn->read_overflow) < 0) + BUG_ON(1); + return; + } + + if (req->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) { + if (req->flags & ISCSI_FLAG_CMD_FINAL) { + scsi_cmnd->is_unsolicited_data = 0; + if (!cmd_pending(scsi_cmnd)) + scsi_cmnd_exec(scsi_cmnd); + } + } else { + /* TODO : proper error handling */ + if (!(req->flags & ISCSI_FLAG_CMD_FINAL) && + scsi_cmnd->r2t_length == 0) + eprintk("initiator error %x\n", cmd_itt(scsi_cmnd)); + + if (!(req->flags & ISCSI_FLAG_CMD_FINAL)) + goto out; + + scsi_cmnd->outstanding_r2t--; + + if (scsi_cmnd->r2t_length == 0) + BUG_ON(!list_empty(&scsi_cmnd->pdu_list)); + + scsi_cmnd_exec(scsi_cmnd); + } + +out: + iscsi_cmnd_remove(cmnd); + return; +} + +/* static int __cmnd_abort(struct istgt_cmd *cmnd) */ +/* { */ +/* if (!cmnd_waitio(cmnd)) { */ +/* cmnd_release(cmnd, 1); */ +/* return 0; */ +/* } else */ +/* return -ISCSI_RESPONSE_UNKNOWN_TASK; */ +/* } */ + +/* static int cmnd_abort(struct iscsi_session *session, u32 itt) */ +/* { */ +/* struct istgt_cmd *cmnd; */ +/* int err = -ISCSI_RESPONSE_UNKNOWN_TASK; */ + +/* if ((cmnd = cmnd_find_hash(session, itt, ISCSI_RESERVED_TAG))) { */ +/* eprintk("%x %x %x %u %u %u %u\n", cmd_itt(cmnd), cmd_opcode(cmnd), */ +/* cmnd->r2t_length, cmnd_scsicode(cmnd), */ +/* cmnd_write_size(cmnd), cmnd->is_unsolicited_data, */ +/* cmnd->outstanding_r2t); */ +/* err = __cmnd_abort(cmnd); */ +/* } */ + +/* return err; */ +/* } */ + +/* static int target_reset(struct istgt_cmd *req, u32 lun, int all) */ +/* { */ +/* struct iscsi_target *target = req->conn->session->target; */ +/* struct iscsi_session *session; */ +/* struct iscsi_conn *conn; */ +/* struct istgt_cmd *cmnd, *tmp; */ + +/* list_for_each_entry(session, &target->session_list, list) { */ +/* list_for_each_entry(conn, &session->conn_list, list) { */ +/* list_for_each_entry_safe(cmnd, tmp, &conn->pdu_list, conn_list) { */ +/* if (cmnd == req) */ +/* continue; */ + +/* if (all) */ +/* __cmnd_abort(cmnd); */ +/* else if (translate_lun(cmd_hdr(cmnd)->lun) == lun) */ +/* __cmnd_abort(cmnd); */ +/* } */ +/* } */ +/* } */ + +/* return 0; */ +/* } */ + +/* static void task_set_abort(struct istgt_cmd *req) */ +/* { */ +/* struct iscsi_session *session = req->conn->session; */ +/* struct iscsi_conn *conn; */ +/* struct istgt_cmd *cmnd, *tmp; */ + +/* list_for_each_entry(conn, &session->conn_list, list) { */ +/* list_for_each_entry_safe(cmnd, tmp, &conn->pdu_list, conn_list) { */ +/* if (cmnd != req) */ +/* __cmnd_abort(cmnd); */ +/* } */ +/* } */ +/* } */ + +static void execute_task_management(struct istgt_cmd *req) +{ +/* struct iscsi_conn *conn = req->conn; */ +/* struct iscsi_target *target = conn->session->target; */ + struct istgt_cmd *rsp; + struct iscsi_tm *req_hdr = (struct iscsi_tm *)&req->pdu.bhs; + struct iscsi_tm_rsp *rsp_hdr; + int function = req_hdr->flags & ISCSI_FLAG_TM_FUNC_MASK; + + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + rsp_hdr = (struct iscsi_tm_rsp *)&rsp->pdu.bhs; + + rsp_hdr->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->itt = req_hdr->itt; +/* rsp_hdr->response = ISCSI_TMF_RSP_COMPLETE; */ + rsp_hdr->response = ISCSI_TMF_RSP_REJECTED; + + eprintk("%x %d %x\n", cmd_itt(req), function, req_hdr->rtt); + +/* switch (function) { */ +/* case ISCSI_FUNCTION_ABORT_TASK: */ +/* case ISCSI_FUNCTION_ABORT_TASK_SET: */ +/* case ISCSI_FUNCTION_CLEAR_ACA: */ +/* case ISCSI_FUNCTION_CLEAR_TASK_SET: */ +/* case ISCSI_FUNCTION_LOGICAL_UNIT_RESET: */ +/* lun = translate_lun(req_hdr->lun); */ +/* if (!volume_lookup(target, lun)) { */ +/* rsp_hdr->response = ISCSI_RESPONSE_UNKNOWN_LUN; */ +/* goto out; */ +/* } */ +/* } */ + +/* switch (function) { */ +/* case ISCSI_FUNCTION_ABORT_TASK: */ +/* if ((err = cmnd_abort(conn->session, req_hdr->rtt)) < 0) */ +/* rsp_hdr->response = -err; */ +/* break; */ +/* case ISCSI_FUNCTION_ABORT_TASK_SET: */ +/* task_set_abort(req); */ +/* break; */ +/* case ISCSI_FUNCTION_CLEAR_ACA: */ +/* rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED; */ +/* break; */ +/* case ISCSI_FUNCTION_CLEAR_TASK_SET: */ +/* rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED; */ +/* break; */ +/* case ISCSI_FUNCTION_LOGICAL_UNIT_RESET: */ +/* target_reset(req, translate_lun(req_hdr->lun), 0); */ +/* break; */ +/* case ISCSI_FUNCTION_TARGET_WARM_RESET: */ +/* case ISCSI_FUNCTION_TARGET_COLD_RESET: */ +/* target_reset(req, 0, 1); */ +/* if (function == ISCSI_FUNCTION_TARGET_COLD_RESET) */ +/* set_cmnd_close(rsp); */ +/* break; */ +/* case ISCSI_FUNCTION_TASK_REASSIGN: */ +/* rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED; */ +/* break; */ +/* default: */ +/* rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_REJECTED; */ +/* break; */ +/* } */ +/* out: */ + iscsi_cmnd_init_write(rsp); +} + +static void noop_out_exec(struct istgt_cmd *req) +{ + struct istgt_cmd *rsp; + struct iscsi_nopin *rsp_hdr; + + if (cmd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) { + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + + rsp_hdr = (struct iscsi_nopin *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_NOOP_IN; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->itt = req->pdu.bhs.itt; + rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + +/* if (req->pdu.datasize) */ +/* assert(req->tio); */ +/* else */ +/* assert(!req->tio); */ + +/* if (req->tio) { */ +/* tio_get(req->tio); */ +/* rsp->tio = req->tio; */ +/* } */ + + BUG_ON(get_pgcnt(req->pdu.datasize, 0) >= ISCSI_CONN_IOV_MAX); + rsp->pdu.datasize = req->pdu.datasize; + iscsi_cmnd_init_write(rsp); + } else + iscsi_cmnd_remove(req); +} + +static void logout_exec(struct istgt_cmd *req) +{ + struct iscsi_logout *req_hdr; + struct istgt_cmd *rsp; + struct iscsi_logout_rsp *rsp_hdr; + + req_hdr = (struct iscsi_logout *)&req->pdu.bhs; + rsp = iscsi_cmnd_create_rsp_cmnd(req, 1); + rsp_hdr = (struct iscsi_logout_rsp *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_LOGOUT_RSP; + rsp_hdr->flags = ISCSI_FLAG_CMD_FINAL; + rsp_hdr->itt = req_hdr->itt; + set_cmd_close(rsp); + iscsi_cmnd_init_write(rsp); +} + +static void iscsi_cmnd_exec(struct istgt_cmd *cmnd) +{ + dprintk("%p,%x,%u\n", cmnd, cmd_opcode(cmnd), + cmnd->pdu.bhs.statsn); + + switch (cmd_opcode(cmnd)) { + case ISCSI_OP_NOOP_OUT: + noop_out_exec(cmnd); + break; + case ISCSI_OP_SCSI_CMD: + scsi_cmnd_exec(cmnd); + break; + case ISCSI_OP_SCSI_TMFUNC: + execute_task_management(cmnd); + break; + case ISCSI_OP_LOGOUT: + logout_exec(cmnd); + break; + case ISCSI_OP_SCSI_REJECT: + iscsi_cmnd_init_write(get_rsp_cmnd(cmnd)); + break; + case ISCSI_OP_TEXT: + case ISCSI_OP_SNACK: + break; + default: + eprintk("unexpected cmnd op %x\n", cmd_opcode(cmnd)); + break; + } +} + +static void __cmnd_send_pdu(struct iscsi_conn *conn, struct scatterlist *sg, + uint32_t offset, uint32_t size) +{ +/* dprintk(D_GENERIC, "%p %u,%u\n", tio, offset, size); */ + offset += sg->offset; + +/* assert(offset <= sg->offset + tio->size); */ +/* assert(offset + size <= tio->offset + tio->size); */ + + conn->write_sg = sg; + conn->write_offset = offset; + conn->write_size += size; +} + +static void cmnd_send_pdu(struct iscsi_conn *conn, struct istgt_cmd *cmnd) +{ + uint32_t size; + + if (!cmnd->pdu.datasize) + return; + + size = (cmnd->pdu.datasize + 3) & -4; + BUG_ON(!cmnd->sg); + __cmnd_send_pdu(conn, cmnd->sg, 0, size); +} + +static void set_cork(struct socket *sock, int on) +{ + int opt = on; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(get_ds()); + sock->ops->setsockopt(sock, SOL_TCP, TCP_CORK, (void *)&opt, sizeof(opt)); + set_fs(oldfs); +} + +void cmnd_release(struct istgt_cmd *cmnd, int force) +{ + struct istgt_cmd *req, *rsp; + int is_last = 0; + + if (!cmnd) + return; + + req = cmnd->req; + is_last = cmd_final(cmnd); + + if (force) { + while (!list_empty(&cmnd->pdu_list)) { + rsp = list_entry(cmnd->pdu_list.next, struct istgt_cmd, pdu_list); + list_del_init(&rsp->list); + list_del(&rsp->pdu_list); + iscsi_cmnd_remove(rsp); + } + list_del_init(&cmnd->list); + } + + if (cmd_hashed(cmnd)) + cmnd_remove_hash(cmnd); + + list_del_init(&cmnd->pdu_list); + iscsi_cmnd_remove(cmnd); + + if (is_last) { + BUG_ON(force); + BUG_ON(!req); + cmnd_release(req, 0); + } + + return; +} + +void cmnd_tx_start(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iovec *iop; + + dprintk("%p:%x\n", cmnd, cmd_opcode(cmnd)); + BUG_ON(!cmnd); + iscsi_cmnd_set_length(&cmnd->pdu); + + set_cork(conn->sock, 1); + + conn->write_iop = iop = conn->write_iov; + iop->iov_base = &cmnd->pdu.bhs; + iop->iov_len = sizeof(cmnd->pdu.bhs); + iop++; + conn->write_size = sizeof(cmnd->pdu.bhs); + + switch (cmd_opcode(cmnd)) { + case ISCSI_OP_NOOP_IN: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + case ISCSI_OP_SCSI_CMD_RSP: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + case ISCSI_OP_SCSI_TMFUNC_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_TEXT_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_SCSI_DATA_IN: + { + struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *)&cmnd->pdu.bhs; + uint32_t offset; + + cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLAG_CMD_FINAL) ? 1 : 0); + offset = rsp->offset; + rsp->offset = cpu_to_be32(offset); + BUG_ON(!cmnd->sg); + __cmnd_send_pdu(conn, cmnd->sg, offset, cmnd->pdu.datasize); + break; + } + case ISCSI_OP_LOGOUT_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_R2T: + cmnd_set_sn(cmnd, 0); + cmnd->pdu.bhs.statsn = cpu_to_be32(conn->stat_sn); + break; + case ISCSI_OP_ASYNC_EVENT: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_REJECT: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + default: + eprintk("unexpected cmnd op %x\n", cmd_opcode(cmnd)); + break; + } + + iop->iov_len = 0; + // move this? + conn->write_size = (conn->write_size + 3) & -4; +} + +void cmnd_tx_end(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + + dprintk("%p:%x\n", cmnd, cmd_opcode(cmnd)); + switch (cmd_opcode(cmnd)) { + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_SCSI_CMD_RSP: + case ISCSI_OP_SCSI_TMFUNC_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC_EVENT: + case ISCSI_OP_REJECT: + case ISCSI_OP_SCSI_DATA_IN: + case ISCSI_OP_LOGOUT_RSP: + break; + default: + eprintk("unexpected cmnd op %x\n", cmd_opcode(cmnd)); + BUG_ON(1); + break; + } + + if (cmd_close(cmnd)) + conn_close(conn); + + list_del_init(&cmnd->list); + set_cork(cmnd->conn->sock, 0); +} + +/** + * Push the command for execution. + * This functions reorders the commands. + * Called from the read thread. + * + * iscsi_session_push_cmnd - + * @cmnd: ptr to command + */ + +static void iscsi_session_push_cmnd(struct istgt_cmd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct list_head *entry; + uint32_t cmd_sn; + + dprintk("%p:%x %u,%u\n", + cmnd, cmd_opcode(cmnd), cmnd->pdu.bhs.statsn, + session->exp_cmd_sn); + + if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) { + iscsi_cmnd_exec(cmnd); + return; + } + + cmd_sn = cmnd->pdu.bhs.statsn; + if (cmd_sn == session->exp_cmd_sn) { + while (1) { + session->exp_cmd_sn = ++cmd_sn; + iscsi_cmnd_exec(cmnd); + + if (list_empty(&session->pending_list)) + break; + cmnd = list_entry(session->pending_list.next, struct istgt_cmd, list); + if (cmnd->pdu.bhs.statsn != cmd_sn) + break; +/* eprintk("find out-of-order %x %u %u\n", */ +/* cmd_itt(cmnd), cmd_sn, cmnd->pdu.bhs.statsn); */ + list_del_init(&cmnd->list); + clear_cmd_pending(cmnd); + } + } else { +/* eprintk("out-of-order %x %u %u\n", */ +/* cmd_itt(cmnd), cmd_sn, session->exp_cmd_sn); */ + + set_cmd_pending(cmnd); + if (before(cmd_sn, session->exp_cmd_sn)) /* close the conn */ + eprintk("unexpected cmd_sn (%u,%u)\n", cmd_sn, session->exp_cmd_sn); + + if (after(cmd_sn, session->exp_cmd_sn + session->max_queued_cmnds)) + eprintk("too large cmd_sn (%u,%u)\n", cmd_sn, session->exp_cmd_sn); + + list_for_each(entry, &session->pending_list) { + struct istgt_cmd *tmp = list_entry(entry, struct istgt_cmd, list); + if (before(cmd_sn, tmp->pdu.bhs.statsn)) + break; + } + + BUG_ON(!list_empty(&cmnd->list)); + + list_add_tail(&cmnd->list, entry); + } +} + +static int check_segment_length(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iscsi_sess_param *param = &conn->session->param; + + if (cmnd->pdu.datasize > param->max_recv_data_length) { + eprintk("too lond data %x %u %u\n", cmd_itt(cmnd), + cmnd->pdu.datasize, param->max_recv_data_length); + + if (get_pgcnt(cmnd->pdu.datasize, 0) > ISCSI_CONN_IOV_MAX) { + conn_close(conn); + return -EINVAL; + } + } + + return 0; +} + +void cmnd_rx_start(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + int err = 0; + + if (check_segment_length(cmnd) < 0) + return; + + switch (cmd_opcode(cmnd)) { + case ISCSI_OP_NOOP_OUT: + err = noop_out_start(conn, cmnd); + break; + case ISCSI_OP_SCSI_CMD: + if (!(err = cmnd_insert_hash(cmnd))) + scsi_cmnd_start(conn, cmnd); + break; + case ISCSI_OP_SCSI_TMFUNC: + err = cmnd_insert_hash(cmnd); + break; + case ISCSI_OP_SCSI_DATA_OUT: + data_out_start(conn, cmnd); + break; + case ISCSI_OP_LOGOUT: + err = cmnd_insert_hash(cmnd); + break; + case ISCSI_OP_TEXT: + case ISCSI_OP_SNACK: + err = -ISCSI_REASON_CMD_NOT_SUPPORTED; + break; + default: + err = -ISCSI_REASON_CMD_NOT_SUPPORTED; + break; + } + + if (err < 0) { + eprintk("%x %x %d\n", cmd_opcode(cmnd), cmd_itt(cmnd), err); + iscsi_cmnd_reject(cmnd, -err); + } +} + +void cmnd_rx_end(struct istgt_cmd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + + dprintk("%p:%x\n", cmnd, cmd_opcode(cmnd)); + switch (cmd_opcode(cmnd)) { + case ISCSI_OP_SCSI_REJECT: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_TMFUNC: + case ISCSI_OP_TEXT: + case ISCSI_OP_LOGOUT: + iscsi_session_push_cmnd(cmnd); + break; + case ISCSI_OP_SCSI_DATA_OUT: + data_out_end(conn, cmnd); + break; + case ISCSI_OP_SNACK: + break; + case ISCSI_OP_PDU_REJECT: + iscsi_cmnd_init_write(get_rsp_cmnd(cmnd)); + break; + case ISCSI_OP_DATA_REJECT: + cmnd_release(cmnd, 0); + break; + default: + eprintk("unexpected cmnd op %x\n", cmd_opcode(cmnd)); + BUG(); + break; + } +} + +static int buffer_ready(struct scsi_cmnd *scmd, + void (*done)(struct scsi_cmnd *)) +{ + struct istgt_cmd *cmnd = (struct istgt_cmd *) scmd->SCp.ptr; + + cmnd->done = done; + complete(&cmnd->event); + return 0; +} + + +static struct iscsi_sess_param default_session_param = { + .initial_r2t = 1, + .immediate_data = 1, + .max_connections = 1, + .max_recv_data_length = 8192, + .max_xmit_data_length = 8192, + .max_burst_length = 262144, + .first_burst_length = 65536, + .default_wait_time = 2, + .default_retain_time = 20, + .max_outstanding_r2t = 1, + .data_pdu_inorder = 1, + .data_sequence_inorder = 1, + .error_recovery_level = 0, + .header_digest = DIGEST_NONE, + .data_digest = DIGEST_NONE, + .ofmarker = 0, + .ifmarker = 0, + .ofmarkint = 2048, + .ifmarkint = 2048, +}; + +static struct iscsi_trgt_param default_target_param = { + .queued_cmnds = DEFAULT_NR_QUEUED_CMNDS, +}; + +static struct iscsi_transport istgt_transport; + +static struct iscsi_cls_session * +istgt_session_create(struct scsi_transport_template *scsit, + uint32_t initial_cmdsn, uint32_t *sid) +{ + struct Scsi_Host *shost; + struct iscsi_session *session; + int err, i; + + shost = iscsi_transport_create_session(scsit, &istgt_transport); + if (!shost) + return NULL; + + if (scsi_tgt_alloc_queue(shost)) + goto destroy_session; + + session = iscsi_hostdata(shost->hostdata); + memset(session, 0, sizeof(*session)); + + dprintk("%p %u %" PRIx64 "\n", session, session->shost->host_no); + + session->shost = shost; + *sid = session->sid = shost->host_no; + memcpy(&session->param, &default_session_param, + sizeof(default_session_param)); + memcpy(&session->trgt_param, &default_target_param, + sizeof(default_target_param)); + init_MUTEX(&session->target_sem); + INIT_LIST_HEAD(&session->session_list); + + session->max_queued_cmnds = session->trgt_param.queued_cmnds; + session->exp_cmd_sn = initial_cmdsn + 1; + session->max_cmd_sn = initial_cmdsn + 1; + + INIT_LIST_HEAD(&session->conn_list); + INIT_LIST_HEAD(&session->pending_list); + + spin_lock_init(&session->cmnd_hash_lock); + for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) + INIT_LIST_HEAD(&session->cmnd_hash[i]); + + session->next_ttt = 1; + + nthread_init(session); + err = nthread_start(session); + if (err) + goto destroy_session; + + return hostdata_session(shost->hostdata); + +destroy_session: + iscsi_transport_destroy_session(shost); + return NULL; +} + +static void istgt_session_destroy(struct iscsi_cls_session *cls_session) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); + struct iscsi_session *session = iscsi_hostdata(shost->hostdata); + int i; + + dprintk("%" PRIx64 "\n", session->sid); + + if (!list_empty(&session->conn_list)) { + eprintk("%" PRIx64 " still have connections\n", session->sid); + BUG(); + } + + BUG_ON(!list_empty(&session->conn_list)); + + for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) + BUG_ON(!list_empty(&session->cmnd_hash[i])); + + down(&session->target_sem); + up(&session->target_sem); + + nthread_stop(session); + iscsi_transport_destroy_session(shost); +} + +static int +istgt_conn_get_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param key, uint32_t *value) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_sess_param *param = &conn->session->param; + + switch(key) { + case ISCSI_PARAM_MAX_RECV_DLENGTH: + *value = param->max_recv_data_length; + break; + case ISCSI_PARAM_MAX_XMIT_DLENGTH: + *value = param->max_xmit_data_length; + break; + case ISCSI_PARAM_HDRDGST_EN: + *value = param->header_digest; + break; + case ISCSI_PARAM_DATADGST_EN: + *value = param->data_digest; + break; + default: + return ISCSI_ERR_PARAM_NOT_FOUND; + } + + return 0; +} + +static int +istgt_session_get_param(struct iscsi_cls_session *cls_session, + enum iscsi_param key, uint32_t *value) +{ + struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); + struct iscsi_session *session = iscsi_hostdata(shost->hostdata); + struct iscsi_sess_param *param = &session->param; + + switch(key) { + case ISCSI_PARAM_INITIAL_R2T_EN: + *value = param->initial_r2t; + break; + case ISCSI_PARAM_MAX_R2T: + *value = param->max_outstanding_r2t; + break; + case ISCSI_PARAM_IMM_DATA_EN: + *value = param->immediate_data; + break; + case ISCSI_PARAM_FIRST_BURST: + *value = param->first_burst_length; + break; + case ISCSI_PARAM_MAX_BURST: + *value = param->max_burst_length; + break; + case ISCSI_PARAM_PDU_INORDER_EN: + *value = param->data_pdu_inorder; + break; + case ISCSI_PARAM_DATASEQ_INORDER_EN: + *value = param->data_sequence_inorder; + break; + case ISCSI_PARAM_ERL: + *value = param->error_recovery_level; + break; + case ISCSI_PARAM_IFMARKER_EN: + *value = param->ifmarker; + break; + case ISCSI_PARAM_OFMARKER_EN: + *value = param->ofmarker; + break; + default: + return ISCSI_ERR_PARAM_NOT_FOUND; + } + + return 0; +} + +static int +istgt_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param key, + uint32_t value) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_session *session = conn->session; + struct iscsi_sess_param *param = &session->param; + + switch(key) { + case ISCSI_PARAM_MAX_RECV_DLENGTH: + param->max_recv_data_length = value; + break; + case ISCSI_PARAM_MAX_XMIT_DLENGTH: + param->max_xmit_data_length = value; + break; + case ISCSI_PARAM_HDRDGST_EN: + param->header_digest = value; + break; + case ISCSI_PARAM_DATADGST_EN: + param->data_digest = value; + break; + case ISCSI_PARAM_INITIAL_R2T_EN: + param->initial_r2t = value; + break; + case ISCSI_PARAM_MAX_R2T: + param->max_outstanding_r2t = value; + break; + case ISCSI_PARAM_IMM_DATA_EN: + param->immediate_data = value; + break; + case ISCSI_PARAM_FIRST_BURST: + param->first_burst_length = value; + break; + case ISCSI_PARAM_MAX_BURST: + param->max_burst_length = value; + break; + case ISCSI_PARAM_PDU_INORDER_EN: + param->data_pdu_inorder = value; + break; + case ISCSI_PARAM_DATASEQ_INORDER_EN: + param->data_sequence_inorder = value; + break; + case ISCSI_PARAM_ERL: + param->error_recovery_level = value; + break; + case ISCSI_PARAM_IFMARKER_EN: + param->ifmarker = value; + break; + case ISCSI_PARAM_OFMARKER_EN: + param->ofmarker = value; + break; + default: + break; + } + + return 0; +} + +static struct scsi_host_template istgt_sht = { + .name = THIS_NAME, + .module = THIS_MODULE, + .can_queue = DEFAULT_NR_QUEUED_CMNDS, + .sg_tablesize = SG_ALL, + .use_clustering = DISABLE_CLUSTERING, + .max_sectors = 65536, + .transfer_response = scsi_cmnd_done, + .transfer_data = buffer_ready, +}; + +static struct iscsi_transport istgt_transport = { + .owner = THIS_MODULE, + .name = "tcp_tgt", + .host_template = &istgt_sht, + .hostdata_size = sizeof(struct iscsi_session), + .max_conn = 1, + .max_cmd_len = 16, + .create_session = istgt_session_create, + .destroy_session = istgt_session_destroy, + .create_conn = istgt_conn_create, + .destroy_conn = istgt_conn_destroy, + .bind_conn = istgt_conn_bind, + .start_conn = istgt_conn_start, + .set_param = istgt_set_param, + .get_session_param = istgt_session_get_param, + .get_conn_param = istgt_conn_get_param, + +}; + +static void istgt_exit(void) +{ + kmem_cache_destroy(istgt_cmd_cache); + iscsi_unregister_transport(&istgt_transport); +} + +static int istgt_init(void) +{ + printk("iSCSI Target Software for Linux Target Framework %s\n", + VERSION_STRING); + + istgt_cmd_cache = kmem_cache_create("istgt_cmd", + sizeof(struct istgt_cmd), + 0, 0, NULL, NULL); + if (!istgt_cmd_cache) + return -ENOMEM; + + if (!iscsi_register_transport(&istgt_transport)) + goto free_cmd_cache; + + return 0; + +free_cmd_cache: + kmem_cache_destroy(istgt_cmd_cache); + return -ENOMEM; + +} + +module_init(istgt_init); +module_exit(istgt_exit); + +MODULE_LICENSE("GPL"); - : send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html