SCSI Userspace Target: example target driver
RFC PATCH 3/3 SCSI Userspace Target: example target driver
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.
drivers/scsi/iscsi_tcp_tgt.c +1941 -0
@@ -0,0 +1,1941 @@ +/* +* (C) 2004 - 2005 FUJITA Tomonori <[email protected]> +* Copyright (C) 2002 - 2003 Ardis Technolgies <[email protected]> +* 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" );